ACTS
Experiment-independent tracking
Loading...
Searching...
No Matches
Material effects in the Kalman filters

Pointwise material formalism shared by the KF and CKF.

Remarks
This page documents the concrete formalism used to apply material effects during Kalman filtering. For the high-level conceptual picture see Material effects, and for the material description itself see Material.

Both the Kalman Filter (KF) and the Combinatorial Kalman Filter (CKF) share the exact same material-update machinery. Rather than integrating material continuously along the trajectory, they use a pointwise ("thin scatterer") model: all material assigned to a surface is collapsed onto a single point, where it is applied as a deterministic correction to the momentum plus an additive Gaussian process noise on the diagonal of the covariance matrix. This matches the \(\vec w_{k-1}\) process-noise term \(\mathbf Q_{k-1}\) of the Kalman formalism [3]. The parametrizations used follow the ATLAS treatment of energy loss and multiple scattering [6].

The same code is used by the standalone Acts::MaterialInteractor propagator actor (used e.g. by plain propagation and the GSF); the KF and CKF, however, do not install that actor. Instead they call the underlying routine directly from their Kalman actors so the material noise can be interleaved with the Kalman update at precisely the right point on each surface.

Where the update is applied

On every surface the fitters apply the pointwise material update, using a Acts::MaterialUpdateMode that avoids double counting:

  • On a measurement surface the interaction is split into a PreUpdate (applied before the Kalman filter update, after the covariance has been transported to the surface) and a PostUpdate (applied after the update), so that noise is added on both approach and exit.
  • On a passive / material-only surface (including holes) a single FullUpdate is applied.
  • At the start and target surface the mode is automatically restricted to PostUpdate / PreUpdate respectively.

Whether scattering and/or energy loss are applied is controlled by the multipleScattering and energyLoss flags on the fitter options (Acts::KalmanFitterOptions, Acts::CombinatorialKalmanFilterOptions), both enabled by default.

Before the material is evaluated, the slab thickness is scaled by the surface path correction \(1/\cos\alpha\) to account for the incidence angle \(\alpha\) of the trajectory.

Multiple Coulomb scattering

Scattering does not change the momentum magnitude but widens the direction uncertainty. A single scattering standard deviation \(\theta_0\) is computed per surface. For all particles except electrons the Highland formula [5], in the parametrization of the Particle Data Group [9] (eq. 33.15), is used:

\[ \theta_0 = \frac{13.6\,\mathrm{MeV}}{\beta c\, p}\, q\, \sqrt{\frac{x}{X_0}} \left( 1 + 0.038\, \ln\!\left(\frac{x}{X_0}\frac{q^2}{\beta^2}\right) \right), \]

with the path length in radiation lengths \(x/X_0\), the momentum \(p\), the velocity \(\beta c\) and the charge \(q\). For electrons and positrons the Rossi–Greisen form (with a \(17.5\,\mathrm{MeV}\) prefactor) is used instead. The Highland term is evaluated as:

// RPP2018 eq. 33.15 (treats beta and q² consistently)
const float t = std::sqrt(xOverX0 * q2OverBeta2);
// log((x/X0) * (q²/beta²)) = log((sqrt(x/X0) * (q/beta))²)
// = 2 * log(sqrt(x/X0) * (q/beta))
return 13.6_MeV * momentumInv * t * (1.0f + 0.038f * 2 * std::log(t));

The single angle \(\theta_0\) is projected onto the two bound angular parameters, giving the process-noise variances that are added to the covariance:

\[ \sigma^2(\theta) = \theta_0^2, \qquad \sigma^2(\phi) = \left(\frac{\theta_0}{\sin\theta}\right)^2 . \]

The \(1/\sin\theta\) factor on \(\phi\) accounts for the metric of the polar parametrization. Note that only the diagonal \(\sigma^2(\phi)\) and \(\sigma^2(\theta)\) entries are modified; no \(\phi\)– \(\theta\) correlation is introduced:

const double theta0 =
computeMultipleScatteringTheta0(slab, absPdg, mass, qOverP, absQ);
// sigmaPhi = theta0 / sin(theta)
const double sigmaPhi =
theta0 * (direction.norm() / VectorHelpers::perp(direction));
result.variancePhi = sigmaPhi * sigmaPhi;
// sigmaTheta = theta0
result.varianceTheta = theta0 * theta0;

Energy loss

Energy loss enters in two ways: a deterministic shift of the mean \(q/p\) and an additional variance on \(q/p\).

Mean energy loss (state update). The mean ionization loss is computed from the Bethe formula [9] (eq. 33.5), including the density-effect correction (eq. 33.6) and the maximum single-collision energy transfer \(W_\text{max}\) (eq. 33.4). The resulting energy loss \(\Delta E\) is applied to the particle energy,

\[ E' = \sqrt{m^2 + p^2} - \Delta E \cdot s, \qquad p' = \sqrt{E'^2 - m^2}, \]

where \(s = \pm 1\) is the propagation direction (energy decreases in the forward direction, increases in the backward/smoothing direction). The updated \(q/p'\) is written back to the track state. A floor of \(p' \geq 10\,\mathrm{MeV}\) is applied so that a too-large loss does not push the particle to negative momentum:

// in forward(backward) propagation, energy decreases(increases) and
// variances increase(decrease)
const double nextE = fastHypot(mass, momentum) - effects.eLoss * propDir;
// put particle at rest if energy loss is too large
double nextP = (mass < nextE) ? fastCathetus(nextE, mass) : 0;
// minimum momentum below which we will not push particles via material
// update
// TODO 10 MeV might be quite low and we should make this configurable
static constexpr double minP = 10 * Acts::UnitConstants::MeV;
nextP = std::max(minP, nextP);
const double nextQOverP =
particleHypothesis.qOverP(nextP, std::copysign(absQ, qOverP));
// update track parameters
stepper.update(state.stepping, position, direction, nextQOverP, time);
Note
The mean correction inside the KF/CKF uses the ionization (Bethe) term only. Radiative (bremsstrahlung / Bethe–Heitler) losses are not added to the mean here. Because bremsstrahlung is strongly non-Gaussian, it cannot be modelled adequately by this pointwise Gaussian update; electron fitting should therefore use the GSF, which models the Bethe–Heitler distribution as a Gaussian mixture.

Energy-loss straggling (variance). The fluctuation of the ionization loss is described by the Landau–Vavilov distribution. Its full width at half maximum ( \(4\varepsilon\), [9] fig. 33.7) is converted to an equivalent Gaussian standard deviation \(\sigma_E\) via \(\sigma_E = \mathrm{fwhm}/(2\sqrt{2\ln 2})\), which is then propagated to a variance on \(q/p\) through the Jacobian \(\mathrm{d}(q/p)/\mathrm{d}E\):

\[ \sigma^2(q/p) = \left(\frac{\mathrm{d}(q/p)}{\mathrm{d}E}\right)^2 \sigma_E^2 . \]

This variance is added to the diagonal \(\sigma^2(q/p)\) covariance entry:

const double sigmaQoverP =
computeEnergyLossLandauSigmaQOverP(slab, mass, qOverP, absQ);
result.varianceQoverP = sigmaQoverP * sigmaQoverP;

Covariance update

The three variances computed above are added onto the corresponding diagonal entries of the bound covariance, \(\sigma^2(\phi)\), \(\sigma^2(\theta)\) and \(\sigma^2(q/p)\). The sign is set by the Acts::NoiseUpdateMode – noise is added during the forward filtering pass. Each variance is floored at zero to protect against numerical underflow. No off-diagonal correlations are created, and the local position and time entries are untouched:

state.stepping.cov(eBoundPhi, eBoundPhi) =
updateVariance(state.stepping.cov(eBoundPhi, eBoundPhi),
effects.variancePhi, noiseUpdateMode);
state.stepping.cov(eBoundTheta, eBoundTheta) =
updateVariance(state.stepping.cov(eBoundTheta, eBoundTheta),
effects.varianceTheta, noiseUpdateMode);
state.stepping.cov(eBoundQOverP, eBoundQOverP) =
updateVariance(state.stepping.cov(eBoundQOverP, eBoundQOverP),
effects.varianceQoverP, noiseUpdateMode);

Summary

Effect Formalism Applied to
Multiple scattering Highland (Rossi–Greisen for \(e^\pm\)) \(\sigma^2(\phi)\), \(\sigma^2(\theta)\)
Mean energy loss Bethe (ionization) mean \(q/p\)
Energy-loss straggling Landau width \(\to\) Gaussian \(\sigma\) \(\sigma^2(q/p)\)
Bremsstrahlung mean — (use GSF)
Correlations none (diagonal noise only)

Implementation pointers

The pointwise application and covariance update themselves live in internal (non-public) detail code; the snippets shown in the sections above are extracted directly from it.