NonlinearLeastSquares#

class torchsim.NonlinearLeastSquares(acquisition=None, *, bounds=None, initial=None, loop=None)[source]#

Bases: Estimator

Levenberg-Marquardt, stepping every voxel together.

Where a dictionary spans a grid whose size is the product of the parameter ranges, a nonlinear fit walks downhill from a starting guess and pays nothing for a third parameter beyond a third column of the Jacobian. What it gives up is the guarantee: it finds a local minimum of the residual, and which one depends on where it started.

This is an Estimator face on GaussNewton, and holds no algorithm of its own. Fitting images voxel by voxel and reconstructing maps from k-space are the same loop over the same ModelOperator with nothing encoding the voxels together, so what is here is only the adaptation: where the fit starts, and what the training set is for.

The loop it runs by default carries a per-voxel trust region, so every voxel takes its step in the same pass, carries its own damping, accepts or rejects on its own, and drops out when it has converged – and the remaining ones close up, so late iterations cost only what is left.

Parameters:
  • acquisition (Simulator, optional) – The sequence being inverted: a simulator that ships with TorchSim, one written by subclassing Simulator, or any other Simulator. Every tissue property that is neither unknown nor measured separately is fixed on it beforehand, with the constructor or bind(). Leave it out to fit from signals handed to fit() directly.

  • bounds (mapping, optional) – {name: (low, high)}, either end None for unbounded. A bound is kept by fitting a transformed variable rather than by clipping a result, so no iterate ever leaves the interval and the bound cannot be sitting exactly on the answer. It also puts every parameter on the same scale whatever its units, which is what the damping term assumes.

  • initial (mapping, optional) – {name: value} to start from, which must be strictly inside that property’s bound. Without one, fit() takes the median of the parameters the training set drew.

  • loop (GaussNewton, optional) – The solve to run, and where every knob it has lives – how many steps, how the damping moves, which tolerance stops a voxel. The default is Levenberg-Marquardt: a TrustRegion over direct(), twenty steps.

Notes

Equality constraints are written into the model, not declared here. A constraint that fixes one parameter in terms of the others removes a degree of freedom, so the way to impose it is to not have that freedom: write the model in terms of the parameters that remain. For a fat-water fit where the two fractions must sum to one, make the fat fraction f the only unknown and write water as 1 - f inside the model. The constraint then holds identically at every iterate, rather than being restored after each one.

The solve is iterative and runs without building a graph, so an estimate carries no gradient with respect to the measurement.

Examples

fit = NonlinearLeastSquares(
    FSESimulator(ESP=5.0, TR=1800.0, flip=train),
    bounds={"T2": (1.0, 500.0)},
).fit(T1=(200.0, 3000.0), T2=(10.0, 300.0))
maps = fit.map(volume)

A solve that needs more room, or a different one entirely:

NonlinearLeastSquares(
    bounds={"T2": (1.0, 500.0)},
    loop=GaussNewton(TrustRegion(tau=1e-3), solve=direct,
                     max_iterations=60),
)

Methods

fit

Fit the estimator over a sampling of the tissue it will meet.

from_coefficients

Map coefficients that are already in this estimator's basis.

map

Estimate the tissue a measurement came from.

training_set

Simulate a training set: signals, unknowns, and knowns.

uncertainty_of

The standard deviation the measurement noise leaves on each map.

iterations#

Steps the last solve took, and how many voxels ran out of them.

property fitted#

Whether a model and a starting point are both in place.