Track reconstruction · a working tutorial

Kalman Track Fitting

A particle detector hands you eight noisy dots. A physics analysis needs a momentum with a trustworthy error bar. The Kalman filter is how you get from one to the other — and how you find out whether the error bar is lying to you.

true track hit ±σ predicted filtered smoothed ±1σ
00

The three stages

Reconstruction is not one algorithm. It is three, and the Kalman filter does two of the jobs.

Before any of the mathematics, the shape of the problem. A collision leaves the detector full of unlabelled hits — clusters of charge on silicon, each one telling you where something crossed, and nothing about what. Turning that into a list of measured particles happens in three distinct stages, and it is worth being precise about what each one produces, because in casual use all three get called “tracking”.

  • Seeding. Find small groups of hits — almost always three — consistent with a single track coming from near the beamline. A seed is a hypothesis, together with a first, crude estimate of the track parameters and their covariance. Its only job is to give the next stage somewhere to start.
  • Track finding. Carry that hypothesis outward and decide, layer by layer, which hits belong to it. The output is a set of hits — still not a measurement. This is section 6.
  • Track fitting. Given the hits, extract the best estimate of the track parameters and, more importantly, the covariance the physics will actually consume. This is sections 1–5 and 7.

The figure runs all three on one simulated event in a nine-layer barrel tracker, seen down the beam axis. Every number in it is computed live, from the same equations the rest of the page derives.

Figure 0 · seeding, finding, fitting one event, three stages, r–φ view
stage
seed triplets
seed pT
hits found
fitted pT
resolution gained

Three things in that figure are worth carrying into the rest of the page.

The seed is bad, and that is fine. Three pixel hits over an 11 cm lever arm pin the momentum to a few percent at 1 GeV, and to about 15% at 5 GeV — the stiffer the track, the less a short lever arm can say about its curvature. The seed is not trying to measure anything. It is trying to be right about which hits, and to be wrong about the parameters by a knowable amount, because that amount is what sets the search road.

The road is the seed's error, extrapolated. It opens from ±0.2 mm at the fourth layer to ±24 mm at the ninth, and it pinches back every time a hit is absorbed. That fanning shape is not decoration — it is ±3√S, computed from the covariance, and section 6 is about nothing else.

Fitting is where the measurement happens. Finding hands over a list of hits; fitting turns that list into a number with an error bar, and it is worth roughly a factor of fifty in momentum resolution over the seed that started it. Everything from section 1 to section 7 is about doing that step properly — and about section 8's question, which is whether the error bar it hands back is telling the truth.

One last thing the figure hides. The finding stage and the fitting stage are the same filter. Finding runs it forward with branching, scoring candidate hits as it goes; fitting runs it over the hits that survived, then smooths backward. Once you have section 4's five equations, you have most of both.

The three-stage framing, and the figure it is drawn from, follow the ACTS documentation's Tracking in a nutshell.

01

Two numbers, one answer

Everything below is one formula from your first statistics course, rewritten until it looks like an algorithm.

You measure the same quantity twice, independently: m1 with uncertainty σ1, and m2 with σ2. The maximum-likelihood combination is the inverse-variance weighted mean, and its variance follows:

$$\hat{x}=\frac{m_1/\sigma_1^{2}+m_2/\sigma_2^{2}}{1/\sigma_1^{2}+1/\sigma_2^{2}} \qquad\qquad \frac{1}{\hat\sigma^{2}}=\frac{1}{\sigma_1^{2}}+\frac{1}{\sigma_2^{2}} \tag{1}$$

Variances do not add. Information — inverse variance — adds. That is the whole idea, and the rest is bookkeeping. Now rewrite the same equation as an update to the first measurement rather than a symmetric average of the two:

$$\hat{x}=m_1+K\,(m_2-m_1) \qquad K=\frac{\sigma_1^{2}}{\sigma_1^{2}+\sigma_2^{2}} \qquad \hat\sigma^{2}=(1-K)\,\sigma_1^{2} \tag{2}$$

Algebraically nothing happened. Conceptually everything did. You now have a current best estimate; a new measurement arrives; you move toward it by a fraction K of the disagreement. That fraction is the Kalman gain, and it is nothing but a statement of relative trust:

  • \(K\to 1\) when you knew almost nothing before (\(\sigma_1\gg\sigma_2\)). Take the new measurement at face value.
  • \(K\to 0\) when the new measurement is much worse than what you have. Barely move.
  • \(\hat\sigma^{2}=(1-K)\,\sigma_1^{2}<\sigma_1^{2}\) always. Absorbing a measurement can never make you less certain.

Drag the sliders below until the gain stops being a formula and starts being obvious.

Figure 1 · combining two measurements move the means and the widths
gain K0.00
combined x̂0.00
combined σ̂0.00
χ² of the pair0.00
02

What a track is, numerically

Five numbers on a reference surface — and one of them is deliberately not the momentum.

A charged particle in a magnetic field follows a helix, which takes five parameters (a sixth, time, comes along if your detector measures it). What varies between experiments is only which five, and what surface they are referred to. The state vector x is those parameters; the covariance matrix C is their 5×5 error matrix. A track, to a fitter, is exactly this pair — nothing else.

Where you'll meet itParametersReferred to
ACTS bound parameters loc0, loc1, φ, θ, q/p, t a detector surface
ATLAS perigee d0, z0, φ0, θ, q/p closest approach to the beamline
CMS local, on a plane q/p, dx/dz, dy/dz, x, y a tracker module
This tutorial's toy y, ty, c plane k, bending plane only

Why q/p and never p

Every one of those real parameterisations carries the momentum as q/p, the signed inverse momentum. This is not a convention, it is load-bearing, for three reasons:

  1. It is what the detector measures. Curvature is proportional to q/p, so the measurement model is close to linear in this parameter and badly nonlinear in p.
  2. Its errors are close to Gaussian. A symmetric Gaussian in q/p is a long asymmetric tail in p. The filter assumes Gaussians; give it the variable where that assumption is least of a lie.
  3. Infinite momentum is an ordinary point. A very stiff track has q/p → 0, which is finite and perfectly well behaved. Parameterise in p and every straight track blows up your covariance matrix.

The toy we will actually run

Strip away everything not needed to see the mechanism: work in the bending plane only, with planes perpendicular to z, and approximate the helix by its parabola (valid while the bend angle stays well under a radian). Three parameters remain:

$$\mathbf{x}_k=\begin{pmatrix}y\\[1pt] t_y\\[1pt] c\end{pmatrix} \qquad t_y=\frac{\mathrm{d}y}{\mathrm{d}z} \qquad c=\frac{\mathrm{d}^{2}y}{\mathrm{d}z^{2}}=\frac{1}{R} \tag{3}$$

The third parameter c is the curvature, and it is the momentum measurement in disguise. For a track of transverse momentum pT in a field B,

$$c=\frac{1}{R}=0.29979\;B\,\bigl(q/p_{\mathrm{T}}\bigr)\tag{4}$$

with B in tesla and pT in GeV, giving c in m−1

So fitting c and quoting σc is fitting the momentum and quoting its resolution — the toy is small, but it is not a cartoon.

03

Getting to the next plane

Transport moves the estimate. It also, unavoidably, makes it worse.

Given the state at plane k−1, the equation of motion tells you where the particle should be at plane k. Write that map as fk and its Jacobian as Fk = ∂f/∂x. The prediction is then

$$\class{kf-pred}{\mathbf{x}_{k|k-1}}=f_k\bigl(\class{kf-filt}{\mathbf{x}_{k-1|k-1}}\bigr) \qquad \class{kf-pred}{\mathbf{C}_{k|k-1}} =\mathbf{F}_k\,\class{kf-filt}{\mathbf{C}_{k-1|k-1}}\,\mathbf{F}_k^{\mathsf T}+\mathbf{Q}_k \tag{5}$$

Read a|b as “at plane a, using hits up to b”. In the toy, transport over a gap Δz is exact and linear:

$$\mathbf{F}(\Delta z)=\begin{pmatrix}1&\Delta z&\Delta z^{2}/2\\0&1&\Delta z\\0&0&1\end{pmatrix} \qquad \mathbf{Q}=\begin{pmatrix}0&0&0\\0&\theta_0^{2}&0\\0&0&0\end{pmatrix} \tag{6}$$
Why it is really an extended Kalman filter

In a real experiment fk is a helix in a mapped, non-uniform field crossing curved surfaces. There is no closed form. You integrate the equation of motion numerically — adaptive Runge–Kutta — and propagate the 5×5 Jacobian alongside the trajectory. The filter equations below are unchanged; they just operate on a linearisation about the current estimate. That approximation frays in two familiar places: very long extrapolations (seeding from the outer layers inward) and very low pT, where the track curls and the local linear map stops resembling the real one.

The Q term, and why this is more than least squares

Qk is the process noise — the randomness the material itself adds between one plane and the next. Dominantly that is multiple Coulomb scattering, whose projected angle has RMS given by the Highland formula,

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

plus energy-loss straggling in the q/p element. Notice what Q does structurally: it lets the true state itself change between measurements. A least-squares fit forces one rigid helix through every hit; a Kalman filter fits a trajectory that is allowed to kink at each scatterer by an amount the material budget says is reasonable. That is why it is the natural fitter for a detector made of stuff.

In the toy, a thin scatterer kicks the slope and leaves the position alone — hence the single non-zero element of Q in equation (6). It does not touch the curvature: the field sets that, not the material.

04

The filter step

Section 1, with matrices in it.

A measurement mk arrives with error matrix Vk. The projection matrix Hk says which combination of state parameters the sensor actually sees — one row for a silicon strip, two for a pixel. Then:

$$\begin{align} \mathbf{r}_k &= \class{kf-meas}{\mathbf{m}_k}-\mathbf{H}_k\,\class{kf-pred}{\mathbf{x}_{k|k-1}} && \class{eq-note}{\text{predicted residual}} \tag{8}\\[3pt] \mathbf{S}_k &= \mathbf{H}_k\,\class{kf-pred}{\mathbf{C}_{k|k-1}}\,\mathbf{H}_k^{\mathsf T}+\mathbf{V}_k && \class{eq-note}{\text{its covariance}} \tag{9}\\[3pt] \mathbf{K}_k &= \class{kf-pred}{\mathbf{C}_{k|k-1}}\,\mathbf{H}_k^{\mathsf T}\,\mathbf{S}_k^{-1} && \class{eq-note}{\text{gain}} \tag{10}\\[3pt] \class{kf-filt}{\mathbf{x}_{k|k}} &= \class{kf-pred}{\mathbf{x}_{k|k-1}}+\mathbf{K}_k\,\mathbf{r}_k && \class{eq-note}{\text{update}} \tag{11}\\[3pt] \class{kf-filt}{\mathbf{C}_{k|k}} &= (\mathbf{I}-\mathbf{K}_k\mathbf{H}_k)\,\class{kf-pred}{\mathbf{C}_{k|k-1}} && \tag{12}\\[3pt] \chi^{2}_k &= \mathbf{r}_k^{\mathsf T}\,\mathbf{S}_k^{-1}\,\mathbf{r}_k && \class{eq-note}{\text{this hit's contribution}} \tag{13} \end{align}$$

Set H = 1 and all the matrices to scalars and equations (10)–(12) collapse exactly onto equation (2). The gain is still “my variance divided by the total variance”; the update is still “move by K times the disagreement.”

The line that explains why anyone bothers is (10). The only matrix ever inverted is S, whose size is the dimension of a single measurement — 1×1 or 2×2. There is no large matrix inversion anywhere, and the cost of the whole fit is linear in the number of hits. A global least-squares fit of the same track with scattering handled properly requires inverting an N×N correlation matrix, because scattering at layer 3 correlates the residuals at layers 4 through N.

Three ways to write equation (12), and which to use

FormExpressionWhen
Standard(I − KH) C Cheapest. The subtraction can drive C non-positive-definite after many updates.
Joseph(I−KH) C (I−KH)ᵀ + K V Kᵀ Algebraically identical, manifestly positive semi-definite, a few more flops. Use this one.
InformationC⁻¹ → C⁻¹ + Hᵀ V⁻¹ H Equation (1) for matrices: information adds. Natural when you start from no information at all (C−1 = 0), but it inverts a 5×5.
measurements + K r forward filter x̂₁|₁ x̂₂|₂ x̂₃|₃ x̂₄|₄ x̂₅|₅ F, +Q F, +Q F, +Q F, +Q stored x̂ₖ|ₖ , Cₖ|ₖ seeds it backward smoother x̂₁|₅ x̂₂|₅ x̂₃|₅ x̂₄|₅ x̂₅|₅ AA AA no measurement is read again on the way back — only stored states
The whole algorithm. The forward pass alternates transport (F, +Q) and update (+K r), and must store the predicted and filtered state at every plane. The backward pass reads only those stored states: it propagates information already extracted, and never touches a measurement twice.
05

Running it on a toy tracker

Eight planes over 70 cm, a 2 T field, and a filter you can step through one hit at a time.

Below is the toy of section 2, fitted with the equations of section 4 — the same code, verified line by line against a NumPy reference. Step the filter forward with the arrows and watch three things.

  • The gain starts at exactly 1 and decays. With three parameters and fewer than three hits, the filter has no opinion to defend: it takes each hit at face value.
  • χ² stays at zero until the fourth hit. A three-parameter curve passes exactly through any three points, so the first three residuals are not tests of anything. This is exactly why \(\mathrm{ndf}=N-3\) — and why a real five-parameter track fit has \(\mathrm{ndf}=N-5\).
  • The filtered estimate is always best at the plane you just left. Watch the curvature error in the lower panel: it falls monotonically and only reaches its final value at the last plane. Everywhere else, the forward filter is worse than it needs to be. Fixing that is the entire job of section 7.

You can also drag any hit vertically. Pull one badly off the track and watch what a single outlier does to the fit — and to every residual downstream of it.

Figure 2 · the toy tracker drag any hit · step the filter with ◀ ▶
step
gain Ky
residual r
χ² this hit
χ²/ndf total
fitted pT
Habit worth forming · check against a closed form

You do not have to take a fitter's word for anything. For N equally spaced planes of resolution σ over a lever arm L, Gluckstern's classic result gives the momentum resolution of a sagitta measurement directly:

$$\sigma_c\;\approx\;\frac{\sigma}{L^{2}}\sqrt{\frac{720}{N+4}}$$

At the defaults above — N = 8, σ = 500 µm, L = 70 cm — that predicts \(7.746\,\sigma/L^{2}\) and the smoother returns \(7.561\,\sigma/L^{2}\). The 2.4% gap is not an error in either: the √720/(N+4) form is a large-N approximation, and it converges (0.9949 of the exact value at N = 20, 0.9991 at N = 50). Push the plane slider up and watch the two agree.

06

Finding the hits: the search window

The same matrix S that normalises the residual and sets the gain also draws the box you look inside.

Every hit so far arrived pre-assigned to your track. Nothing in a real event does that. A layer in a busy event carries thousands of clusters and no labels, and the question “which of these belongs to the track I am following?” has to be answered before the filter can absorb anything.

The answer is already in section 4. At layer k you hold a prediction and its covariance, and equation (9) gives Sk — the covariance of the residual before you know which hit will produce it. So any candidate hit m can be scored with the same expression the fit already uses,

$$\chi^{2}(\class{kf-meas}{\mathbf{m}})= \bigl(\class{kf-meas}{\mathbf{m}}-\mathbf{H}\,\class{kf-pred}{\mathbf{x}_{k|k-1}}\bigr)^{\mathsf T} \mathbf{S}_k^{-1} \bigl(\class{kf-meas}{\mathbf{m}}-\mathbf{H}\,\class{kf-pred}{\mathbf{x}_{k|k-1}}\bigr) \;<\;\chi^{2}_{\max}\tag{14}$$

The region of the surface satisfying (14) is the search window — also called the road, or the validation gate. In one dimension it is the interval \(|r| < n\sqrt{S}\) with \(n=\sqrt{\chi^{2}_{\max}}\). Everything else in this section is consequences of that one line.

Two things about S that decide whether your window is right

It is built from S, not from the prediction error. S = HCHT + V carries both the uncertainty in where the track will be and the uncertainty in where the sensor puts the hit. Leaving V out is a tempting shortcut — the window is “about the extrapolation”, after all — and it gets worse precisely as the fit gets better, because HCHT shrinks while V does not:

PlaneCorrect ±3√S Prediction error onlyWindow is
36706 µm6536 µm2.5% too narrow
5355032179.4% too narrow
72777233815.8% too narrow

In more than one dimension it is an ellipse, not a box. On a pixel sensor S is 2×2 and its off-diagonal term is generally not zero — a track crossing a module at a shallow angle has correlated errors in the two local coordinates. A rectangle is still useful, but only as a pre-filter: it is cheap, it can be evaluated against a layer's binned hit index without touching a covariance matrix, and it cuts the list down before the real test runs. It has to circumscribe the ellipse, never crop it.

sensor surface, local coordinates prediction inside the ellipse — accepted, and the filter updates on it inside the box but outside the ellipse — scored, then rejected outside the box — never scored at all the ellipse is set entirely by S = H C H T + V where the track will be where the sensor puts the hit its axes are the eigenvectors of S; its size is √χ²max along each
The gate is an ellipse; the rectangle is only an optimisation. The rectangle drawn here is the tight bounding box of the ellipse, and it is what you evaluate against a binned hit index, because it costs two comparisons per coordinate instead of a matrix product. It admits corner hits that the exact χ2 test then throws out — which is fine, and far cheaper than scoring every hit on the layer. What is not fine is a rectangle that crops the ellipse: those hits are never scored at all, and the loss never appears in any residual you plot.

The window narrows as the fit tightens

Take the three innermost planes as given — that is what a seeding algorithm produces — and search outward from plane 3. The window shrinks at every step, and its composition changes as it does:

Planeσ of the prediction√S ±3√S windowPrediction's share of S
32179 µm2235 µm6.71 mm95.0 %
4139214794.4488.6 %
5107211833.5582.1 %
689410253.0776.2 %
77799262.7870.8 %

At the first search layer the window is 95% your ignorance about the track. By the last it is 71%, and heading toward the floor set by the sensor itself: however good the fit gets, the window can never shrink below V. A tracker whose windows have bottomed out at the hit resolution has extracted everything the geometry has to give.

The strategy, in the order production code runs it

  1. Navigate. Ask the geometry which surfaces the trajectory crosses next, and in what order. This is a geometry problem, not a statistics one, and in a real detector it is most of the running time.
  2. Propagate to the surface, transporting C with F and adding Q — equation (5).
  3. Bound. Form S, take a rectangle that circumscribes the χ2 ellipse, and query the layer's binned hit index for everything inside it.
  4. Gate. Score that shortlist with equation (14) and keep what passes.
  5. Branch. Every surviving hit spawns its own copy of the track state, updated with that hit. Spawn a hole branch as well — the candidate continuing with no hit on this layer — so that a dead module or a genuine inefficiency does not silently kill a real track.
  6. Prune, or drown. Cap the branches per seed, cut on accumulated χ2, cut on the number of holes, and drop candidates that share too many hits with a better one.
  7. Repeat until the layers run out, keep the surviving candidate or candidates, and only then smooth — section 7.

Steps 1–4 and 7 are the fit you already know. Steps 5 and 6 are the whole difference between a fitter and a combinatorial Kalman filter, and they are where the CPU goes.

Choosing χ2max

The gate is a straight trade. The true hit lands inside a symmetric \(n\sqrt{S}\) window with probability \(\mathrm{erf}(n/\sqrt{2})\), while the number of unrelated hits admitted grows linearly with n — and each one is a branch, which is a track candidate, which costs a full propagation at the next layer:

nχ²max Efficiency for the true hit Unrelated hits admitted (1/cm, at plane 5)
1168.27 %0.24
2495.45 %0.47
3999.73 %0.71
41699.9937 %0.95
52599.9999 %1.18

Efficiency saturates; the combinatorics do not. That asymmetry is why n = 3 is such a common default — it buys 99.73% of the true hits, and everything past it costs branches to purchase a fraction of a percent. Note also that this compounds: 99.73% per layer across five search layers is 98.7% of tracks keeping every hit, and a gate at n = 2 would leave you 79.2%.

Why one branch is not enough

That 99.73% is the probability the true hit lies inside the window given a correct prediction. It is not the probability that a search which always follows its single best candidate ends up on the right track, because the two are coupled: take the wrong hit once and the filtered state moves with it, so the next window is centred in the wrong place, so the next pick is likelier to be wrong again. Failures compound. Here is the greedy strategy — always absorb the smallest χ2, never branch — measured over five search layers at n = 3:

Unrelated hits per cmFound the right hit Took the wrong hitFound nothing (hole)
099.56 %0.00 %0.43 %
0.290.77 %4.46 %4.77 %
0.483.23 %9.53 %7.25 %
1.065.09 %22.24 %12.68 %
2.044.01 %38.88 %17.11 %

On a clean layer the greedy search is essentially perfect. Add four tenths of an unrelated hit per centimetre — a quiet event by any collider standard — and one search in six already ends on the wrong hit or on nothing at all. None of that is the window's fault: the window is correct at every step, and the true hit is inside it 99.73% of the time it is given a fair chance. It is the cost of committing to one answer at a layer where the data do not yet distinguish between two. Carrying both and deciding later, once more layers have voted, is exactly what branching buys — and why no production tracker searches greedily.

The figure below runs the greedy search, so you can watch it fail: it follows the best χ2 at every layer and marks the places where a real CKF would have forked instead.

Figure 3 · the search, layer by layer propagate → open the window → gate → update
layer
phase
√S
window ±n√S
hits in window
branches, unpruned

Four things are worth doing to it. Turn the gate down to n = 1 and the true hit starts falling outside its own window: the track loses hits it genuinely produced, and the fit continues on whatever happened to stay inside. Turn the gate up to 6 and nothing is ever missed — but count the branches. Turn the unrelated-hit density up and the window does not change at all, yet the candidate count climbs as a product over layers; that product is what pruning exists to contain. Push the scattering slider and watch √S grow: at 15 mrad the window at layer 5 goes from 1183 to 2189 µm, and a fitter that had left Q out would still be searching the narrow one. Underneath all of it, watch the window narrow layer by layer — the search gets cheaper and sharper the further along the track you already are, which is why seeding well matters so much.

Where Q comes back

Since S = H(FCFT + Q)HT + V, material widens the window as well as the errors. Whether that matters at all is a single comparison — the scattering displacement across the gap, θ0Δz, against √S:

Trackθ0 (3% X0 layer) θ0Δz (Δz = 10 cm) As a fraction of √S
50 GeV0.04 mrad4 µm0.3 %
10 GeV0.20201.7 %
2 GeV1.021028.6 %
1 GeV2.0420417.3 %
0.5 GeV4.0840834.5 %

So in a fine-grained silicon tracker with 10 cm gaps, dropping Q from the window costs a stiff track essentially nothing — 20 µm against a 1183 µm window is not a measurable effect, and you can convince yourself the term is optional. It stops being optional exactly where tracking is hard: soft tracks, thick material, and the long extrapolations across a gap between subdetectors, where θ0Δz can exceed the extrapolation error outright. In figure 3 the window at plane 5 goes 1183 → 1247 → 1729 → 2189 µm as the scattering slider moves 0 → 3 → 10 → 15 mrad — it nearly doubles. A gate tuned on 10 GeV muons and shipped for 1 GeV pions is running windows too narrow in the one regime that needed them wide.

07

The smoother

The forward filter gives its worst answer exactly where physics needs its best one.

The filtered state at plane k has seen hits 1…k. Only the last plane has seen everything. But the parameters an analysis wants — impact parameter, direction and momentum at the innermost layer, where the track meets the vertex — live at the plane the filter knew least about. The forward pass alone is the wrong tool for the job you actually have.

The Rauch–Tung–Striebel smoother fixes this by walking back, carrying the information that arrived later:

$$\begin{align} \mathbf{A}_k &= \class{kf-filt}{\mathbf{C}_{k|k}}\,\mathbf{F}_{k+1}^{\mathsf T}\, \class{kf-pred}{\mathbf{C}_{k+1|k}^{-1}} \tag{15}\\[3pt] \class{kf-smooth}{\mathbf{x}_{k|n}} &= \class{kf-filt}{\mathbf{x}_{k|k}} +\mathbf{A}_k\bigl(\class{kf-smooth}{\mathbf{x}_{k+1|n}} -\class{kf-pred}{\mathbf{x}_{k+1|k}}\bigr) \tag{16}\\[3pt] \class{kf-smooth}{\mathbf{C}_{k|n}} &= \class{kf-filt}{\mathbf{C}_{k|k}} +\mathbf{A}_k\bigl(\class{kf-smooth}{\mathbf{C}_{k+1|n}} -\class{kf-pred}{\mathbf{C}_{k+1|k}}\bigr)\mathbf{A}_k^{\mathsf T} \tag{17} \end{align}$$

Read (16) carefully: the correction is driven by xk+1|nxk+1|k, the amount by which later hits moved the state away from what plane k predicted. If the prediction turned out to be right, the smoother leaves plane k alone. No measurement appears anywhere in (15)–(17) — the backward pass only redistributes information the forward pass already extracted, which is why you must store both xk|k−1, Ck|k−1 and xk|k, Ck|k at every plane on the way out — a real memory cost, and the reason a filter that only needs the final parameters can skip the smoother entirely.

Frühwirth's original paper gives an alternative, the two-filter smoother: run a second, independent filter inward from the last plane, then combine the outward and inward estimates at each plane with the weighted mean of equation (1). It costs more, but it hands you an estimate at plane k that is statistically independent of the hits on one side — convenient when you want a residual that excludes the hit under test.

What smoothing actually buys, in numbers

From the default toy — 8 planes over 70 cm, 500 µm hits, no scattering. Two different stories, one for each kind of parameter:

Planeσy filtered σy smoothed σc filtered σc smoothed
0500.0 µm420.8 µm5.0×10⁻² cm⁻¹7.7×10⁻⁵ cm⁻¹
1500.0264.54.9×10⁻²7.7×10⁻⁵
2500.0240.91.2×10⁻³7.7×10⁻⁵
3487.3264.55.0×10⁻⁴7.7×10⁻⁵
4470.6264.52.7×10⁻⁴7.7×10⁻⁵
5453.2240.91.6×10⁻⁴7.7×10⁻⁵
6436.4264.51.1×10⁻⁴7.7×10⁻⁵
7420.8420.87.7×10⁻⁵7.7×10⁻⁵

Curvature is flat after smoothing. It has to be: c is a property of the whole trajectory, so once every hit has been seen, the answer is the same wherever you evaluate it. The filtered column, by contrast, is a record of the filter slowly learning — and if you read the momentum off the innermost plane of a filter-only fit, you get the seed's error, not the tracker's.

Position is not flat. The smoothed σy is best in the middle (241 µm) and worst at both ends (421 µm), because the interior planes are interpolations between hits and the end planes are extrapolations beyond them. This is the same reason a straight-line fit's error band has a waist. Note also that both end values equal the filtered value at the last plane — the smoothed fit run in either direction must agree at the point where all the information has arrived.

Trap · smoothed residuals are biased

The residual mkHxk|n is too small, because hit k helped determine the very state it is being compared against. Its variance is \(\sigma_{\mathrm{hit}}^{2}-\sigma_{\mathrm{track}}^{2}\), not the sum. In this toy at plane 3 that is 424 µm rather than the 589 µm you would get from an unbiased residual, where the hit is excluded from the fit and the variance is \(\sigma_{\mathrm{hit}}^{2}+\sigma_{\mathrm{track,excl}}^{2}\). Use the biased version to judge the fit; use the unbiased one for alignment, hit-resolution studies, and anything else where the hit under test must not vote on its own residual.

08

Do you believe your errors?

The fit returns x and C. For most analyses, C is the one that decides whether your result is right.

Track parameter uncertainties propagate into everything downstream — invariant mass resolution, impact-parameter significance for b-tagging, vertex χ², isolation. A fit that is 15% optimistic about its momentum error does not announce itself; it quietly biases a whole analysis. So you have to test C directly, and there are exactly two standard instruments.

Pulls

Generate tracks whose true parameters you know, fit them, and histogram

$$\mathrm{pull}_i=\frac{\hat{x}_i-x_i^{\,\mathrm{true}}}{\sqrt{\mathbf{C}_{ii}}}\tag{18}$$

If the model is right this is a unit Gaussian, and the two ways it fails are diagnostic: a shifted mean means the fit is biased; a width above 1 means the errors are underestimated; below 1, overestimated or double-counted.

χ² per degree of freedom

Sum equation (13) over planes. With a diffuse seed, the degrees of freedom are (total measurement dimensions) − (number of track parameters): N−5 for a real 5-parameter fit, N−3 here.

Both instruments are below. The selector deliberately breaks the fit in three different ways — the point is to learn what each failure looks like, because you will meet all of them.

Figure 4 · validating the covariance 4000 toy tracks, refitted live in your browser
pull mean
pull RMS
⟨χ²/ndf⟩
σ(pT)/pT quoted
The result worth remembering

Select “scattering not in Q”. The χ²/ndf lands around 1.02 — you would sign that off without a second thought. The curvature pull width is 1.15: the momentum errors the fit reports are 13% too small, and every significance computed from them is 15% too large.

χ² does not catch a missing material term. Scattering biases the errors far more than it inflates the residuals, and this is the single most common way a track fit lies to you in production. Pulls catch it; χ² does not. Run both.

09

Things that will bite you

A short list, all of it learned the hard way by somebody.

Units, and the condition number

Put q/p in MeV−1 instead of GeV−1 and that element of C changes by 106; mix it with a position in millimetres and the diagonal of your covariance matrix spans a dozen orders of magnitude. Every subsequent inversion loses digits to that spread. Pick one unit system, write it at the top of the file, and never guess.

The seed, from both sides

C0 has to be diffuse enough that the answer does not depend on it — and small enough that equation (17) does not cancel catastrophically. Both walls are real, and the second one is much closer than people expect. Here is this page's toy, with everything held fixed except the curvature element of the seed. The correct answer, from an unconstrained least-squares fit of the same eight hits, is \(\sigma_c=7.7152\times10^{-5}\,\mathrm{cm}^{-1}\):

seed σc smoothed σc reported × too largeWhat you would notice
0.057.7151×10⁻⁵1.00Correct. (the default here)
0.27.7181×10⁻⁵1.00Correct.
1.07.9627×10⁻⁵1.03Nothing.
2.0negative varianceA NaN, several functions downstream.
5.03.4508×10⁻⁴4.47Nothing. Positive-definite again.
10.01.6940×10⁻³21.96Nothing.

Read the last two rows twice. Equation (17) subtracts two nearly equal large matrices; once the seed is wide enough, the digits that survive are noise. At \(\sigma_c=2\) that noise happens to be negative and you get a loud NaN. At 5 and 10 it happens to be positive, the covariance passes every positive-definiteness check you might write, and the fit calmly reports momentum errors twenty-two times too large. Positive-definite is not the same as correct. The only real test is a pull distribution — which is why section 8 exists.

The alternative, standard in production fitters, is to seed properly from the first few hits (a three-point circle fit) and inflate that covariance by a modest factor, so the dynamic range never gets large in the first place.

Keep the covariance a covariance

Symmetrise after every update — C ← (C+CT)/2 costs nothing and stops asymmetry compounding. Use the Joseph form. Assert that the diagonal is positive rather than discovering it via a NaN. When you need real robustness, the square-root and UD-factorised filters propagate a factor of C instead of C, halving the dynamic range that has to fit in a double.

One bad hit ruins everything after it

A wrong hit — a neighbouring track's, a noise cluster — enters the fit at full weight and drags the state. Every prediction downstream is then wrong, so their residuals are wrong too, and the damage propagates forward. The cheap defence is a cut on the per-hit χ2k of equation (13): reject a hit whose predicted residual is implausible before it is absorbed. Try it in figure 2 by dragging a hit far off the track. The principled version is the deterministic annealing filter, which gives every hit a weight between 0 and 1 rather than a binary verdict.

Direction matters, physically

Filtering inside-out and outside-in give the same smoothed answer in exact arithmetic, but they are not interchangeable in practice: energy loss has a sign that depends on which way you are propagating, and the linearisation errors differ. Choose deliberately, and be consistent between fitting and any subsequent extrapolation.

10

Where this goes next

Three directions, all of them the same algebra with something added.

  • Material done properly. Real Q needs a geometry that knows its own radiation lengths, and energy loss as a bias in q/p rather than just a variance. This is most of the engineering in a production fitter.
  • When Gaussian is wrong. Electrons radiate, and Bethe–Heitler energy loss is nothing like a Gaussian. The Gaussian-sum filter carries a weighted mixture of Gaussians instead of one, running a bank of Kalman filters in parallel. The deterministic annealing filter handles contaminated hits the same way, with soft weights annealed over iterations.
  • Vertices. Vertex fitting is the identical machinery with a different H: the state is the vertex position (plus track momenta), each track is a “measurement”, and tracks are added one at a time exactly as hits are here. Frühwirth's 1987 paper introduced both applications together for precisely this reason.

For code to read rather than write: ACTS is the modern, experiment-independent reference implementation, and its track-fitting documentation maps directly onto the vocabulary above — predicted, filtered, smoothed. GenFit is a smaller, more readable toolkit if you want to follow a fit end to end in an afternoon.

References

  1. [1]R. Frühwirth, “Application of Kalman filtering to track and vertex fitting”, Nucl. Instrum. Meth. A 262 (1987) 444. The paper that started it. Short, and still the clearest statement of the method.
  2. [2]A. Strandlie and R. Frühwirth, “Track and vertex reconstruction: from classical to adaptive methods”, Rev. Mod. Phys. 82 (2010) 1419. The review to read next: smoothers, DAF, GSF, and the statistics behind them.
  3. [3]R. Frühwirth and A. Strandlie, Pattern Recognition, Tracking and Vertex Reconstruction in Particle Detectors, Springer (2021). Book-length treatment, and open access.
  4. [4]ACTS track-fitting documentation. A Common Tracking Software. Predicted / filtered / smoothed, GSF and the global χ² fitter, in a production codebase.
  5. [5]H. E. Rauch, F. Tung and C. T. Striebel, “Maximum likelihood estimates of linear dynamic systems”, AIAA J. 3 (1965) 1445. Equations (15)–(17), from the aerospace side of the family.
  6. [6]Particle Data Group, Review of Particle Physics, “Passage of particles through matter”. The Highland formula (7) and the radiation lengths you will need for Q.