ModelOperator#
- class torchsim.ModelOperator(acquisition, *unknown, bounds=None, scale=None, amplitude=True, subspace=None)[source]#
Bases:
ModuleA signal model over parameter maps, with its derivatives.
Maps are stacked on the last axis, as everywhere else in TorchSim:
(..., channels)in,(..., contrasts)out, every leading axis a voxel axis.physics()is where that flips to the channel-first convention deepinv and mri-nufft use, and it is the only place it flips.- Parameters:
acquisition (Simulator) – The sequence being inverted, with every property that is not being solved for already fixed on it. A property bound as a map – a measured B1, a known T1 – is one value per voxel and rides along.
unknown (str, optional) – The property names being solved for, in the order their channels appear. At least one.
bounds (mapping, optional) –
{name: (low, high)}, either endNonefor unbounded. A bound is kept by solving for a transformed variable, so no iterate is ever outside it – which matters more here than in a fit, because the model is evaluated at every voxel to predict every k-space sample and one voxel out of range corrupts the whole residual. A two-sided bound also puts the parameter on a scale of order one whatever its units, which is the preconditioning a mixed parameter set needs.scale (mapping, optional) –
{name: value}, the size of a step in a parameter left unbounded. Bounded parameters are already scaled by their interval.amplitude (bool, optional) – Whether to carry a complex amplitude multiplying the model output. On, which is what a reconstruction wants; off for a fit whose model already exposes its own proton density.
subspace (Subspace, optional) – A
Subspacethe prediction is projected through, for a reconstruction that solves in the temporal basis rather than in the contrasts.
Examples
operator = ModelOperator( MultiEchoSimulator(TE=echo_times, T1=1000.0), "T2", bounds={"T2": (10.0, 300.0)}, ) maps = operator.initial((128, 128), T2=80.0) images = operator.A(maps) # (128, 128, echoes), complex
Notes
The operator holds nothing on a device of its own. It follows the maps it is called with, taking the sequence and any bound tissue along, so there is nothing here to move and
to()moves nothing.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 on the parameters that remain. For a fat-water separation whose two fractions must sum to one, make the fat fraction
fthe only unknown and write water as1 - finside the model. The constraint then holds identically at every Gauss-Newton iterate, to the bit, rather than being restored after each one.- Raises:
ValueError – If nothing is unknown, if a bound or a scale names something that is not unknown, or if a bound is not increasing.
Methods
The name a reconstruction knows the forward operator by.
The directional derivative
J d, in one forward-mode pass.The adjoint product
J^H v, in one reverse-mode pass.Maps to start from, as the variables actually solved for.
The derivative block of every voxel, built.
This operator as a
deepinv.physics.Physics.This operator over a subset of the voxels.
The maps
xstands for, in their own units.- property channels#
How many map channels
xcarries.
- property names#
What each channel of
xis, in order.
- split(x)[source]#
The maps
xstands for, in their own units.- Parameters:
x (torch.Tensor) –
(..., channels), the variables being solved for.- Returns:
One entry per unknown, inside its bound, plus
"amplitude"complex where one is carried.- Return type:
- initial(shape=(), **values)[source]#
Maps to start from, as the variables actually solved for.
- Parameters:
shape (sequence of int, optional) – The voxel shape. Empty gives one voxel.
values (float or array-like, optional) –
{name: value}in the property’s own units, one value or one per voxel – a map broadcasts againstshape, which is what turns known parameters into a state and makes this the inverse ofsplit(). An unknown left out starts at the middle of its bound, and a one-sided or absent bound has no middle, so it must be given. The amplitude starts at one unlessamplitude=says otherwise.
- Returns:
(*shape, channels).- Return type:
torch.Tensor
- Raises:
ValueError – If a property with no two-sided bound is left out, or if a value sits on a bound – a value on a bound has no unconstrained image.
- select(keep)[source]#
This operator over a subset of the voxels.
A property bound as a map – a measured B1, a known efficiency – has one value per voxel, so a loop that retires the voxels it has finished has to take the maps with it. Anything that is not one value per voxel is left alone.
- Parameters:
keep (torch.Tensor) – An index or a boolean mask over the voxel axis.
- Returns:
A copy over those voxels.
- Return type:
- forward(x)[source]#
The contrasts these maps record.
- Parameters:
x (torch.Tensor) –
(..., channels).- Returns:
(..., contrasts), complex.- Return type:
torch.Tensor
- A(x)#
The name a reconstruction knows the forward operator by.
- A_jvp(x, d)[source]#
The directional derivative
J d, in one forward-mode pass.One pass whatever the channel count – the step itself is the tangent, so nothing is built column by column.
- Parameters:
x (torch.Tensor) –
(..., channels), the point and the direction.d (torch.Tensor) –
(..., channels), the point and the direction.
- Returns:
(..., contrasts), complex.- Return type:
torch.Tensor
- A_vjp(x, v)[source]#
The adjoint product
J^H v, in one reverse-mode pass.Real, because the maps are: what comes back is the real part of the conjugate product, which is the gradient direction of the squared modulus and the adjoint of
A_jvp()under the real inner product.- Parameters:
x (torch.Tensor) –
(..., channels), the point.v (torch.Tensor) –
(..., contrasts), the cotangent.
- Returns:
(..., channels), real.- Return type:
torch.Tensor
- jacobian(x)[source]#
The derivative block of every voxel, built.
Wanted where the blocks are solved outright, which is the voxel-wise fit; a Gauss-Newton step under an encoding operator wants
A_jvp()andA_vjp()instead and never sees a block.- Parameters:
x (torch.Tensor) –
(..., channels).- Returns:
(..., channels, contrasts), complex.- Return type:
torch.Tensor
- physics(**kwargs)[source]#
This operator as a
deepinv.physics.Physics.The wrapper is where the map axis moves to the front, because that is the convention deepinv and mri-nufft share:
(batch, channels, *xyz)in,(batch, contrasts, *xyz)out. Composing it with an encoding operator –encoding * operator.physics()– gives aComposedPhysicsevery deepinv optimizer and prior applies to.- Parameters:
kwargs (dict, optional) – Passed to
deepinv.physics.Physics, so a noise model can be given.- Return type:
deepinv.physics.Physics
- Raises:
ImportError – If deepinv is not installed. TorchSim does not depend on it.
Notes
A
ComposedPhysicstakes its Jacobian products by automatic differentiation through the whole chain, so composing this way gives up the analytic derivative below.GaussNewtonchains the two operators’ own products instead, and keeps it.