One motion model is never enough for a target that changes its mind — run a bank of Kalman filters, mix before you filter, and let Bayes vote.
A jet cruises straight and level at 250 m/s for three minutes. Your constant-velocity Kalman filter tracks it beautifully — 3-meter accuracy, textbook NEES. Then the pilot pulls a 3g turn, and your filter has to choose between two humiliations.
Start with the tool you already own: the constant-velocity filter from the Kalman lesson, tuned the way consistency debugging taught — small Q for the smooth cruise, innovations white, NEES sitting in its band. Everything about this filter is right. Until the pilot banks.
Do the turn geometry concretely. A 3g coordinated turn at 250 m/s means a centripetal acceleration of a = 3 × 9.81 = 29.43 m/s², a turn rate of ω = a/v = 0.1177 rad/s = 6.74°/s, and a circle of radius r = v²/a = 2123.7 m. In ONE second the jet is 14.70 m away from where straight-line physics says it should be. In two seconds, 58.59 m.
Now watch the quiet filter's humiliation. Its steady-state promise (worked below) is σ = 3.08 m. The turn injects 14.7 m of un-modeled displacement per second — a 4.8-sigma model violation every single step. The estimate cuts inside the turn, dragging seconds behind the jet. This is lag, and it is bias, not noise — averaging more measurements does not fix it, because every measurement is being compared against a prediction that is wrong in the same direction.
So brace the filter instead. Inflate Q until it can swallow 14.7 m of surprise per step — Q = 14.7² = 216.1 — and the steady-state sigma balloons to 8.62 m: 2.8× worse than the quiet filter during the 90+% of the flight that is straight. The estimate floats: it chases every measurement blip, because it genuinely believes the jet might jerk sideways at any instant.
And before you propose it: a middle Q (say 30) is not a compromise, it is the worst point on the curve — still multi-sigma biased in the turn AND inflated in cruise. The lag-float tradeoff is a curve, and every single-model filter lives somewhere on it.
The camera follows the jet (gray triangle). Two constant-velocity Kalman filters consume the same noisy measurements (red dots): quiet-tuned (small Q) and maneuver-tuned (big Q). Watch the teal track swing wide outside every turn while its skinny tube excludes the truth; watch the warm track wobble on every straight inside a fat tube it never earns back.
Setup. A jet at v = 250 m/s pulls a 3g coordinated turn. Quantify the model error a constant-velocity filter eats, then compute the steady-state accuracy of a quiet-tuned versus maneuver-tuned scalar random-walk Kalman filter (F = H = 1, R = 100, i.e. σz = 10 m).
Step 1 — turn geometry.
Step 2 — how far off is straight-line physics? The cross-track deviation from the straight-line prediction after T seconds is r(1 − cos ωT): 14.70 m at T = 1 s and 58.59 m at T = 2 s. The small-angle shortcut ½aT² gives 14.71 m and 58.86 m — the maneuver injects ~15 m of pure model error in ONE second.
Step 3 — the quiet filter's promise. The steady-state variance of the scalar random-walk KF solves P = (P+Q)R/(P+Q+R), which rearranges to
Quiet tuning Q = 1 with R = 100 gives P = 9.51, so σ = 3.08 m — the cruise promise.
Step 4 — the turn breaks the promise. During the turn, the quiet filter absorbs 14.7 m/step of un-modeled displacement against a 3.08 m claimed sigma: a 4.8-sigma bias per step. The innovation sequence turns one-sided; NEES (sf-21) fires immediately; the estimate lags the turn.
Step 5 — the braced filter's tax. To not lag, inflate Q to the square of the per-step maneuver displacement: Q = 14.7² = 216.1. Steady state: σ = 8.62 m — a float penalty factor of 8.62/3.08 = 2.8, paid at every quiet step.
Step 6 — the kicker (foreshadowing chapter 7): the jet flies straight about 86% of the time and turns about 14% — so the braced filter pays its 2.8× tax almost all of the time, to be ready for a regime that is almost never active.
So here is the lesson's thesis. The honest model is a hybrid — a continuous state plus a discrete flight mode that jumps. Estimate both: run one filter per hypothesis, weigh them by evidence, and manage the hypothesis explosion. That management problem — solved beautifully by the Interacting Multiple Model filter — is the entire lesson.
The honest model of the jet has two kinds of state: WHERE it is (position, velocity — continuous) and WHAT it is doing (cruising or turning — discrete). The second kind jumps.
Define the object formally. A hybrid system couples a continuous state xk with a discrete mode rk ∈ {1, …, r}. When the dynamics and noises are linear-Gaussian given the mode, it is a jump-Markov linear system (JMLS):
Each mode is a complete Kalman-filter model — its own F, Q, H, R. The mode picks which one is live. Mode 1 might be constant-velocity with tiny Q; mode 2 a coordinated turn, or the same CV structure braced with huge Q.
Now give the mode a dynamics of its own: rk is a Markov chain with transition probability matrix (TPM) Π, where πij = P(rk = j | rk−1 = i). Rows sum to 1. The diagonal πii is persistence — how much the pilot tends to keep doing what they are doing; the off-diagonals are switching rates.
Make the TPM physical. With π11 = 0.98 per half-second step, cruise legs last 1/(1 − 0.98) = 50 steps = 25 seconds on average. Sojourn time — how long the chain stays in a mode before leaving — is geometrically distributed with mean 1/(1 − πii). The TPM is not a nuisance parameter to be tuned; it is a behavioral model of the pilot, and chapter 7 shows how to design it from exactly this sojourn logic.
What does a JMLS generate? Sample the chain and the trajectory is a concatenation of regimes — straight CV segments while r = 1, coordinated-turn arcs while r = 2. This is what real air-traffic and vehicle trajectories actually look like, and it is precisely the structure a single Gaussian Q could not represent in chapter 0.
Top: the mode strip, sampled live from the 2-state Markov chain (teal = cruise, warm = turn). Middle: the trajectory that mode sequence generates. Bottom: observed turn-sojourn lengths piling up against the geometric distribution the TPM predicts. Drag π11 toward 1 and watch legs stretch; drag π22 down and turns get twitchy.
Now pose the estimation problem this lesson answers: given z1:k, infer BOTH the continuous state and the mode — the joint posterior p(xk, rk | z1:k). The mode posterior μj(k) = P(rk = j | z1:k) is itself a deliverable: "the target is maneuvering with probability 0.9" feeds gate widening (sf-12), alerting, and downstream decision logic. No single-model filter can produce it.
And set expectations honestly: with the mode UNKNOWN, exact inference must reason about every possible mode history. The next chapter counts those histories and finds a catastrophe.
Here is the beautiful, useless truth: the exact Bayes filter for a jump-Markov system exists in closed form. It just needs more Kalman filters than there are atoms of time before your track file ends.
Derive the structure in three moves. Move 1 — condition on the history. Fix the entire mode history r1:k = (r1, r2, …, rk). Given the history, every step used a known F, Q, H — the system is plain linear-Gaussian, so p(xk | r1:k, z1:k) is exactly Gaussian, computed by one Kalman filter that followed that history's models step by step.
Move 2 — total probability. Sum over all histories:
The exact posterior is a Gaussian mixture with one component per mode history — each component an ordinary KF output, each weight a history's posterior probability (computable recursively from the TPM and the innovation likelihoods).
Move 3 — count. Each step, every history branches r ways: rk histories at step k. Draw the tree — root at k = 0, r children per node — and watch 2, 4, 8, 16 sprout. At k = 10 with two models: 1024 live Kalman filters. This is a hypothesis tree, and exact inference is running all of it.
Grow the tree one step at a time. Node size = that history's posterior probability. Then switch the strategy: Prune keeps only the best M branches (the rest gray out and die); Merge-by-mode collapses every history ending in the same current mode into one node — exactly r survivors per column, forever.
Setup. Count the exact filter's hypothesis load for r models over k steps, and compare with what the practical algorithms run per step.
Step 1. Each history is a mode string r1…rk; each step multiplies the count by r. Two models, k = 10: 210 = 1024 histories — 1024 parallel Kalman filters already.
Step 2. k = 50 — twenty-five seconds of radar at 2 Hz — is 250 = 1 125 899 906 842 624 = 1.126 × 1015 histories.
Step 3. At one microsecond per hypothesis-update, that is 1.126 × 109 seconds = 35.7 years — for 25 seconds of data. Exactness is not expensive; it is impossible.
Step 4. The explosion is worse in r than in k: three models over just 20 steps give 320 = 3 486 784 401 = 3.49 × 109.
Step 5. Per-step filter counts, exact versus the cut policies this lesson builds:
| Algorithm | Filters per step (r = 2) | Where it cuts the tree |
|---|---|---|
| Exact | 2k and growing | Never — runs the whole tree |
| Static MMAE (ch 3) | 2 | Never branches at all — no switching allowed |
| GPB1 (ch 4) | 2 | Merges everything to ONE Gaussian after each step |
| GPB2 (ch 4) | 4 | Branches one step deep, merges over the past mode |
| IMM (ch 5) | 2 | Merges BEFORE filtering — GPB2 conditioning at GPB1 cost |
The whole game is the bottom row.
And flag the honest cost up front: merging is lossy. A mixture is multimodal; its moment-matched Gaussian is not. Chapter 4 quantifies exactly what merging keeps (the first two moments) and what it forgets (which hypothesis you were in).
The simplest cut of the tree: never branch at all. Run r filters, one per model, and let Bayes score them. It is beautiful, it converges — and convergence is exactly what kills it.
Meet the Multiple Model Adaptive Estimator (MMAE, Magill 1965): r Kalman filters run in parallel forever, each committed to its own model. A model probability μj is updated each step by
Λj is model j's innovation likelihood — how well filter j predicted the measurement — and c normalizes. The output is the μ-weighted mix of the bank.
Why is the innovation likelihood the right evidence? Because filter j's innovation νj should be zero-mean with covariance Sj = HP−HT + R if model j is true — the promise from sf-21. Evaluating the Gaussian density of the actual innovation under that promise IS the Bayes factor for model j. This exact scoring mechanism survives unchanged into the IMM.
Now run it on the switching jet with real numbers (worked below). In quiet flight the quiet model beats the braced model by a likelihood ratio of 1.9336 per step — small, but compounding. After 30 quiet steps from a 50/50 start, μ = [1 − 2.6×10−9, 2.563×10−9]. The maneuver hypothesis is nine orders of magnitude dead.
Then the pilot banks. The maneuver model now wins each step by a ratio of 14.612 — overwhelming evidence — and yet MMAE needs 8 consecutive maneuver steps just to claw back to 50/50. The recovery time is log(prior odds)/log(likelihood ratio): the deeper the quiet spell dug the hole, the longer the wake-up, WITHOUT BOUND. Four seconds of blindness at 2 Hz, during exactly the seconds that matter most.
Name the disease: lock-in. The recursion μ ← μΛ has no mechanism to re-inject probability into a dead hypothesis, because its generative assumption is that the true model never changes. The failure is not the model set; it is the STATIC assumption.
Two-filter MMAE on the worked example's numbers. The log scale is the point: during the quiet phase μmaneuver sinks through 10−3, 10−6, 10−9 — a straight line of slope −log 1.93 per step. When the maneuver starts, count the steps back to 50/50. Then give it a probability floor and watch the recovery collapse to ~2 steps — a preview of what the TPM does for free.
Setup. Two-model MMAE with stationary innovation statistics: model 1 (quiet) has innovation variance S1 = 1, model 2 (braced for maneuvers) has S2 = 4. Quiet flight produces typical innovations ν = 0.3; a maneuver produces ν = 3.0 per step. Start at μ = [0.5, 0.5], run 30 quiet steps, then count the steps of full maneuver evidence needed to return to 50/50.
Step 1 — one quiet step. L1 = N(0.3; 0, 1) = 0.3814 and L2 = N(0.3; 0, 4) = 0.1972 — a likelihood ratio of 1.9336 for the quiet model. Being braced (S2 = 4) spreads model 2's density thin, so it under-explains small innovations. Alertness costs likelihood every quiet step.
Step 2 — compound 30 quiet steps. The log-odds grow by log(1.9336) per step, and μ collapses to [1.000000, 2.563×10−9]. Nothing is wrong yet — this IS the correct Bayesian posterior under the static assumption.
Step 3 — the maneuver begins. ν = 3.0. Now L1 = 0.004432 and L2 = 0.064759 — a 14.612 ratio in favor of the maneuver model. The evidence is overwhelming, every single step.
Step 4 — count the recovery. Starting from odds of ~3.9×108 against, each step divides the odds by 14.612; reaching 50/50 needs log(3.9×108)/log(14.612) = 7.4 → 8 full steps. The script confirms: 8 steps, ending at μ = [0.1580, 0.8420].
Step 5 — generalize.
The collapse depth grows linearly with quiet time and is unbounded — MMAE's detection delay is a function of how long the target was boring.
Two cures exist. The kludge: clamp every μj above an artificial floor ε (it works — try it in the sim — but ε is unprincipled and fights the probabilities). The principled cure: give the mode the Markov dynamics of chapter 1, so that every step the TPM automatically leaks probability into every mode. That leak is the IMM's mixing step, and chapter 7 shows the floor it implies is p12 — the switching rate itself.
If pruning is winner-take-all, merging is a coalition: collapse several hypotheses into one Gaussian that tells the same story to second order. There is exactly one honest way to do it — and one classic bug.
Derive moment matching for a scalar mixture with weights wi, means mi, variances Pi. The merged Gaussian keeps the mixture's first two moments. The mean is the weighted average. The variance, by the law of total variance, is two terms:
The first term is the within-component variance — the average width of the hypotheses. The second is the spread-of-means term — the variance contributed by not knowing WHICH hypothesis is right. Bold that second term in your mind; it carries the rest of the lesson.
Setup. Merge the two-component mixture 0.7 × N(10, 0.5) + 0.3 × N(12, 1.5) into a single Gaussian by moment matching, then verify against a 1,000,000-sample Monte Carlo.
Step 1 — merged mean: 0.7 × 10 + 0.3 × 12 = 10.6.
Step 2 — within-component variance: 0.7 × 0.5 + 0.3 × 1.5 = 0.8 — the average width of the hypotheses.
Step 3 — spread-of-means term: 0.7 × (10 − 10.6)² + 0.3 × (12 − 10.6)² = 0.252 + 0.588 = 0.84 — the disagreement BETWEEN hypotheses.
Step 4 — merged variance: 0.8 + 0.84 = 1.64. Sit with that: the disagreement term EXCEEDS the average component width. Most of this posterior's uncertainty is "which mode", not "where within a mode". Dropping the spread term would claim variance 0.8 — half the truth.
Step 5 — Monte Carlo audit. Sample the component label, then the Gaussian; one million samples give mean 10.6019 and variance 1.6375. Moment matching, verified.
Top: two components (teal, warm), their true mixture (dashed), and the moment-matched Gaussian (purple). The stacked bar decomposes the merged variance into within (solid) and spread (hatched). Toggle "forget the spread term" and watch the purple curve go confidently, dangerously thin. Bottom: where GPB1 and GPB2 place the merge in their cycles.
Now deploy the merge as a tree-cut policy. GPB1 (first-order Generalized Pseudo-Bayesian): each step, branch the single current Gaussian r ways, filter each, then merge ALL r posteriors back to ONE Gaussian. Cost: r filters. Weakness: every filter starts each step from the SAME blended prior — the bank remembers nothing mode-specific. A freshly-activated turn hypothesis inherits a cruise-shaped prior.
GPB2 (second-order): keep r mode-conditioned Gaussians alive. Each step, branch each of them r ways (r² filters — one per transition i → j), filter all, then merge over the PREVIOUS mode i, leaving r posteriors — one per CURRENT mode j. The merge respects the tree: histories that agree on the present are collapsed; the present itself is preserved.
Why is GPB2 visibly better? Because what matters for the next update is conditioning on the CURRENT mode — a turn filter should enter the turn with a turn-shaped prior. GPB2 keeps exactly that conditioning; GPB1 destroys it every step. The price: r² = 4 filters instead of 2 for our two-mode bank, and r² grows brutally with richer banks.
End on the tantalizing question — verbatim the one Blom & Bar-Shalom answered in 1988: GPB2's merge happens AFTER the r² filters run, but each merged output only depends on the merged statistics. Could we merge FIRST, before filtering, and run only r filters on pre-mixed priors? That is the IMM. Next chapter.
Look at GPB2's r² filters again: all the branches that share a CURRENT mode get merged anyway at the end of the step. So why run them separately? Merge their starting conditions first — then one filter per current mode is enough. That single re-ordering is the IMM.
State the reframe cleanly. GPB2 computes r² transition-conditioned posteriors and merges over the past mode i. But the merge only keeps moments — so pre-merge the PRIORS over i (one blend per destination mode j), and run just r filters from those mixed priors. The Interacting Multiple Model estimator (Blom & Bar-Shalom, 1988): the models interact exactly once per cycle, through their starting conditions.
Stage 1 — mixing probabilities. Before seeing zk, apply Bayes to the transition: μi|j = πij μi / cj with normalizer cj = ∑i πij μi (the predicted mode probability). Read μi|j as: given we are NOW in mode j, how likely did we COME FROM mode i — a backward question the TPM answers. And note cj is also the anti-lock-in floor promised in chapter 3: even a dead μj receives roughly πij from the living modes, every step, forever.
Stage 2 — mixed initial conditions. Each destination model j blends the bank's estimates with ITS OWN weights — column j of the mixing matrix: x0j = ∑i μi|j xi, and P0j = ∑i μi|j (Pi + (xi − x0j)(xi − x0j)T) — chapter 4's moment-matched merge, spread term mandatory. The key structural fact: each model gets a DIFFERENT prior. The turn filter enters the step leaning toward whatever a turn transition would inherit. That is where mode-conditioning survives.
Stage 3 — the filter bank. r ordinary Kalman filters (kalman-filter mechanics untouched), each predicting with its own Fj, Qj from its own mixed prior and updating with the shared measurement zk. Nothing exotic happens here — the atoms are stock KFs.
Stages 4 and 5. Each filter reports Λj = N(νj; 0, Sj) — MMAE's scoring rule intact. Then μj = cj Λj / ∑l cl Λl — an HMM forward-algorithm step whose emission densities are Kalman innovation likelihoods. The TPM prior cj and the evidence Λj split the labor exactly as prior and likelihood should.
Stage 6 — output merge, and the most-misread line in the algorithm. x̂ = ∑j μj xj with the spread-term covariance is computed FOR THE CONSUMER ONLY. It is never fed back into the bank. The bank carries its r mode-conditioned pairs (xj, Pj) into the next cycle's mixing. Feed the merged estimate back and you have silently re-invented GPB1 — destroying the conditioning you paid to keep.
Step through the six stages; the active stage lights up and the equation strip below shows the LIVE numbers from chapter 6's worked example flowing through. Toggle the GPB1/GPB2 ghosts to see what the IMM re-ordered, and flip the "wrong feedback" bug to see the classic mistake drawn in red.
Count the ledger against chapter 2's table: r filters per step (2 for our bank), one r × r mixing computation, one output merge. Empirically the IMM sits within a few percent of GPB2's accuracy at 1/r of its filter count — the cost-quality sweet spot that made it the workhorse of air surveillance. The next chapter runs every one of these six stages with real numbers, to six decimals.
Algorithms are believed when their numbers are watched. Scalar system, F = H = 1, two models, one surprising measurement — we run all six stages by hand, to six decimals.
The setup, so every quantity is one number. TPM Π = [[0.95, 0.05], [0.10, 0.90]] (cruise sticky, turns shorter); current mode probabilities μ = [0.8, 0.2]; bank estimates x = [10.0, 10.4] with variances P = [0.5, 1.2]; process noises Q = [0.1, 2.0] (quiet vs braced — a 20× separation); R = 1. The measurement arrives: z = 13 — nearly 3 sigma beyond the quiet model's world view. A maneuver, probably, has begun.
Stage 1 — mixing probabilities.
The TPM has already leaked probability toward the maneuver mode before any evidence (0.2 → 0.22). The mixing matrix μi|j has columns [0.974359, 0.025641] (who feeds the quiet model) and [0.181818, 0.818182] (who feeds the turn model): the turn filter's prior will be 18% built from the quiet filter's estimate.
Stage 2 — mixed initial conditions.
Each model's prior slides toward the other's estimate by its mixing weight. The covariances teach the subtler lesson: model 1's prior inflated (0.5 → 0.522 — it absorbed a disagreeing neighbor, the spread term at work), while model 2's prior tightened (1.2 → 1.097 — blending in model 1's sharper P = 0.5 outweighed the disagreement). Mixing is not inflation; it is honest moment bookkeeping in both directions.
Stage 3 — the bank. Per-model predict (F = 1) gives P− = P0 + Q = [0.621946, 3.096529] — the braced model's Q = 2 swamps its prior. Update with z = 13:
Read the gains: the quiet model trusts z at 38%, the braced model at 76% — same measurement, different worlds.
Stage 4 — likelihoods. Λ = [0.019915, 0.082422]. The braced model explains z = 13 4.14 times better — its innovation is 1.32 sigma under its own S, versus the quiet model's 2.35 sigma. Stage 5 — mode update: μnew ∝ c × Λ = [0.78 × 0.0199, 0.22 × 0.0824], normalized:
One surprising measurement moved belief from 80/20 quiet to 46/54 maneuver — decisive movement, tempered verdict.
Dwell on WHY only 54%. The likelihood ratio 4.14 acts on prior odds c of 0.78/0.22 = 3.55 against, leaving posterior odds 4.14/3.55 = 1.17 — barely past even. Bayes is properly cautious: one 3-sigma measurement is also exactly what a noise fluke looks like. A second surprising z would push μ past 80% (chain the recursion yourself). Detection in an IMM is typically a 2–3 measurement affair — versus MMAE's 8.
Stage 6 — output merge (chapter 4's formula):
Of that 0.9365, the spread term is 0.3524 — 38% of the reported variance is the two hypotheses disagreeing about what just happened. That fraction is the honest signature of a mode transition in progress, and it is exactly what the spread-forgetting bug would silently delete.
| Stage | Quantity | Model 1 (quiet) | Model 2 (braced) |
|---|---|---|---|
| 1 | c = ΠTμ | 0.78 | 0.22 |
| 1 | mixing column μi|j | [0.974359, 0.025641] | [0.181818, 0.818182] |
| 2 | x0 / P0 | 10.010256 / 0.521946 | 10.327273 / 1.096529 |
| 3 | P−, S, K | 0.621946, 1.621946, 0.383457 | 3.096529, 4.096529, 0.755891 |
| 3 | xnew / Pnew | 11.156694 / 0.383457 | 12.347563 / 0.755891 |
| 4 | Λ | 0.019915 | 0.082422 |
| 5 | μnew | 0.461404 | 0.538596 |
| 6 | merged | x̂ = 11.7981, P = 0.9365 (spread term 0.3524 = 38%) | |
The whole cycle, validated bit-for-bit against the hand-worked numbers above, then raced through a 120-step trajectory with a 20-step maneuver burst against both single-model filters. The IMM wins total RMSE (0.684 vs 0.888 quiet and 0.803 braced) while its maneuver-mode probability averages 0.55 inside the burst vs 0.19 outside.
python import numpy as np # Two-model IMM on a scalar state: quiet model (Q=0.1) vs maneuver model (Q=2.0). # F = H = 1 keeps every matrix a number so the whole cycle stays inspectable. PI = np.array([[0.95, 0.05], # PI[i, j] = P(mode j now | mode i before) [0.10, 0.90]]) Q = np.array([0.1, 2.0]); R = 1.0 def imm_step(mu, x, P, z): c = PI.T @ mu # predicted mode probs mix = (PI * mu[:, None]) / c[None, :] # mixing probs mu_{i|j} x0 = mix.T @ x # mixed means P0 = np.array([mix[:, j] @ (P + (x - x0[j])**2) for j in range(2)]) Pp = P0 + Q # per-model predict (F = 1) S = Pp + R # innovation covariances K = Pp / S nu = z - x0 # innovations xn = x0 + K * nu # per-model updates Pn = (1 - K) * Pp lam = np.exp(-nu**2 / (2 * S)) / np.sqrt(2 * np.pi * S) mun = c * lam; mun /= mun.sum() # mode-probability update xm = mun @ xn # merged output Pm = mun @ (Pn + (xn - xm)**2) # note the spread term! return mun, xn, Pn, xm, Pm # --- validate one cycle against the hand-worked chapter-6 numbers --- mu, x, P = np.array([0.8, 0.2]), np.array([10.0, 10.4]), np.array([0.5, 1.2]) mun, xn, Pn, xm, Pm = imm_step(mu, x, P, z=13.0) print('mode probs :', np.round(mun, 6)) # [0.461404 0.538596] print('merged x :', round(float(xm), 4)) # 11.7981 print('merged P :', round(float(Pm), 4)) # 0.9365 assert np.allclose(mun, [0.461404, 0.538596], atol=1e-6) # --- now race it: quiet KF vs maneuver KF vs IMM through a real maneuver --- rng = np.random.default_rng(3) T, xt, vel = 120, 0.0, 0.0 truth, zs, mode = [], [], [] for k in range(T): maneuvering = 40 <= k < 60 # a 20-step burst vel = rng.normal(0, np.sqrt(2.0)) if maneuvering else 0.97*vel xt += vel if maneuvering else rng.normal(0, np.sqrt(0.1)) truth.append(xt); zs.append(xt + rng.normal(0, 1.0)); mode.append(maneuvering) def single_kf(q): xk, Pk, out = 0.0, 10.0, [] for z in zs: Pk += q; S = Pk + R; Kk = Pk/S xk += Kk*(z - xk); Pk *= (1 - Kk); out.append(xk) return np.array(out) mu, x, P = np.array([0.9, 0.1]), np.zeros(2), np.array([10.0, 10.0]) imm_out, mu_hist = [], [] for z in zs: mu, x, P, xm, Pm = imm_step(mu, x, P, z) imm_out.append(xm); mu_hist.append(mu[1]) truth = np.array(truth) for name, est in (('quiet KF ', single_kf(0.1)), ('maneuver KF', single_kf(2.0)), ('IMM ', np.array(imm_out))): err = est - truth print('%s RMSE total %.3f | quiet segs %.3f | maneuver seg %.3f' % ( name, np.sqrt((err**2).mean()), np.sqrt((err[~np.array(mode)]**2).mean()), np.sqrt((err[np.array(mode)]**2).mean()))) print('mean maneuver-mode prob during burst: %.2f, outside: %.2f' % ( np.mean(np.array(mu_hist)[np.array(mode)]), np.mean(np.array(mu_hist)[~np.array(mode)])))
The IMM's equations are fixed; its behavior is designed. Three knobs — which models, how far apart, and a 2×2 matrix of switching probabilities — decide whether your tracker is alert, sluggish, or twitchy.
Start with the TPM, because it has a derivation, not a vibe. Sojourn times are geometric (chapter 1), so E[steps in mode i] = 1/(1 − pii), which inverts to
Interview the domain, not the filter: how long do airliners fly straight legs? About 25 s or more between maneuvers in terminal areas. How long does a turn last? About 4 s. At dt = 0.5 s: p11 = 1 − 1/50 = 0.98, p22 = 1 − 1/8 = 0.875. Two behavioral interviews, four matrix entries.
Check the design against long-run reality with the stationary distribution: for a 2-state chain, μ∞ = (p21, p12)/(p12 + p21) = [0.8621, 0.1379] — the chain predicts the target maneuvers ~14% of the time. If your recorded tracks say 5% or 40%, your sojourns are wrong. This is a free, data-checkable audit of the TPM.
Now cash in chapter 3's promise — the anti-lock-in floor, quantified. Suppose quiet flight has collapsed μ to [0.999, 0.001]. The predicted maneuver probability is c2 = p12 × 0.999 + p22 × 0.001 = 0.020855 — approximately p12 = 0.02, no matter how dead the hypothesis. Mixing re-seeds every mode every step at the switching rate. MMAE's unbounded 8-step hole becomes a 1–2 step recovery, and the floor is a modeling consequence, not an epsilon hack.
Setup. Design a two-mode TPM for an air-surveillance IMM at dt = 0.5 s, given straight legs of about 25 s and maneuvers of about 4 s. Then audit it three ways.
Step 1 — sojourn to diagonal. 25-second legs are 50 steps, so p11 = 1 − 1/50 = 0.98; 4-second maneuvers are 8 steps, so p22 = 1 − 1/8 = 0.875. Check the inversion: expected sojourns 1/(1 − p11) = 50.0 steps and 1/(1 − p22) = 8.0 steps.
Step 2 — stationary distribution. μ∞ = (0.125, 0.02)/0.145 = [0.8621, 0.1379] — the design claims the target maneuvers 13.8% of the time; the leading-eigenvector computation returns the same [0.8621, 0.1379]. Compare against recorded track statistics before flying it.
Step 3 — the floor. With μ = [0.999, 0.001], the predicted maneuver probability is c2 = 0.02 × 0.999 + 0.875 × 0.001 = 0.020855 — approximately p12 = 0.02 regardless of collapse. This is what static MMAE lacked: chapter 3's 2.6×10−9 cannot happen here.
Step 4 — memorylessness audit. P(a maneuver lasts ≥ 8 s) = p2215 = 0.87515 = 0.1349 — the geometric sojourn gives turns a heavy tail and a constant per-step hazard of ending, which real fixed-angle turns do not have. A real 90° turn has a duration set by geometry, not a coin flip. Semi-Markov models with phase-type sojourns fix this at complexity cost; know the limitation, name the fix, usually accept the geometric.
Set the sojourns in SECONDS and the update rate; the TPM, stationary distribution, and anti-lock-in floor derive live. Below, a scripted 60-second scenario (quiet — one 4-s maneuver — quiet) runs through a two-model IMM mode update: watch detection delay and quiet-phase chatter trade off as you drag. Every slider setting drops a dot on the delay-vs-chatter scatter.
Model-set design, rule 1 — separation. Models must be distinguishable through the likelihood. Our bank's Q ratio is 20× (0.1 vs 2.0), which bought a per-step quiet likelihood ratio of ~1.9 and a maneuver ratio of ~14.6 (chapter 3's numbers). Shrink the separation to 2–3× and the likelihoods become near-identical: μ dithers around the stationary distribution, contributing nothing — worse than useless, since you still pay for both filters and the merge blends two near-copies.
Rule 2 — coverage without crowding. More models is not better. Each added model splits likelihood mass; overlapping models compete for the same evidence and all get lukewarm probabilities (probability dilution); and cost grows linearly while merge quality degrades. The classic guidance: 2–3 well-separated models (quiet CV / braced CV or CT) beat a 10-model ladder of Q values in both accuracy and detection speed. If the maneuver spectrum is genuinely wide, that is what VS-IMM is for (next chapter).
Rule 3 — TPM sensitivity, the reassuring one. IMM performance is famously flat in the TPM's fine values — 0.98 vs 0.97 changes little. What matters is the ORDER OF MAGNITUDE of the off-diagonals (the switching rate sets both the lock-in floor and the quiet-time probability chatter), and CONSISTENCY WITH dt: change the update rate and the TPM must be re-derived (pii = 1 − dt/τ), or your 2 Hz design silently becomes a 10×-too-jumpy 20 Hz pilot model. The bench's dt buttons make both effects tangible.
Everything so far assumed the bank is fixed and every measurement belongs to our target. Real trackers get neither luxury — and the IMM slots into both fights without changing its core cycle.
Variable-Structure IMM (VS-IMM, Li & Bar-Shalom): the model set itself becomes state-dependent. A ground target on a road network needs an "on-road along segment 7" model only near segment 7; an aircraft at 150 m/s cannot hold a 7g turn (chapter 0's algebra: ω = a/v — feasible turn rates depend on speed). VS-IMM activates and retires models by rule — total probability redistributed on activation — keeping the LIVE bank small and separated (chapter 7's rules) while the model LIBRARY stays large. The digraph-switching formalism defines which sets are reachable from which.
IMM-PDA. In clutter, each measurement might be a false alarm and the true return might be missing — sf-12's territory, deferred there for the association machinery itself. The composition is clean: replace each model's Kalman update (stage 3) with a PDA update — an association-weighted mixture over "which measurement, if any, is the target's". The subtle point examiners probe: the mode likelihood Λj is no longer a single innovation Gaussian; it must become the PDA likelihood — a clutter-density-weighted sum over association events including the missed-detection term — or the mode probabilities silently mis-calibrate in clutter.
The gate handshake, both directions. Association gates are built from S = HPHT + R, and a single-model quiet filter's overconfident S shrinks gates exactly at maneuver onset — rejecting the maneuvering target's own returns as clutter (the divergence cascade sf-12 and sf-21 both warn about). The IMM's merged S is inflated by the spread term during mode conflict, widening gates automatically when hypotheses disagree. The mode probability μ is also a first-class output here: trackers use μmaneuver to trigger explicit gate inflation and track-quality logic.
IMM smoothing exists and is worth a pointer: mode-conditioned RTS passes (rts-smoothing owns the smoothing machinery) with backward reweighting of mode probabilities. Post-flight, the mode sequence sharpens dramatically, because the future reveals which regime the ambiguous steps belonged to. Offline analysis should never settle for filtered mode probabilities.
The Monte Carlo cousin. Run a particle filter over the MODE SEQUENCE only, with one Kalman filter per particle handling the continuous state exactly — the Rao-Blackwellized particle filter (particle-filter). It is chapter 2's exact tree explored stochastically: unbiased where the IMM is biased-by-merging, expensive where the IMM is cheap, and the honest benchmark when you must audit what the IMM's moment-matching costs. Nonlinear models slot in the same way: replace each bank member with an EKF/UKF (sf-07, sf-08) and the cycle is unchanged — a coordinated-turn model with unknown turn rate is the standard nonlinear member.
And the honest limits of the pattern itself. During transitions the IMM's output is a blend of disagreeing hypotheses — a unimodal summary of a genuinely bimodal belief; downstream consumers who need the multimodality (collision avoidance, intent inference) should consume the bank and μ, not the merge. And the geometric-sojourn and fixed-TPM assumptions mean the IMM models WHEN modes switch far more crudely than WHAT the modes are — the research edge (semi-Markov sojourns, context-dependent TPMs, learned maneuver models) lives exactly there.
| Extension | What changes in the cycle | What it buys |
|---|---|---|
| VS-IMM | Model set activates/retires by rule; probability redistributed on activation | Large model library, small live bank — coverage without dilution |
| IMM-PDA | Stage 3 becomes a PDA update; Λj becomes the clutter-aware PDA likelihood | Survives false alarms and missed detections |
| IMM smoother | Mode-conditioned RTS passes + backward mode reweighting | Post-flight mode sequences sharpen dramatically |
| RBPF | Particles over mode sequences, one KF per particle | Asymptotic exactness on the tree — the merge-bias audit |
| EKF/UKF bank | Bank members become nonlinear filters | Coordinated-turn models with unknown rate, and beyond |
Every claim this lesson made, running at once: a target that turns, three trackers that disagree, and a strip chart that shows the IMM literally changing its mind.
Orient yourself. One target flying legs and coordinated turns, one stream of noisy position measurements, three trackers consuming the identical stream — the quiet CV Kalman filter (teal), the maneuver-tuned CV filter (warm), and a two-model IMM (purple, bold). This is chapter 0's dilemma with the lesson's answer racing both horns of it.
Read the mode strip first, because it is the centerpiece: a stacked area of μCV vs μmaneuver flowing under the arena, with the TRUE mode drawn as a thin band above it. The horizontal offset between a true-mode edge and μ crossing 0.5 IS the detection delay — you watch belief move, 2–5 steps behind reality with the chapter-7 default TPM, exactly the 2–3 measurement commitment chapter 6's odds arithmetic predicted.
Read the RMSE table like an engineer. During cruise, the quiet KF and the IMM sit together well below the floating maneuver KF; during turns, the maneuver KF and the IMM sit together far below the lagging quiet KF. Neither specialist wins both columns. The IMM is within ~10–20% of whichever specialist is currently right, in BOTH columns — echoing the verified scalar race (0.888 / 0.803 / 0.684 total RMSE in chapter 6's code block).
Then make chapter 7 muscle memory. Drag the leg sojourn up (p11 toward 0.999) and the mode strip cleans up but the detection-delay counter climbs; drag it down and detection sharpens while quiet-phase chatter appears as flicker in the strip and small RMSE damage in cruise. The delay-chatter scatter from the design bench is now being generated by a live tracker.
Arena on top (camera follows the target), mode-probability strip beneath it sharing the time axis, live readouts below. The purple ellipse visibly INFLATES for a few steps at each mode transition — that is the spread term buying honest uncertainty — then re-tightens.
Close with the transferable habit: the mode strip is not a debugging visual, it is a product — a calibrated maneuver alarm that downstream logic (gates in sf-12, alerting, intent) consumes. A tracker that only outputs x̂ is discarding half of what the bank computed.
The object layout below — a filter list, a TPM, mode probabilities — is exactly what MATLAB's trackingIMM and Stone Soup ship. A 2D CV-quiet + CV-hot bank flies a leg, a 90° coordinated turn, and a leg; the printed μ(hot) trace reads ~0.07 in cruise, ~0.78 through the turn, and ~0.07 again after exit.
python import numpy as np from filterpy.kalman import KalmanFilter, IMMEstimator # Production framing: a 2D CV + CV-hot bank, the way FilterPy / Stone Soup / # MATLAB trackingIMM ship it. State: [x, vx, y, vy]. dt = 1.0 def make_cv(q): kf = KalmanFilter(dim_x=4, dim_z=2) kf.F = np.array([[1, dt, 0, 0], [0, 1, 0, 0], [0, 0, 1, dt], [0, 0, 0, 1]], float) kf.H = np.array([[1, 0, 0, 0], [0, 0, 1, 0]], float) G = np.array([[dt**2/2, 0], [dt, 0], [0, dt**2/2], [0, dt]]) kf.Q = G @ G.T * q # white-accel process noise kf.R = np.eye(2) * 25.0 # sigma_z = 5 m kf.P *= 100.0 return kf cv_quiet = make_cv(q=0.01) # straight-and-level hypothesis cv_hot = make_cv(q=9.0) # ~3 m/s^2 maneuver hypothesis # TPM from sojourn times (ch 7): legs ~50 steps, maneuvers ~8 steps. M = np.array([[0.98, 0.02], [0.125, 0.875]]) mu = np.array([0.9, 0.1]) # start believing 'quiet' imm = IMMEstimator([cv_quiet, cv_hot], mu, M) # Fly a leg, a 90-degree coordinated turn, then a leg. rng = np.random.default_rng(11) pos, vel = np.array([0.0, 0.0]), np.array([50.0, 0.0]) for k in range(60): if 25 <= k < 40: # the turn: rotate velocity 6 deg/step th = np.radians(6.0) rot = np.array([[np.cos(th), -np.sin(th)], [np.sin(th), np.cos(th)]]) vel = rot @ vel pos = pos + vel*dt z = pos + rng.normal(0, 5.0, 2) imm.predict(); imm.update(z) if k in (10, 27, 33, 50): xf = np.asarray(imm.x).flatten() print('k=%2d mu(hot)=%.2f est=(%7.1f,%7.1f)' % (k, imm.mu[1], xf[0], xf[2])) # Production notes: banks share H and z so likelihoods are comparable; # per-model states live in imm.filters[i].x (post-mixing); use imm.mu as # a maneuver detector for gate widening (sf-12) and log it for post-hoc # NEES audits (sf-21). MATLAB's trackingIMM and Stone Soup's IMM mirror # this exact object layout: filter list + TPM + mode probabilities.
You have run every stage by hand and watched every failure mode live. Now sit the exam: seven questions of the kind that open a target-tracking quals, expecting derivation, limits, and judgment — not recitation.
How to use this chapter: attempt each question on paper (or the Teach whiteboard) BEFORE expanding the worked answer. The answers are graded sketches — full credit requires the algebra plus the sanity checks at the limits, and several answers lean on numbers verified earlier in this lesson.
The questions group into four themes: foundations (the rk explosion, the mixing derivation), algorithm analysis (GPB1 vs GPB2 vs IMM, MMAE lock-in and recovery arithmetic), design (TPM from sojourns, mixed-dimension banks), and interpretation (what the merged output is, and when to refuse to report it).
Condition on the full mode history r1:k. Given the history, every step's F, Q, H are known, so the system is linear-Gaussian and p(xk | r1:k, z1:k) is exactly Gaussian — computed by one Kalman filter that followed that history's models. Law of total probability: p(xk | z1:k) = ∑h P(r1:k | z1:k) N(xk; x̂h, Ph), with the weights available recursively from the TPM and innovation likelihoods. Histories multiply by r per step: rk components — with r = 2, 1024 at k = 10 and 250 = 1.126×1015 at k = 50 (35.7 years at a microsecond per hypothesis, for 25 seconds of data at 2 Hz). Therefore every practical algorithm is a hypothesis-management policy on this tree: prune branches (MMAE keeps a fixed non-branching set; MHT-style keeps the best M) or merge them by moment matching (GPB1 after everything — r filters; GPB2 over the past mode — r² filters; IMM before filtering — r filters).
Mixing probability: μi|j = P(rk−1 = i | rk = j, z1:k−1) = πij μi / cj with cj = ∑i πij μi, by Bayes on the transition before seeing zk — "given we are now in j, where did we come from". Mixed initial conditions are the moment-matched merge of the bank under column j's weights: x0j = ∑i μi|j xi and P0j = ∑i μi|j (Pi + (xi − x0j)(xi − x0j)T). The spread term is forced by the law of total variance: uncertainty about WHICH previous estimate is right is real variance, and dropping it makes the mixed prior overconfident precisely during mode conflict. Both directions, with chapter 6's numbers (μ = [0.8, 0.2], x = [10.0, 10.4], P = [0.5, 1.2], Π = [[0.95, 0.05], [0.10, 0.90]]): c = [0.78, 0.22]; model 1's prior INFLATES 0.5 → 0.521946 (it absorbs a disagreeing neighbor), while model 2's prior TIGHTENS 1.2 → 1.096529 (blending in the sharper P = 0.5 with weight 0.1818 outweighs the disagreement). Mixing is honest moment bookkeeping, not uniform inflation.
GPB1: branch the single Gaussian r ways, filter, merge ALL posteriors to one — r filters, but every model starts each step from the same blended prior, so no mode-conditioned information survives a step. GPB2: keep r current-mode-conditioned Gaussians, branch to r² transition-conditioned filters, then merge over the PREVIOUS mode — r² filters, preserving conditioning on the current mode, which is what actually matters for the next update (a turn filter should enter a turn with a turn-shaped prior). IMM: observe that GPB2's r² branches sharing a current mode j get merged anyway — and since only the merged moments survive, merge their STARTING conditions instead: per-destination mixed priors (column j of the mixing matrix), then r filters. The approximation relative to GPB2 is merging before the update instead of after it — the KF update is linear in its prior mean, so the mean bookkeeping commutes exactly; the difference enters only through the likelihood weighting and covariance nonlinearity, which is why empirically the IMM sits within a few percent of GPB2. Counts for r = 2: exact 2k, GPB1 = 2, GPB2 = 4, IMM = 2. The IMM is the standard because it buys the valuable conditioning at linear cost.
MMAE log-odds evolve additively: each step adds the log of the likelihood ratio. Quiet phase, per-step ratio 1.9336 for the quiet model (L1 = 0.3814 vs L2 = 0.1972 at ν = 0.3 with S1 = 1, S2 = 4): after 30 steps from 50/50, μ2 = 2.563×10−9 — odds ~3.9×108 against. Maneuver evidence gives per-step ratio 14.612 (L2 = 0.064759 vs L1 = 0.004432 at ν = 3.0). Recovery to 50/50 needs k = log(prior odds)/log(ratio) = log(3.9×108)/log(14.612) = 7.4 → 8 steps (script-verified: 8, ending at [0.1580, 0.8420]). The collapse depth grows linearly with quiet time, so the delay is UNBOUNDED. IMM floor: mixing computes c2 = p12μ1 + p22μ2, so with the chapter-7 TPM (p12 = 0.02) even μ = [0.999, 0.001] gives c2 = 0.020855 — the dormant mode restarts each step from ~2%, not 10−9. From odds of ~0.02/0.98 = 49-to-1 against, one 14.6× step reaches ~30%, two reach ~80%: recovery in 1–2 steps regardless of how long the quiet phase lasted. The floor converts an unbounded delay into a constant one, and it is derived from the switching rate, not tuned.
Geometric sojourns give E[steps in mode i] = 1/(1 − pii), so pii = 1 − dt/τi. With dt = 0.5 s, 25 s legs: p11 = 1 − 1/50 = 0.98; 4 s maneuvers: p22 = 1 − 1/8 = 0.875; the off-diagonals complete the rows. Audit 1 — stationary distribution: μ∞ = (p21, p12)/(p12 + p21) = [0.8621, 0.1379]; the design claims ~14% of time maneuvering — check against recorded tracks (the leading eigenvector of ΠT confirms the closed form). Audit 2 — the floor: c2 at collapsed μ = [0.999, 0.001] is 0.020855, approximately p12, bounding detection delay. Audit 3 — memorylessness: P(maneuver ≥ 8 s) = 0.87515 = 0.1349 with constant exit hazard; real fixed-geometry turns are not memoryless — semi-Markov sojourns fix it if it matters. The dt change: pii = 1 − dt/τ is RATE-dependent. Reusing a 2 Hz TPM at 20 Hz implies τ1 = dt/(1 − p11) = 0.05/0.02 = 2.5 s legs instead of 25 s — a pilot who maneuvers ten times as often — inflating quiet-time chatter and degrading cruise accuracy. Every dt change requires re-deriving the TPM; performance is otherwise famously insensitive to modest TPM errors, so get the order of magnitude of the off-diagonals right and move on.
Mixing (stages 1–2) needs all bank estimates in a COMMON state space — you cannot form ∑i μi|j xi across dimensions 4 and 6. Standard fixes: (a) state augmentation — pad the CV state with zero acceleration and assign the padded entries a designed variance (zero variance claims certainty the CV model does not have about acceleration and suppresses the CA model's warm start; too large poisons the CA prior — a real design choice), or (b) explicit linear maps Tij carrying model i's estimate into model j's space (the general unbiased-mixing formulation). Likelihood comparability is a separate issue and is about the MEASUREMENT, not the state: Λj = N(νj; 0, Sj) are densities over the same zk whenever every model uses the same measurement (same z, same physical H semantics), so they are directly comparable even across state dimensions. They become incomparable if models consume different measurement sets or gate differently (IMM-PDA's clutter-aware likelihood restores comparability by scoring the whole measurement event). Examiners also expect: after mixing into the larger space, the spread term must be computed IN that space — disagreement about the padded acceleration is genuine variance.
The merged output x̂ = ∑j μj xj is the mean of the IMM's mixture posterior — the MMSE point estimate under the algorithm's approximation — with covariance including the spread term (chapter 6: 11.7981 with P = 0.9365, of which 0.3524 = 38% is spread). It is NEVER fed back: the bank propagates the r mode-conditioned pairs through per-destination mixing; feeding back the merge collapses the mixing columns to one blend and silently rebuilds GPB1. When reporting it is wrong: during a mode transition the true posterior is BIMODAL — chapter 6's components sit at 11.157 and 12.348 with weights 46/54, and the merged mean 11.798 lies between them, where NEITHER hypothesis puts the target. For collision avoidance, weapon-danger zones, or intent inference, a unimodal summary of a bimodal belief is actively dangerous: the consumer should receive the full mixture (or at least the mode-conditioned estimates and μ) and reason about both futures. The merged Gaussian is a reporting convenience for consumers that genuinely want a single second-order summary — a display track, an RMSE benchmark — not a substitute for the posterior.
Finally, three debate prompts — oral-exam finishers. There is no single right answer, but there are defensible and indefensible positions. Argue with compute, consistency, and detection-delay arithmetic, not with vibes.
Strip the tracking away and this lesson taught one design pattern: when the world switches regimes, run one believer per regime and make Bayes the referee. Here is where that pattern lives across the site, and beyond.
The unifying frame, in one paragraph: the exact answer is a hypothesis tree (chapter 2); every practical algorithm is a cut policy; and the IMM's specific genius is WHERE it cuts — merging into per-destination priors so that conditioning on the present survives at linear cost. Prune vs merge, and merge placement, are the transferable vocabulary.
| Method | Filters/step | Handles switching? | Detection delay | Mode posterior? | Canonical habitat |
|---|---|---|---|---|---|
| Single tuned KF | 1 | No — lag or float | — | No | One-regime systems |
| Adaptive KF | 1 | Drifts yes, jumps poorly | Window-length lag | No | Slowly drifting noise |
| Static MMAE | r | No — locks in | Unbounded | Yes (static) | Constant unknown model, fault ID |
| GPB1 | r | Yes | Slower than IMM | Yes | Historical stepping stone |
| GPB2 | r² | Yes | ~IMM | Yes | Accuracy reference |
| IMM | r | Yes | 2–5 steps | Yes | Air surveillance, automotive tracking |
| RBPF | N particles | Yes (asymptotically exact) | ~IMM | Yes (sampled) | Merge-bias audits, rich mode structure |
The sibling bridges, explicitly. adaptive-kalman is the SINGLE-hypothesis answer to the same disease — adapt Q online rather than hypothesize in parallel; it wins when regimes drift slowly, loses when they JUMP (adaptation lag is MMAE-like), and its model-uncertainty chapter hands off to this lesson. hmm owns the discrete-chain machinery the mode update reuses — the IMM's stage 5 is literally a forward-algorithm step with Kalman innovation likelihoods as emissions. particle-filter owns the RBPF escape hatch when merging bias or nonlinearity bites.
Deployed systems, concretely. FAA and NATO air-surveillance trackers run IMM-PDA / IMM-JPDA cores (the sf-12 handshake); automotive perception stacks run CV/CT/CA IMM banks per tracked vehicle (sf-18's fusion context); ballistic-versus-maneuvering discrimination and reentry tracking are classic IMM territory; and maneuvering MULTI-target trackers embed IMM banks inside the PHD/LMB machinery of multi-target-rfs — one bank per hypothesized target.
What this lesson deliberately did NOT cover, with pointers: association depth and clutter models (sf-12); the NEES/NIS consistency machinery used as instrumentation throughout (sf-21); smoothing mechanics for mode-conditioned RTS (rts-smoothing); filtering banks over attitude modes on manifolds (lie-group-filtering); and the semi-Markov / learned-TPM research edge named in chapter 8.
An estimator that cannot change its mind is a liability. The IMM is the disciplined way to keep several minds at once, pay only linearly for them, and report honestly — including the spread of their disagreement — while Bayes decides which mind fits the moment.