Skip to content

Fit apps.mobafit by per-voxel Levenberg-Marquardt; rename nlop.SignalModel to ModelOperator - #141

Merged
mcencini merged 1 commit into
mainfrom
claude/project-thread-4pzne2
Oct 12, 2026
Merged

mcencini merged 1 commit into
mainfrom
claude/project-thread-4pzne2

Conversation

@mcencini

Copy link
Copy Markdown
Collaborator

What changed

  • apps.mobafit: fits by Levenberg-Marquardt one voxel at a time. Each voxel's damped system is solved directly, with its own damping and its own acceptance test, and converged voxels drop out through model.select. cg_maxiter, inner, alpha, alpha_min, redu and reference are removed. The CLI's --liniter is accepted and ignored.
  • nlop.SignalModel: renamed to nlop.ModelOperator, matching blochsim.ModelOperator. It requires blochsim 0.0.12.
  • Documentation: AGENTS.md, the apps API page, the nonlinear explanation page and two examples now describe the new method.

Why

Without an encoding operator, the linearized problem is one small system per voxel. A global conjugate gradient converges only as fast as its worst-conditioned voxel. It stops with those voxels still pulled towards the Tikhonov reference.

Measured on 1000 voxels of a multi-echo decay with T2, M0 and a noise-floor offset unknown (true offset 0.05):

Fit Time Median T2 error Offset
Global conjugate gradient 8 s 6.0 % 0.086
Per-voxel Levenberg-Marquardt 1.2 s 2.4 % 0.0499

Validation

  • Suite: BARTORCH_LIBRARY=… PYTHONPATH=src pytest tests/test_apps.py tests/test_cli.py tests/test_nlop_blochsim.py gave 238 passed, 2 skipped.
  • Wider earlier run: an earlier run that also took in test_docs, test_docstrings and test_dependencies gave 290 passed, 3 skipped.
  • New test: test_mobafit_finds_what_scipy_finds holds the fit to scipy.optimize.least_squares with rtol=1e-3.
  • Lint: ./scripts/lint.sh is clean.
  • Not available: no CUDA device.

Public API and documentation

  • API: nlop.SignalModel is now nlop.ModelOperator, and apps.mobafit takes fewer keywords.
  • Docs: updated as listed above.
  • Release order: this follows pulserver/torchsim (blochsim 0.0.12) and precedes pulserver.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Qarafd1QwmJGNY8ZwdvwUY


Generated by Claude Code

…nalModel to ModelOperator

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qarafd1QwmJGNY8ZwdvwUY

Copy link
Copy Markdown
Collaborator Author

The wheel test step fails on No matching distribution found for blochsim>=0.0.12; PyPI has releases up to 0.0.11.

The pin belongs to this PR, because nlop.ModelOperator needs the API that pulserver/blochsim#41 trims. It resolves once blochsim 0.0.12 is released, and I'll re-run CI then. Until that release, any other check that installs the package will fail the same way.


Generated by Claude Code

@codecov

codecov Bot commented Oct 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.14815% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/bartorch/apps/_mobafit.py 97.93% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@mcencini
mcencini marked this pull request as ready for review October 12, 2026 17:43
@mcencini
mcencini merged commit 7e12f65 into main Oct 12, 2026
43 of 57 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants