Alignment solver reference
AlignConfig settings, Jacobians, coupling and linear-solve diagnostics.
Use this reference when changing AlignConfig fields, in Python or as keys of a
tomojax align --config TOML file. For data preparation,
mode selection, and interpreting recovered parameters, start with the
alignment guide. These options are experimental; the
free-voxel pilot still has a failed cell.
tj.align and tomojax align in pose and cor-then-pose modes start from
coupled_pose_config(): the joint coupling with
gn_joint_solver="pose_eliminated", Joseph plane sampling
(ray_integrator="joseph") and fp32 gathers. cor and full keep the
fixed-volume coupling, also with Joseph sampling. --dry-run prints the full
set for a mode. A bare AlignConfig() keeps the older alternating defaults,
which the statements of defaults below describe.
The opt-in spectral_setup schedule (or a setup stage with
objective_kind="projection_consistency", optimizer="spectral") initializes
parallel-beam axis direction and detector-u centre from Fourier common lines,
preserving bounds, frozen parameters and the supplied detector roll. It leaves
the volume unchanged: compose it before a pose stage or reconstruct its returned
scan. Its diagnostics say unvalidated_seed, not a successful validation fit;
the default full schedule still validates and reconstructs its setup updates.
For calibrated, unrolled laminography, a separate projection_consistency /
spectral setup stage with ("dx", "dz") initializes smooth detector-frame
motion from zero poses, also without reconstructing or claiming validation.
Gauss–Newton updates at interpolation boundaries
The default gn_jacobian="autodiff" differentiates the ray model within its
current interpolation cells. At a voxel-grid boundary this chooses
a one-sided derivative. For sharp objects, an update can enter a wrong local
minimum even for a small detector shift.
Set gn_jacobian="central" to form a symmetric finite-difference Jacobian for
the Gauss–Newton step. The forward prediction, acceptance loss, projector AD,
and matched volume adjoint stay unchanged. This option costs ten projection
evaluations per view for the five Jacobian columns, plus the residual and
candidate checks; it is a recovery option, not a derivative-speed claim.
gn_difference_step=1e-3 is a fraction of the smallest physical voxel pitch.
Rotation increments use that displacement divided by the grid's bounding
sphere radius, so the stencil scales with physical size and multiresolution
level. Both settings are saved in checkpoint configuration. This local stencil
does not guarantee recovery from large initial motion or poor identifiability.
Small rigid-transform products and Gauss–Newton normal equations explicitly request full FP32 multiplication precision. This prevents reduced global JAX matrix-multiply settings from quantising small pose updates on CUDA.
The gn_coupling="joint" option, which tj.align uses in pose and
cor-then-pose modes, is for per-view
least-squares alignment. It solves a damped linearised volume-and-pose
problem after each reconstruction refresh, then accepts the updated pair only
after scoring its constrained nonlinear objective. This addresses slow
alternation when free voxels can compensate for pose errors. AlignConfig()
keeps gn_coupling="fixed_volume". In align_multires, the implicit schedule
uses a joint_volume_pose stage when this option is enabled. Explicitly
fixed-volume schedule stages continue to keep the volume fixed; a custom joint
stage must declare objective_kind="joint_volume_pose" and optimizer="gn".
Joint GN requires opt_method="gn", pose_model="per_view" and
gather_dtype="fp32". It supports sampled and exact integration, active/frozen
pose parameters, pose bounds, volume masks, nonnegative voxels, pose smoothness,
and Huber-TV or zero volume regularisation. It does not support nonsmooth TV
or polynomial/spline pose models. gn_joint_iterations=40 bounds the inner
matrix-free PCG solve; gn_joint_rtol=1e-4 controls its residual stopping
criterion. gn_damping and gn_volume_damping damp pose and voxel increments
respectively; they are not priors on the recovered object. The returned
joint_linear_relative_residual is recomputed explicitly, so reaching the
iteration limit does not imply an accurate linear solve. The optional pose
column cache on the device is capped at 64 MiB. Host-staged joint solves can
retain the active FP32 columns in host memory for one linear update, reading
only the current view batch on the device. Planning reserves eight image
vectors (twelve with spectral preconditioning) and half the remaining available
host memory; without that space,
columns are recomputed. Each update rebuilds the cache at its current image
and poses, including its weights and residual filters.
Repeated joint calls reuse compiled objectives when array shapes and solver options match. Measurements, nominal poses, masks, detector coordinates and weights remain inputs to each call, so a new scan can reuse a program without reusing the previous scan's values. Changing shapes or program options still requires compilation; this reuse is separate from the optional JAX persistent cache for different processes. On the six-cell free-voxel pilot, reuse made warm calls faster and left cold start-up and process GPU memory unchanged.
The gn_joint_solver="pose_eliminated" method factors the pose block
and runs PCG on the volume Schur system, then back-substitutes poses. It solves
the same damped linear problem as the default "stacked" method, with the
same joint residual threshold and nonlinear acceptance. Without pose smoothness,
the factors are independent 5-by-5 blocks; with smoothness, a block-banded
factorisation retains inter-view coupling with storage linear in view count.
Damping and iteration budgets are unchanged. Independent dense, constrained
workflow and CUDA checks pass. On the free-voxel pilot it recovers the same
five of six cells as the stacked solve, faster on the tilted cells, and
sampled GPU memory rises from 280 to 320 MiB. It is the default for pose and cor-then-pose
alignment (AlignConfig() keeps "stacked"); it does not establish
complete recovery.
Joint steps report objective_kind="joint_volume_pose", their actual
projector backend, accepted line-search scale, and linear-solve diagnostics.
The shared central-difference stencil is unchanged. Recovery and performance
claims require the complete free-voxel pilot;
passing the numerical tests alone does not establish them.
Alignment's info["lipschitz"] and checkpoint lipschitz retain the effective
FISTA step bound, including smooth-TV curvature. Reusing a bound does not add a
safety factor on each outer iteration. If reconstruction falls back to public FISTA,
the adapter removes the existing TV contribution before that solver adds it;
the bound therefore stays consistent across both paths and resume. A fallback
override too small to supply a positive data bound triggers operator-norm
estimation. The public FistaConfig.lipschitz itself remains a data-term bound.
Without an override, Huber-FISTA alignment initialises its data bound from
max(A.T @ (A @ ones)), with a 20% margin applied once. For the nonnegative
trilinear ray model this row sum bounds the normal operator's largest
eigenvalue. It costs one projection/adjoint pair and accounts for actual
physical sampling, including detector density and oblique rays. The old
dimension-only estimate did not scale with physical length units.