linop.LinearOperator#

class bartorch.linop.LinearOperator#

Bases: Operator

A linear map between two C-order shapes, with an adjoint.

A subclass is defined either by _create(), which builds one of BART’s operators, or in Python by forward() and adjoint(), and normal() where a cheaper form exists. BART reaches a Python-defined operator through callbacks, one crossing into Python per application.

Both kinds compose into a single BART operator, are solved by bartorch.optim, and differentiate in torch. The backward pass of A(x) is A.adjoint, which for complex tensors is the conjugate Wirtinger gradient torch expects, not the transpose.

ishape, oshape

Domain and codomain, C order.

Type:

tuple of int

device#

Where the operator does its arithmetic, when that is not where its operands are.

Type:

torch.device or None

Examples

An operator defined in Python, composed with a BART operator into one:

>>> class Phase(linop.LinearOperator):
...     def __init__(self, phase):
...         self.phase = phase
...         self.ishape = self.oshape = tuple(phase.shape)
...         super().__init__()
...     def forward(self, x, out=None):
...         return x * self.phase
...     def adjoint(self, y, out=None):
...         return y * self.phase.conj()
>>> A = linop.FFT((64, 64), axes=(-1, -2)) @ Phase(torch.exp(1j * torch.rand(64, 64)))
>>> y = A(x)        # recorded for autograd when x requires a gradient
>>> z = A.H(y)      # the adjoint; A.gram() is the normal operator A^H A
forward()#

A x, without recording for autograd.

out is written in place when given; for a BART-backed operator it must be a contiguous complex64 tensor of oshape on the input’s device.

adjoint()#

A^H y, without recording for autograd.

normal()#

A^H A x.

A BART-backed operator applies its own normal, which for a non-Cartesian encoding built with toeplitz=True is a convolution with a point spread function. Otherwise adjoint(forward(x)).

classmethod from_callbacks()#

An operator from Python functions, applied through BART.

forward maps ishape to oshape and adjoint back; normal, where a cheaper form exists, is adjoint(forward(x)) in one. Each receives a view of BART’s buffer, and every application crosses into Python.

property plan#

The encoding form this operator was lowered into, or None.

Diagnostic: reading it changes nothing about the operator. An MRI encoding reports what the planner chose for it – the transform, the element-wise factors on each side of it, the contraction, what is streamed, how the normal is applied, and which executor path ran it. The algebra carries the plan through, so a composition with one encoding in it reports that encoding’s plan; anything else has none.

plan.fused is the field to check: it is false where the coil-slab loop could not take the form and where a sum of terms was left as a chain, both of which give the same numbers several times slower. plan.executor is read back from the library after the build, so it reports the path that ran rather than the one intended.

Accessing this on a composition builds it, since the plan is decided by lowering and lowering is what building does.

Return type:

bartorch.linop._form.Plan or None

Examples

>>> A = linop.CartesianSense(maps, (64, 64), pattern=mask)
>>> A.plan.transform, A.plan.normal, A.plan.executor
('fft', 'kernel', 'slab')
>>> A.plan.fused
True
property H#

A^H, from BART’s own adjoint constructor.

The result is a BART operator rather than a Python wrapper, so A.H @ B is one operator and its normal is A A^H.

property T#

A^T, the adjoint without the conjugation, as conj(A).H.

conj()#

conj(A): conjugate the input, apply, conjugate the output.

gram()#

A^H A as an operator.

BART’s own, so an encoding built with toeplitz=True gives the point-spread convolution rather than the two applications.

cogram()#

A A^H as an operator.

opnorm()#

The spectral norm, by BART’s power iteration on A^H A.

BART starts the iteration from its process-global generator, so this returns a slightly different number each time it is called. It is an estimate: what a step size or a Lipschitz constant needs, not an exact singular value.

__call__()#

A x, recorded for autograd when x requires a gradient.

Parameters:
  • x (torch.Tensor) – Array of ishape.

  • out (torch.Tensor, default=None) – Contiguous complex64 array of oshape to write into, so that repeated applications reuse one buffer. Not allowed when x requires a gradient.

property dim_shape#

The domain, under pyxu’s name for it; the same as ishape.

property codim_shape#

The codomain, under pyxu’s name for it; the same as oshape.

property dim_size#

How many elements the domain holds.

property codim_size#

How many elements the codomain holds.

property dim_rank#

How many axes the domain has.

property codim_rank#

How many axes the codomain has.

to_nonlinear()#

The same operator as a NonlinearOperator.

pinv()#

(A^H A + damp I)^-1 A^H y, the damped least-squares solution.

Solved in closed form where BART has one that has been checked, and by bartorch.optim.CG otherwise; the same quantity either way.

BART offers the closed form through a norm_inv, which only linops/sum.c carries, and that routine divides by a count linop_sum_create overwrites afterwards – so it answers for a differently scaled operator than the one it is attached to. A class therefore opts in through _exact_pinv once a test holds the closed form and the solver to the same answer, and only ScaledSum does. Everything else takes the solver, chains, sums and adjoints included, since those drop the norm_inv regardless.

Parameters:
  • y (torch.Tensor) – Array of oshape.

  • damp (float, default=0.0) – The Tikhonov weight, CG’s lambda_.

  • **kwargs – Passed to CG, with x0 as the warm start. Refused when the closed form applies, because there is then no solver for them to configure.

Examples using LinearOperator#

Trajectories and transforms

Trajectories and transforms

Radial SENSE reconstruction

Radial SENSE reconstruction

Dynamic golden-angle radial MRI

Dynamic golden-angle radial MRI

Subspace-constrained T1 mapping

Subspace-constrained T1 mapping

Parameter maps straight from k-space

Parameter maps straight from k-space

Off-resonance correction of spiral imaging

Off-resonance correction of spiral imaging