The Kalman filter is only optimal if you hand it the right Q and R — this lesson is about what happens when you don't, how the filter can tell, and how it can fix itself.
Your Kalman filter is mathematically perfect. The derivation is airtight, the code is bug-free, the matrices are the right shapes. And it is about to sail serenely past the target while reporting its best confidence ever — because someone typed Q two orders of magnitude too small.
Here is how it happens. The tracker worked beautifully in the lab. Before the demo, an engineer "cleaned up" the tuning: the process noise looked generous, so they cut Q by a factor of 100 to smooth the output. The demo got smoother — noticeably, satisfyingly smoother. Everyone approved. Then the target turned, and the filter didn't.
The mechanism is one number (chapter 1 derives it). For a random-walk state with measurement variance r = 1, cutting q from 0.01 to 0.0001 shrinks the steady-state gain from K = 0.095 to K = 0.00995. Each measurement now moves the estimate by one percent of the innovation. Since the filter's response time is roughly 1/K steps, it went from a 10-step memory to a 100-step memory. It is not broken. It is asleep.
What makes this failure dangerous is its asymmetry: it is not a noisy-estimate failure you would notice. Q too small produces the smoothest, most confident output the filter will ever give you — right up until the model is wrong. A maneuver, a gust, a slope in a sensor's drift — anything the model didn't predict — and the sleeping gain cannot wake up. The claimed σ says centimeters; the actual error is meters and compounding.
Truth (teal) is a slow random walk; red dots are measurements. Two filters run on the same data: honest q and mistuned q ÷ slider. Before the maneuver the mistuned filter looks BETTER — smoother, tighter tube. Trigger the maneuver and watch which one wakes up. The corner readout turns red when actual error exceeds 3× the claimed σ.
This lesson exists as the bridge from sf-21-debugging-consistency.html. That lesson taught you the instruments — NIS, NEES, whiteness — for detecting a lying filter offline, on the bench, with ground truth available. But Q and R are not constants of nature: sensors degrade, vibration changes with speed, maneuvers come and go. A filter tuned honest on Tuesday is a liar by Friday. The fix must run inside the filter, online, with no ground truth.
Here is the arsenal this lesson builds. First, learn to read the innovation sequence — the one signal the filter can audit about itself (chapter 2). Second, close the loop: estimate R and Q from innovations (chapters 3–4). Third, forget the past on purpose, so the filter can never fall this deeply asleep (chapter 5). Fourth, when the mismatch is structural rather than a scale, run competing filters and let Bayes referee (chapter 6). And fifth — because an adaptive filter is a numerical stress test by construction — make the covariance arithmetic itself trustworthy (chapters 7–8).
Honest scope: this lesson OWNS online adaptation and Kalman numerics. The theory of the consistency tests lives in sf-21 (we use NIS bands here as instruments, not derive them), and full multi-model switching logic lives in imm-estimation.html — chapter 6 walks you to its doorstep.
Small Q means the filter trusts its model, so the gain is tiny and the output is a heavily-averaged, beautiful, smooth curve with a tiny claimed σ. On benign data this LOOKS like a better filter — which is exactly why the mistuning ships. The lie only becomes error when the model is violated, and by then the gain is too small to recover.
Before we can adapt Q and R, we need to feel what they DO. Not their definitions — kalman-filter.html gave you those — but their fingerprints: what exactly goes wrong, in which direction, when each one lies.
Reframe both matrices as confessions rather than parameters. Q says: "between two ticks, the world does things my model F did not predict — this much of them." It is the error bar on your physics. R says: "when the sensor speaks, this is the variance of its lie." It is the error bar on your evidence. The filter's entire behavior — every gain it ever computes — is the argument between these two confessions.
Let's make the argument quantitative with the simplest possible system: a random-walk state (F = 1) with Var(w) = q and Var(v) = r. The predicted variance obeys Ppred = P + q with the posterior P = Ppred r / (Ppred + r). At steady state these two lines close into a quadratic, and the quadratic has a closed-form solution:
Every claim in this chapter is this one formula, evaluated. Read its regimes: for q much smaller than r, Ppred ≈ √(qr), so K ≈ √(q/r). The gain depends on the RATIO of the two confessions — and only as a square root. Cut q by 100 and K falls by 10, not 100. Hold that thought: it has a sharp consequence for adaptation later, because steady-state behavior cannot identify q and r separately — only their ratio. Chapter 4 pays this bill.
Take r = 1 and the two q values from chapter 0's disaster.
q = 0.01: Ppred = (0.01 + √(0.0001 + 0.04))/2 = (0.01 + 0.200250)/2 = 0.105125. Gain K = 0.105125/1.105125 = 0.095125. Response time 1/K = 10.5 steps.
q = 0.0001 (the chapter-0 mistuning): Ppred = (0.0001 + √(1e-8 + 4e-4))/2 = 0.010050, K = 0.010050/1.010050 = 0.009950. Response time 1/K = 100.5 steps.
The ratio of gains is 0.095125/0.009950 = 9.56 — cutting q by 100 cut K by roughly √100 = 10, confirming K ≈ √(q/r) in the small-q regime (√0.01 = 0.1 and √0.0001 = 0.01, both within 5% of the exact gains). Sanity-check the closed form by brute force: start P = 10 and iterate the two-line recursion 10,000 times with q = 0.0001; the iterated Ppred lands on 0.010050 — the same number, so the quadratic solution is the true fixed point.
Each mistuning direction has a distinct, diagnosable symptom — this table is the diagnostic heart of the chapter:
| Mistuning | Behavior | Symptom you see |
|---|---|---|
| Q too small | Gain collapses, memory lengthens | Sluggish, smooth, overconfident — lags maneuvers, diverges gracefully (chapter 0's disease) |
| Q too big | Gain saturates high | Twitchy estimate that shadows the raw measurements — honest but wasteful; you paid for a filter and got a wire |
| R too small | Filter treats noise as signal | Chases jitter, plus OVERconfidence — claimed σ far below actual scatter |
| R too big | Measurements barely register | Free-runs on the model — smooth, lagging, and (if the model drifts) biased |
The diagnostic: Q errors and R errors both change smoothness, but their INNOVATION signatures differ — which is exactly what chapter 2 learns to read.
Left: a live tracker running your assumed (q, r) against fixed true values (q = 0.01, r = 1). Right: drag the dot on the log-log map to set the filter's assumptions; the cross marks truth. Drag ALONG an iso-gain diagonal (constant q/r): the estimate trace is unchanged while the claimed σ tube inflates and deflates — the identifiability lesson made physical.
One more confession, about the confessions. Engineers commit a deliberate crime with Q: they use it as a tuning knob rather than a physical quantity. Real systems inflate Q to cover unmodeled dynamics — linearization error in the EKF, unmodeled accelerations in constant-velocity trackers — a lie told in a safe direction. The steady-state formula is the evidence for why the direction is safe: overestimating q costs you variance like a square root (gentle), underestimating costs you divergence (catastrophic). When forced to guess, guess fat.
Close with the identifiability foreshadow. Since K depends only on q/r in steady state, two engineers — one with the true (Q, R), one with both doubled — ship IDENTICAL estimate sequences. But NOT identical covariances: the computed P and the promised innovation variance S = Ppred + r both scale up with the common factor. The estimates cannot tell the difference; the innovation STATISTICS can. That asymmetry is the crack that chapters 2–3 wedge open.
K depends on q and r only through their ratio (and only as a square root). Scaling both by the same constant c leaves K — and therefore every estimate — unchanged, while scaling P and S by c. That is the identifiability constraint every adaptive method must respect: innovations expose the ratio easily, the absolute scale only through S.
The filter cannot see its own error — that would require the truth. But there is one quantity it predicts BEFORE nature answers: the next measurement. The gap between prediction and answer is the innovation, and it is the only mirror the filter will ever get.
Define the object: νk = zk − H x̂pred is the part of the measurement the filter could NOT predict — the genuinely new information, hence the name. Everything predictable was already in x̂pred; what remains should be pure surprise. You met innovations in sf-21-debugging-consistency.html as a way to TEST a filter on the bench. Here is the pivot this lesson needs: the innovation becomes the sensor that DRIVES adaptation, in flight.
A correctly-modeled filter makes a precise, three-part promise about its innovation stream:
Zero mean: no systematic surprise. Covariance exactly S: the filter knows HOW surprised it should be. And whiteness: innovations at different times are uncorrelated, because an optimal filter extracts every predictable pattern, leaving residue with no structure. The derivations live in sf-21; what we need here is what each violation MEANS.
Whiteness is the deep one, and the intuition is worth a PhD: if innovations are correlated in time, there is a pattern in the surprises — and a pattern in the surprises is information the filter failed to extract. A too-small gain leaves tracking error that persists across steps, so consecutive innovations share the same sign: positive autocorrelation. This is Mehra's classic 1970 observation, and it is directional evidence: fat-but-white innovations indict the noise levels; colored innovations indict the gain — the q/r ratio, or the model itself.
Quantify chapter 0's disaster. Truth: random walk with qtrue = 0.25, measured with rtrue = 1. The filter assumes q = 0.0025 — 100x too small.
Step 1 — the promise. The filter's internal steady state under its assumed q: Ppred = (0.0025 + √(0.0025² + 4×0.0025))/2 = 0.0513. So it PROMISES S = Ppred + r = 1.0513 and runs gain K = 0.0488.
Step 2 — the reality. For a fixed suboptimal gain the actual error dynamics are epost = (1−K)epred + Kv with epred picking up a fresh w each step, which closes into a steady state Pactual = (K²rtrue + qtrue)/(1 − (1−K)²) = 2.6523.
Step 3 — the broken promise. The ACTUAL innovation variance is C = Pactual + rtrue = 3.6523. The filter promised 1.0513 and delivers 3.6523 — a broken promise by the factor C/S = 3.47. That ratio is exactly what a running NIS average would read.
Step 4 — Monte Carlo confirms. A 200,000-step seeded run measures innovation variance 3.634, and lag-1 autocorrelation 0.674 — the surprises are massively patterned, because the sleepy gain leaves persistent tracking error that leaks into consecutive innovations.
Step 5 — the control. Same data, filter assuming the TRUE q = 0.25: promised S = 1.6404, measured 1.648, lag-1 autocorrelation −0.002. Right scale, white residue. The mirror works.
Top: the live innovation stream with the PROMISED ±2√S band (teal). Bottom: sample autocorrelations ρ1..ρ10 over the trailing 300 innovations, with the ±2/√N whiteness band (dashed). Use the presets to learn each mistuning's signature: band spill = scale violation, tall bars = color violation.
The promise hands you a free gift on the way out: the innovation gate. Since ν should be N(0, S), a measurement with ν²/S > 9 (a 3-σ event) is a once-in-370 occurrence; reject it as an outlier before it touches the state. This is the cheapest robustness in all of estimation — and it is only as good as S is honest. An overconfident filter (small S) starts gating TRUE measurements, which is how the confident-lie failure cascades. Depth on outlier theory — influence functions, Huber losses, when hard gates fail — belongs to robust-estimation.html; the gate itself belongs here, because S does.
Whiteness is the sharper test: correlated surprises mean a pattern was left on the table — persistent tracking error leaking across steps, the fingerprint of a wrong gain or wrong dynamics. Scale can accidentally match while structure betrays the filter; that is why adaptation schemes read the autocorrelation, not just the variance ratio.
Chapter 2 built the diagnosis: measured surprise versus promised surprise. The adaptive move is almost embarrassingly direct — if the promise does not match the measurement, change the promise until it does.
Set up the estimator. Over a sliding window of the last W innovations, form the sample covariance Ĉ = (1/W) Σ ννT. This is the measured side. The promised side is S = H Ppred HT + R. The innovation identity says that IF the model were right, these would agree in expectation. So read the identity backwards, as an equation FOR R:
Everything on the right is observable. Nothing needs truth. This is innovation-based adaptive estimation (IAE), the correlation-family member of Mehra's classic taxonomy of adaptive filters — Bayesian, maximum-likelihood, correlation-matching, covariance-matching (chapter 4 takes the last).
Now the subtlety that separates this from a homework exercise: the Ppred in the formula is the FILTER'S internal covariance, computed under its current wrong R. So the first solve gives an IMPROVED R, not the true one. The equation is implicit: changing R changes the gain, which changes Ppred, which changes what R̂ solves to. The honest algorithm is a fixed-point iteration — and the fixed point is exactly the truth, because when assumed R equals true R the filter is consistent and C = H Ppred HT + R holds with equality.
Scalar random walk with q = 0.09 (known and correct) and true measurement variance r = 4.0. The filter starts believing r = 1.0. At each iteration: compute the filter's internal steady-state Ppred and gain under its assumed r, compute the ACTUAL innovation variance C that gain produces (closed form, exactly as in chapter 2), then update via R̂ = C − Ppred.
Iteration 1: assumed r = 1.0 gives internal Ppred = 0.3484, gain K = 0.2584. Actual innovation variance: C = Pactual + rtrue where Pactual = (K²rtrue + q)/(1−(1−K)²), giving C = 4.7934. Update: R̂ = 4.7934 − 0.3484 = 4.4450. An overshoot — the overconfident filter underestimates its own Ppred, so too much of C gets attributed to R.
Iteration 2: assumed r = 4.4450 gives Ppred = 0.6791 and C = 4.6475, so R̂ = 3.9684. Now slightly under — the loop is contracting.
Iterations 3–5: R̂ = 4.0024, then 3.9998, then 4.0000. Geometric convergence onto the truth.
Fixed-point check: at assumed r = 4.0 exactly, Ppred = 0.6467, the filter is consistent so C = Ppred + 4.0 = 4.6467, and R̂ = C − Ppred = 4.0 — the update maps the truth to itself.
Window size W is the knob. W = 30–100 is typical. Smaller tracks sensor changes faster but injects estimation noise into the gain: the variance of a sample variance of Gaussian data is 2σ⁴/W, so R̂'s relative jitter is roughly √(2/W) — about 20% at W = 50, and that jitter feeds straight into K. Larger W is calmer but slower to notice a degrading sensor. You are spending one statistical budget two ways.
Three strips, one time axis. (1) Innovations with the promised ±2√S band — at start the filter believes r = 1 (true 4): band far too tight, dots spilling. (2) R̂ (warm) versus the true r (teal dashed) — press Degrade sensor to double the true r and watch R̂ re-converge after a W-length lag. (3) Running mean NIS: starts near 3.5, collapses toward 1 as R̂ locks on. Step iteration performs one frozen-time fixed-point solve per press — the worked table, animated. Freeze shows the frozen filter's NIS staying broken, for contrast.
What about Q? Same door, harder passage. The innovation identity constrains H Ppred HT + R as a SUM — with R trusted, surplus innovation variance can be attributed to Q: raise q until S matches Ĉ, using Ppred's dependence on q from chapter 1's Riccati closed form. But H projects the state: innovations only see Q through the measured subspace, so process noise on unobserved states is weakly identified, slowly, through the dynamics. Practical systems therefore adapt a scalar inflation on a fixed Q shape rather than a full matrix — estimate the level, trust the structure.
And the boundary every adaptive filter must fear: adapt R or Q from the same innovation stream and you are fine; adapt both freely and you have one equation (the S level) with two unknowns — chapter 1's ratio identifiability, now with teeth. The standard discipline: fix whichever side you can characterize offline (R from sensor bench data, usually), adapt the other online, and let whiteness — not just scale — arbitrate when both are suspect. Colored innovations indict the Q side; white-but-fat indicts the R side. Chapter 4 shows what happens when this discipline is skipped.
The whole chapter in 40 lines. A mistuned filter (assumed R four times too small) versus the same filter with sliding-window adaptation, on the same seeded data. The frozen filter's mean NIS reads ~3.65 — chapter 2's broken-promise ratio; the adaptive one heals to ~1.03 with R̂ ≈ 3.85 against a true 4.0. No ground truth used anywhere.
python import numpy as np # Scalar random-walk tracker: q known and correct, R badly wrong. rng = np.random.default_rng(11) q_true, r_true = 0.09, 4.0 N = 4000 xs = np.cumsum(rng.normal(0, np.sqrt(q_true), N)) # truth zs = xs + rng.normal(0, np.sqrt(r_true), N) # measurements def run(adapt, W=50, r_assumed=1.0): """One filter pass. adapt=True closes the IAE loop: R_hat = mean(nu^2 over window) - P_pred (innovation identity, read backwards), floored to stay positive.""" xh, Pf, Rh = 0.0, 1.0, r_assumed window, nis = [], [] for k in range(N): Pp = Pf + q_true # predict (F = 1) S = Pp + Rh # the promise nu = zs[k] - xh # the surprise nis.append(nu*nu/S) # running self-audit (sf-21) window.append(nu*nu) if len(window) > W: window.pop(0) if adapt and len(window) == W: Chat = np.mean(window) # measured surprise level Rh = max(1e-3, Chat - Pp) # solve the promise for R K = Pp/(Pp + Rh) # gain under current belief xh = xh + K*nu Pf = (1-K)*Pp # (short form is fine here: return float(np.mean(nis[500:])), Rh # scalar, exact-arithmetic K) nis_frozen, _ = run(adapt=False) nis_adapt, R_final = run(adapt=True) print('frozen (R assumed 1, true 4): mean NIS =', round(nis_frozen, 2)) print('adaptive (window W=50): mean NIS =', round(nis_adapt, 2)) print('final R_hat =', round(R_final, 2), ' (true 4.0)') # frozen mean NIS ~3.65 (the broken promise, cf. chapter 2's ratio); # adaptive ~1.03 with R_hat ~3.85: consistency restored from innovations # alone -- no ground truth was used anywhere above.
Only the internal Ppred is available without truth. Since it is computed under the current wrong R, one application improves but does not correct the assumption (4.445 from a true 4.0 in the worked numbers). Repeated application contracts to the unique fixed point — assumed R equal to true R — because exactly there the filter is consistent and the identity closes.
A sliding window stores W innovations and recomputes a covariance every tick. An engineer with a microcontroller asks the obvious question: can I get the same adaptation in one line, no buffer? Sage and Husa answered in 1969 — with a formula and, hidden inside it, a trap.
Derive the recursive form from the window. A sample mean over a growing window obeys mk = mk−1 + (1/k)(new − mk−1). Replace 1/k with a forgetting schedule dk = (1−b)/(1−bk+1) built from a forgetting factor b (typically 0.95–0.999): early on, dk is near 1/k (use all data); asymptotically it settles at 1−b (an exponential window of effective length 1/(1−b)). Apply this averaging to chapter 3's IAE quantity and you get the Sage-Husa measurement-noise estimator:
The hand step. Forgetting factor b = 0.95, so the asymptotic weight is d = 0.05. Previous R̂ = 2.5, innovation ν = 3.1, H Ppred HT = 0.6. Instantaneous evidence: ν² − 0.6 = 9.61 − 0.6 = 9.01 — a one-sample estimate of R, huge because this particular innovation was large. Blend with the memory: R̂ = 0.95 × 2.5 + 0.05 × 9.01 = 2.375 + 0.4505 = 2.8255. The estimate moves 5% of the way toward the instantaneous evidence — that 5% IS the forgetting factor's asymptotic weight d = 1−b. One multiply-accumulate per tick; the buffer is gone.
The seeded run. The chapter-3 system (q = 0.09 known, true r = 4.0, filter starting at R̂ = 1.0), b = 0.995, 5000 steps, seed 7, R̂ floored at 10−6. The early schedule dk ≈ 1/k learns fast from nothing: R̂ climbs to 3.54 by step 500 and 3.99 by step 2000 — converged within noise. But it does not STAY there: over steps 3000–5000 the estimate wanders with mean 3.78, because an exponential window of effective length 1/(1−b) = 200 has irreducible relative jitter of roughly √(2/200) ≈ 10%. The wander is not a bug to fix but a budget to spend: shrink it only by growing the memory, which slows real-change tracking by the same factor.
Be honest about the estimator's texture: each instantaneous term ν² − H Ppred HT is a ONE-SAMPLE variance estimate — brutally noisy, and frequently negative (whenever a small innovation arrives, ν² < H Ppred HT). The exponential average smooths it, but two engineering guards are mandatory: floor R̂ at a small positive value (a negative or zero R breaks the gain arithmetic and the gate), and treat persistent flooring as an alarm, not a state.
Main strip: R̂ (warm) against the true r (teal dashed) — press Degrade to step r from 4 to 8 (rain on the radar). The gray dots are the raw evidence ν² − Ppred: violently noisy, many negative — see what the exponential average tames. Low b: leaps in ~10 steps but rattles. High b: glides, arrives ~1000 steps late. Then flip Also adapt Q: the inset plots the (Q̂, R̂) trajectory sliding ALONG the identifiability ridge away from truth while the innovations stay plausible.
The forgetting factor b controls the same tradeoff as chapter 3's W, because it is the same object: b → 1 gives long memory — low estimator variance, slow response to a genuinely degrading sensor; small b gives fast tracking that jitters. The wander scales like √(2(1−b)) relative — at b = 0.995, about 10% — and that jitter feeds straight into the gain. You are always choosing where to spend the same statistical budget: confidence in the noise estimate versus freshness of it.
Now the trap the title promised. Sage and Husa also published the dual Q estimator — Q̂ from the state-correction statistics (KννTKT plus the covariance-decrease terms). Run BOTH estimators simultaneously and the scheme can diverge, and quals examiners love asking why: chapter 1 proved the steady-state innovation statistics constrain roughly the SUM H Ppred(q) HT + R — one observable level, two unknowns. The joint estimator has a ridge of near-fixed-points: raise Q̂, lower R̂ compatibly, and the innovations look statistically identical. Estimation noise performs a random walk along the ridge until the gain is nonsense. The whiteness statistic breaks the tie in principle — Q-mistuning colors innovations, pure R-scale mistuning does not — but the classic recursions never use it.
Know the map beyond the classics. Offline, autocovariance least squares (ALS, Odelson-Rawlings 2006) solves for Q and R jointly from logged innovation autocorrelations at MULTIPLE lags — the extra lags restore the identifiability that the single-lag level lacks. Online, variational Bayes adaptive filters put an inverse-Wishart posterior on R and update it jointly with the state (Sarkka-Nummenmaa 2009) — principled uncertainty on the noise itself. Sage-Husa survives in practice because it costs nothing; know what it approximates, and where its blind spot is.
One scalar promise (the S level) cannot pin down two free parameters. The likelihood surface has a ridge of near-equivalent (Q, R) pairs; noise moves the estimates along it while every scale diagnostic stays green. Identifiability returns only with more observables — innovation autocorrelations at multiple lags (ALS) or whiteness-based tie-breaking — or by freezing one side.
Every divergence story in this lesson ends the same way: the filter becomes too sure of a past that no longer describes the present. The bluntest cure is also the most elegant: make the filter constitutionally incapable of remembering too much.
The mechanism is one line. In the predict step, replace Ppred = F P FT + Q with
with a fading factor α (Fagin 1964, Sorenson-Sacks 1971). Inflating covariance is the filter's language for humility: each tick, the filter is forced to concede a fixed fraction of its accumulated certainty.
"Inflate P" sounds like a hack until you see what it means in data terms. Unroll the recursion: a measurement j steps old enters the current estimate with weight proportional to α−2j. The fading filter is EXACTLY exponentially-weighted least squares. The effective data window is Neff = α²/(α² − 1): α = 1.01 remembers about 51 samples, α = 1.05 about 11. One knob, direct control over memory length.
Take the WORST case for responsiveness: q = 0. The model claims the state never changes, so the standard filter's gain decays to zero and it falls asleep permanently. With fading (measurement variance r):
Steady state: Ppred = α²(1 − K) Ppred with K = Ppred/(Ppred + r). Substituting K and solving: Ppred + r = α² r, so Ppred = (α² − 1) r — the predicted variance refuses to fall below a floor set purely by α.
The gain floor follows: K∞ = Ppred/(Ppred + r) = (α² − 1)/α². For α = 1.05: α² = 1.1025, K∞ = 0.1025/1.1025 = 0.092971. For α = 1.01: K∞ = 0.019704.
Effective memory: Neff = α²/(α² − 1): α = 1.05 gives 10.76 samples; α = 1.01 gives 50.75. The two views agree: K∞ ≈ 1/Neff — a gain floor IS a memory ceiling.
Verification by iteration: starting P = 5 with α = 1.05, q = 0, the faded Riccati recursion settles at K = 0.092971 and Ppred = 0.1025 = (α² − 1) r exactly — the index-card theorem survives contact with the recursion.
The insurance premium: the un-faded filter on a truly constant state would drive variance to 0; the faded one pays a permanent steady-state Ppred of 0.1025. That is the fixed cost of never being fully asleep.
Left: truth is constant, then steps (press Trigger step). Three q = 0 filters race: the standard KF (gain draining to zero — the sleeping filter made pure), the fading filter at the slider's α, and a ghost at a comparison α. The gain bars under the pane show the standard filter's bar draining while the fading filters rest on their floors. Right: the theorem as a picture — K∞(α) and Neff(α) with your slider position marked.
Place the tool against chapters 3–4. Adaptive estimation MEASURES the mismatch and corrects the model — accurate, but with a lag, an estimator variance, and failure modes (ridges, gated windows). Fading memory measures nothing. It is open-loop humility: always on, zero parameters estimated, working against ANY slow mismatch — drifting biases, aging sensors, unmodeled dynamics — including ones your adaptive estimator was not designed to see. The price is permanent: steady-state variance is inflated even when the model happens to be right. An insurance premium, paid every tick.
You will meet this idea wearing other costumes. Fading with factor α² is the same medicine as adding pseudo-noise Qextra proportional to F P FT (state-proportional inflation), and the same idea as the forgetting factor in recursive least squares (λ = α−2 — EE269's adaptive filters are this lesson's cousins). In the EKF it earns its keep twice over: linearization error grows with P, and fading keeps the filter responsive enough to re-linearize before the error compounds — many production EKFs run α = 1.01–1.05 as a matter of policy (see ekf.html).
The honest tuning doctrine: choose α from the timescale of trust — if you believe your model for roughly N steps, set α² = N/(N−1). Watch the failure directions: α too large discards information you paid for (the estimate jitters at the gain floor), and fading is NOT a substitute for gross mistuning — it bounds the sleep depth but cannot fix a wrong H or a biased sensor. It composes cleanly with chapter 3: adapt R with IAE, fade against everything else — belt and suspenders, and the showcase runs exactly that stack.
The inflation fights the contraction to a standoff: Ppred settles at (α² − 1) r, giving K = (α² − 1)/α² > 0 forever. This is the theorem that makes fading an insurance policy — no model claim, however confident, can drive the gain below the floor, so the filter can always respond to a reality its model denied.
Chapters 3–5 assumed the model family was right and only the noise levels lied. But sometimes the disagreement is structural: is the target cruising or maneuvering? Is the sensor healthy or degraded? You cannot slide continuously between hypotheses. So stop sliding — run them all.
Reframe adaptation as hypothesis testing. Postulate a finite set of models M1..MN — here, the same F and H with different Q hypotheses (q, 10q, 100q), though the same machinery covers different R, F, or H. Each model gets its own complete Kalman filter running on the same measurements. This is the multiple-model adaptive estimator (MMAE, Magill 1965) — the Bayesian answer to "which confession is true?"
The referee takes two lines to derive. The filter matched to the true model produces innovations that are N(0, Si) — that is chapter 2's promise. So the likelihood of the observed innovation under model i is:
Note carefully what is being scored: not smallness of the innovation, but AGREEMENT WITH THE PROMISE. A model that predicted big surprises and got them outscores a model that promised small surprises and got big ones. The exponent penalizes surprise relative to the promise; the 1/√(2πS) normalization taxes over-wide promises. The referee scores calibration — the same virtue NIS tests.
A two-filter bank with equal priors p = (0.5, 0.5). The quiet filter promises innovation variance S1 = 1.05; the maneuver filter promises S2 = 4.0. A maneuver begins, and innovations ν = 2.5, −2.2, 2.8 arrive.
First innovation ν = 2.5: L1 = exp(−6.25/2.1)/√(2π × 1.05) = 0.01985; L2 = exp(−6.25/8)/√(2π × 4) = 0.09132. The quiet model called a 2.4-σ event; the maneuver model called a comfortable 1.25-σ event. Bayes: unnormalized (0.5 × 0.01985, 0.5 × 0.09132), normalized: p = (0.1786, 0.8214). One measurement moved the bank from agnostic to 82% maneuver.
Second innovation ν = −2.2: the likelihood ratio again favors the wide promise; p = (0.0719, 0.9281).
Third innovation ν = 2.8: p = (0.0095, 0.9905). Three consistent surprises produce 99% confidence — multiplicative Bayes compounds evidence exponentially fast. No threshold was tuned, no detector designed; the promise-versus-evidence ratio did all the work.
Read the design lesson in both directions: the same compounding that detects the maneuver in 3 ticks would, after 1000 quiet ticks, leave p(maneuver) around 10−40 if unfloored. Detection speed and saturation depth are the same mechanism — which is exactly what IMM's Markov mixing repairs.
The bank has two outputs, and you should know when to use which. The MMSE estimate is the probability-weighted blend Σ pi x̂i — smooth, hedges during ambiguity. The MAP choice takes the highest-probability filter's estimate — commits, better when models are genuinely exclusive. And the blended covariance must include the spread-of-means term, Σ pi(Pi + (x̂i − x̂)(x̂i − x̂)T), because disagreement between filters IS uncertainty — dropping that term reproduces this lesson's original sin: overconfidence.
Top: truth alternates cruise segments and maneuver bursts (tinted bands = maneuver truth); a bank with q hypotheses {q, 10q, 100q} tracks it — faint individual estimates, bold blend. Middle: the model-probability bars animate as likelihoods arrive. During cruise the small-q bar saturates; when a maneuver starts, count the ticks before the big-q bar overtakes. Bottom: the log-odds pit deepening during cruise and the climb-out delay after each switch. The floor visibly shortens the climb-out; the cruise-length slider makes pit-depth-versus-lag experimentally discoverable.
Now the structural flaw that makes this chapter a bridge rather than a destination. STATIC MMAE assumes one model is true for all time. The probability update is multiplicative, so log-odds accumulate; after a long cruise, p(maneuver) decays exponentially toward zero — and when the target finally does turn, the bank must climb out of a log-probability pit that deepened for the entire cruise. The referee saturates. Band-aids exist — floor the probabilities, exponentially discount old likelihoods (chapter 5's forgetting trick applied to log-odds) — but they treat the symptom.
Name the disease precisely: "which model?" is not a static unknown. It is a STATE that switches — cruise, then turn, then cruise — with its own dynamics. The principled fix gives model probability its own transition model: a Markov chain over modes, mixing the bank's estimates before each cycle so no hypothesis ever starves. That is the Interacting Multiple Model (IMM) filter, and it is the entire subject of the sibling lesson imm-estimation.html: this chapter hands you the bank and the referee; IMM adds the mode dynamics, the mixing step, and the full worked cycle.
Static MMAE treats the model as fixed forever, so log-odds accumulate linearly with every quiet tick — the wrong-model probability decays exponentially without bound. The first maneuver innovations must reverse an arbitrarily deep deficit. Flooring probabilities caps the pit; modeling the mode as a Markov chain that can SWITCH (IMM) removes it in principle.
An adaptive filter changes its gain constantly, from noisy estimates, in finite-precision arithmetic. Time to confess something about the covariance update you have been using since kalman-filter.html: it silently assumes the gain is EXACTLY right — and yours never is.
Derive the general truth first. If you update with ANY gain K, the posterior error is epost = (I − KH)epred − Kv, with epred and v independent. Its true covariance is therefore:
No optimality invoked anywhere: this formula is just bookkeeping for whatever estimator you actually built. Substitute the OPTIMAL K and the cross terms collapse algebraically to the short form P = (I − KH)Ppred. The short form is a special case that happens to be cheaper — and everyone shipped it.
Now expose the fragility with a derivative. Perturb K to K + δ. The Joseph form, being the true variance, changes only at second order — dP/dK vanishes at the optimum, because the optimal gain minimizes posterior variance; that is what optimal means. The short form is LINEAR in K: its error is first-order in δ, and worse, in the wrong direction — increase K past optimal and (I − KH)Ppred DECREASES, claiming improvement while the actual variance grows. The short form does not report your estimator's variance; it reports what the variance would have been had your K been perfect.
Scalar update, exact arithmetic: Ppred = 1, R = 1, so S = 2 and the optimal gain is K = 0.5 with true posterior variance 0.5.
At K = 0.5 both forms agree: short (1−0.5)×1 = 0.5; Joseph (1−0.5)²×1 + 0.5²×1 = 0.25 + 0.25 = 0.5. At the exact optimum the forms are algebraically identical.
At K = 0.6 (a 20% gain error — one bad adaptive step): the short form claims (1−0.6)×1 = 0.40 — an improvement over the optimal 0.5, which is impossible: no gain can beat the optimum. The short form is not measuring the estimator; it is extrapolating a formula outside its validity. Joseph: (0.4)²×1 + (0.6)²×1 = 0.16 + 0.36 = 0.52 — which IS the true variance of the K = 0.6 estimator, since Var[(1−K)epred + Kv] = (1−K)²Ppred + K²R. A 2-million-sample Monte Carlo agrees: 0.5196. Note 0.52 = 0.5 + 2×(0.1)² — second-order in the gain error, the optimality of K = 0.5 made visible.
At K = 1.2 (an adaptive overshoot): the short form gives (1−1.2)×1 = −0.20 — a negative variance, which downstream becomes a negative S, an imaginary gate threshold, and a sign-flipped gain. The filter is not degraded; it is insane. Joseph: (−0.2)² + (1.2)² = 0.04 + 1.44 = 1.48 — a terrible estimator, honestly reported.
Floating point makes "wrong gain" not exotic but guaranteed: even the optimal K is stored rounded, so the short form is ALWAYS evaluated off-optimum. Its first-order error accumulates over thousands of updates, and because (I − KH)P is a product of non-symmetric factors, P drifts asymmetric (P01 ≠ P10) and eventually indefinite. In float32 — still the currency of embedded autopilots and GPU-resident filter banks — a strong measurement (small R) can push the naive update's smallest eigenvalue negative within a handful of steps. Joseph's quadratic structure plus an explicit symmetrization P ← (P + PT)/2 degrades gracefully under the same conditions. Cost accounting: Joseph is roughly 2x the flops of the short form — the cheapest robustness you will buy this lesson.
Single precision emulated live with Math.fround. A 2-state filter absorbs alternating near-collinear strong measurements (H rows [1,1] and [1,1+ε], R = ε²) — the classic ill-conditioned pair. Naive short form versus Joseph + symmetrization, identical inputs. The plot is each P's smallest eigenvalue per update on a signed-log axis — at aggressive ε the red trace dives below zero within a few updates. Flip to float64: both healthy. The failure is arithmetic, not algebra.
While we are at the update step, take the second numerics upgrade: sequential processing. When R is diagonal (independent sensor channels), a vector measurement need not be absorbed in one matrix-inverse gulp. Process each scalar component through its own scalar update — Si = Hi P HiT + ri, one division each, no matrix inversion at all. The posterior is exactly the same: conditioning on independent evidence commutes. Three wins: no inverse to go ill-conditioned; per-channel innovation gates (chapter 2's gate now isolates WHICH channel is lying — one bad GPS pseudorange gets rejected without discarding the epoch); and per-channel adaptive-R hooks for chapter 3's machinery.
A vector KF update written as short form, Joseph, and sequential scalar channels — agreeing at the optimum, then deliberately fed an overshooting adaptive gain. Watch the assertions: the short form's covariance goes indefinite (a negative eigenvalue); Joseph reports the honest, larger covariance of the gain actually used.
python import numpy as np rng = np.random.default_rng(3) n = 2 P = np.array([[1.0, 0.3], [0.3, 0.5]]) # predicted covariance H = np.array([[1.0, 0.0], [0.0, 1.0]]) # two independent channels R = np.diag([1.0, 2.25]) # diagonal -> sequential OK def joseph(P, K, H, R): IKH = np.eye(n) - K @ H Pn = IKH @ P @ IKH.T + K @ R @ K.T return 0.5*(Pn + Pn.T) # explicit symmetrization # --- optimal gain: all forms agree --- S = H @ P @ H.T + R K = P @ H.T @ np.linalg.inv(S) P_short = (np.eye(n) - K @ H) @ P P_jos = joseph(P, K, H, R) assert np.allclose(P_short, P_jos), 'forms agree at the optimum' # --- sequential scalar processing: same posterior, no inversion --- Ps = P.copy() for i in range(2): # one channel at a time h = H[i:i+1, :] Si = float(h @ Ps @ h.T) + R[i, i] # scalar S: one division Ki = (Ps @ h.T) / Si IKHi = np.eye(n) - Ki @ h Ps = IKHi @ Ps @ IKHi.T + (Ki * R[i, i]) @ Ki.T # Joseph per channel Ps = 0.5*(Ps + Ps.T) assert np.allclose(Ps, P_jos, atol=1e-12), 'sequential == batch' # --- perturbed gain (one bad adaptive transient): forms diverge --- Kbad = K * 2.2 # gain overshoot, 2.2x optimal P_short_bad = (np.eye(n) - Kbad @ H) @ P P_jos_bad = joseph(P, Kbad, H, R) print('short-form eigenvalues :', np.round(np.linalg.eigvalsh(P_short_bad), 4)) print('joseph eigenvalues :', np.round(np.linalg.eigvalsh(P_jos_bad), 4)) assert np.linalg.eigvalsh(P_short_bad).min() < 0 # short form: indefinite! assert np.linalg.eigvalsh(P_jos_bad).min() > 0 # joseph: honest and PD # prints short-form eigenvalues [-0.138, 0.2524]: the short form under an # overshooting gain reports a covariance with a NEGATIVE eigenvalue -- a # filter that believes an impossible thing. Joseph reports the honest # (larger) covariance of the gain actually used, eigenvalues # [0.3841, 1.393]: that honesty is what an adaptive filter, whose gain is # always slightly wrong, is buying for its ~2x flops.
Set the boundary honestly, to hand off to chapter 8: Joseph plus symmetrization plus sequential updates fixes the UPDATE STEP for realistic conditioning, and it is the right default for every filter in this lesson. But when the condition number of P itself approaches the reciprocal of machine precision — strong measurements, near-perfect correlations from an adaptive transient, float32 budgets — no arrangement of P-arithmetic survives, because P can no longer even REPRESENT its own small eigenvalues. Then you stop computing with P, and start computing with its square root.
The short form is linear in K and decreasing through the optimum, so an overshoot yields a claimed variance better than the best achievable — the 0.40-versus-0.5 impossibility from the worked numbers. Joseph, being the actual variance of the applied gain, shows the second-order penalty: 0.52. For a filter whose gain is perpetually perturbed by adaptation noise, Joseph is not a luxury but the only honest bookkeeping.
Chapter 7 patched the update formula. But there is a deeper problem no formula fixes: at extreme conditioning, the matrix P cannot faithfully HOLD its own small eigenvalues in finite precision. The 1969 Apollo-era answer remains the state of the art: never store P at all.
Diagnose the representation problem, not the algorithm. P's eigenvalues span its condition number κ(P); float32 carries about 7.2 decimal digits; and the entries of P mix the large and small eigendirections, so the small eigenvalue lives in the trailing digits of every entry. Quantify with this chapter's running example: P = [[1, 0.999],[0.999, 1]] has eigenvalues 1.999 and 0.001 — κ = 1999, so log10(κ) = 3.3 of your 7.2 digits are spent before any algorithm runs. Perturb a single entry by 0.1% (0.999 → 1.000) and the small eigenvalue is annihilated: the matrix goes exactly singular. The weak direction was never safely stored.
The cure is a factorization with a theorem attached. Store S with P = S ST — any square root; Cholesky's lower-triangular factor is the canonical choice. Since P's eigenvalues are the squares of S's singular values:
The log-condition halves. For the running example: 44.71 versus 1999. In digit terms: 1.65 digits spent instead of 3.3. The rule of thumb this buys is famous and worth stating exactly: square-root filtering achieves the same effective precision as covariance filtering with TWICE the word length — float32 square-root roughly matches float64 naive.
(a) Conditioning. Eigenvalues of P: 1.999 and 0.001, so κ(P) = 1999.0. Cholesky: S = [[1, 0],[0.999, 0.04471]] — note the weak direction gets its OWN entry, 0.04471 = √(1 − 0.999²). κ(S) = 44.71 = √1999: the doubling theorem, numerically. Now perturb P's 0.999 to 1.000 — a 0.1% change to one entry: the eigenvalues become (0, 2). The small eigenvalue is 100% destroyed; P is singular. The same-sized relative perturbation applied to S's off-diagonal entry moves the reconstructed small eigenvalue by 0.1% — three orders of magnitude less damage from the same-sized wound. Digit accounting: at κ(P) = 107, P in float32 has NO digits left for the weak direction while S retains ~3.7 — which predicts exactly what (b) shows.
(b) Precision — the Grewal-Andrews stress test. From P0 = I, absorb two near-collinear strong measurements (H = [1,1] then [1, 1+ε], R = ε², ε = 2.5×10−4). Naive float32 covariance update: the smallest eigenvalue of P comes out exactly 0.000e+00 — float32 could not represent the surviving sliver of uncertainty, so the filter now claims PERFECT knowledge along one direction, and the next measurement in that direction divides by a vanishing S. The exact float64 answer is 1.562e-08. A Potter square-root filter in the SAME float32: smallest eigenvalue 2.668e-08 — not exact (the problem sits at float32's edge) but positive and the right order of magnitude. Same arithmetic budget, qualitatively different integrity.
Left: the covariance ellipse of P(ρ) = [[1, ρ],[ρ, 1]] degenerating into a needle as ρ → 1, with both eigenvalues as log-scale bars. Right: the precision budget — each bar is float32's 7.2 decimal digits; P spends log10κ(P) of them (red), S spends half. Press Perturb 0.1% against P: the ellipse snaps to a degenerate line (DESTROYED). The same perturbation against S barely moves it.
Quals expect the names and the deltas of the classical family. Potter (1963, flew on Apollo): measurement update directly on S for scalar measurements, one square root per update. Carlson: triangular-factor variant. Bierman UD (1977): factor P = U D UT with U unit-upper-triangular and D diagonal; the measurement update is a rank-one modification needing NO square roots at all — pure multiply-add-divide, the reason it owned 1970s–80s flight computers and still owns radiation-hardened and fixed-point targets. Thornton: the companion time update, propagating U and D through F and Q via modified weighted Gram-Schmidt. UD is algebraically a square-root method (S = U √D) with the numerical benefits and integer-friendly arithmetic.
The modern framing connects to least squares. Today's preferred formulation is the array (QR) form: stack the propagated factor and noise factors into a tall block matrix, orthogonally triangularize, read off the updated factor. Numerically this is solving the underlying least-squares problem the way numerical analysts insist all least squares be solved — by orthogonal transforms on the square root of the information, never by forming the normal equations. P-arithmetic IS the normal equations, squared condition number and all. This is the same principle that makes factor-graph smoothers do QR on square-root information matrices — the thread runs from Apollo to modern SLAM unchanged.
python import numpy as np def ud_factorize(P): """P = U D U' with U unit upper-triangular, D diagonal (one-time).""" n = P.shape[0] U, D, P = np.eye(n), np.zeros(n), P.copy() for j in range(n-1, -1, -1): D[j] = P[j, j] for i in range(j): U[i, j] = P[i, j] / D[j] for k in range(i+1): P[k, i] -= U[k, j] * D[j] * U[i, j] return U, D def bierman_update(x, U, D, h, R, z): """Scalar-measurement UD update (Bierman 1977). No square roots: multiply/add/divide only -- fixed-point and certification friendly. D stays positive BY CONSTRUCTION: indefiniteness is unrepresentable.""" f = U.T @ h # f = U'h v = D * f # v_i = D_i f_i alpha = R # innovation variance accumulator b = np.zeros(len(x)) for j in range(len(x)): alpha_prev = alpha alpha += f[j]*v[j] D[j] *= alpha_prev/alpha p = -f[j]/alpha_prev bj = v[j] for i in range(j): Uij = U[i, j] U[i, j] = Uij + b[i]*p b[i] += Uij*bj b[j] = bj K = b/alpha # gain, free byproduct return x + K*(z - h @ x), U, D # verify against the textbook update rng = np.random.default_rng(3) A = rng.normal(size=(3, 3)); P = A @ A.T + 3*np.eye(3) x = rng.normal(size=3); h = rng.normal(size=3); R, z = 0.7, 1.9 U, D = ud_factorize(P) assert np.allclose(U @ np.diag(D) @ U.T, P) x2, U2, D2 = bierman_update(x.copy(), U.copy(), D.copy(), h, R, z) S = h @ P @ h + R; K = P @ h / S x_ref = x + K*(z - h @ x) P_ref = P - np.outer(K, h @ P) assert np.allclose(x2, x_ref) assert np.allclose(U2 @ np.diag(D2) @ U2.T, P_ref) assert D2.min() > 0 print('UD update == reference KF: PASS min(D) =', round(D2.min(), 4)) # Production notes: pair with Thornton's MWGS time update to propagate # U, D through F and Q; process vector measurements as sequential scalar # channels (decorrelate R first if needed). Modern alternative: the # array/QR square-root form -- orthogonal triangularization of stacked # factors -- the same numerics that power square-root information # smoothers in factor-graph SLAM.
Close with the engineering decision table and the adaptive tie-in. Modern float64 plus Joseph plus symmetrization is adequate for most well-conditioned filters — square-root costs roughly 1.5–2x and is the wrong complexity to buy reflexively. Buy it when: float32 or fixed-point hardware (embedded, GPU-resident filter banks); condition numbers pushed by strong-sensor/weak-sensor mixes or by adaptive transients (an adaptive filter slamming R̂ down 4x in one window is a conditioning event BY DESIGN); long-mission covariance integrity requirements; or certification demands provable PSD. And note the adaptive synergy runs both ways: chapter 3's machinery keeps S honest statistically; this chapter keeps it honest arithmetically — the showcase runs the safe update forms underneath the adaptive loop.
The damage ill-conditioning does is measured by log10(κ) — digits spent spanning the eigenvalue range. Since the factor's singular values are the square roots of P's eigenvalues, its log-condition is exactly half: the running example spends 1.65 digits instead of 3.3, and the float32 stress test shows the practical consequence — naive P collapses to exact singularity while the factor form retains the weak direction.
You have the instruments (chapter 2), the estimators (chapters 3–4), the insurance (chapter 5), the referee (chapter 6), and trustworthy arithmetic (chapters 7–8). Time to fly the whole stack — and to break it on purpose, from the cockpit.
The cockpit: a 2D arena where a target flies constant-velocity legs punctuated by coordinated turns — the classic maneuvering profile. A position sensor reports every tick with true measurement variance rtrue. The filter is a 4-state CV Kalman tracker — exactly kalman-filter.html's chapter-8 machine — but its ASSUMED q and r are set by two mischief sliders (0.01x to 100x truth). The adaptive stack sits behind one master toggle: innovation-window R estimation (chapter 3), a fading factor on the predict (chapter 5), Joseph-form sequential updates underneath (chapter 7).
One honest detail before you fly. The 1.0x baseline q is not the raw random-acceleration variance of the truth — it is chapter 1's "deliberate crime" performed correctly: q inflated about 8x so the coordinated turns live inside the process-noise budget. That is the tuning a deployed CV tracker actually ships with, and it is why 1.0x/1.0x reads consistent: YES on a maneuvering target — with only brief instrument blips as each turn passes through the windows. Everything the mischief sliders do is measured against that covering tuning.
Arena: truth, measurements, estimate with its 95% covariance ellipse — the single most important element on screen; it turns red whenever it fails to cover the truth. Instruments: running mean NIS with the 2-dof 95% band, whiteness bars (lags 1–5), and R̂ against rtrue. Run the four experiments below.
Experiment 1 — recreate chapter 0, honestly. Drag Q to 0.01x, adaptation OFF. During straight legs the picture is seductive: glassy estimate, tiny ellipse. Watch the instruments instead: mean NIS creeps above the 2-dof 95% band [0.05, 7.38] (sf-21's chart, running live), and the lag-1 autocorrelation bar pushes outside its whiteness band. Then the first turn: the estimate cuts the corner wide, NIS spikes past 20, and — the detail worth staring at — the ellipse does not grow, because the filter's Q says turns do not happen. Confidence and error have fully decoupled.
Experiment 2 — the opposite quadrant. Q back to truth, R to 0.01x. The estimate shivers, gluing itself to every measurement. Now read the instrument correctly: with R assumed tiny, the promised S is small, so NIS = ν²/S runs HIGH — the filter is more surprised than its promise allows. Contrast with R assumed huge (100x): S is fat, NIS sinks BELOW the band, and the filter free-runs. The panel teaches the diagnosis direction: NIS high means promised S too small (overconfident somewhere); NIS low means S too fat (underconfident); the autocorrelation bars say whether the GAIN — the q/r balance — is also wrong.
Experiment 3 — the payoff. Leave the sliders wrong. Flip ADAPT on. The R̂ readout walks from its wrong start toward rtrue within roughly two window-lengths; the fading factor keeps the gain floor up so maneuvers stop being catastrophic; mean NIS bends back into the band; the whiteness bars sink into their thresholds. Then fight it: yank the sliders mid-flight, hit Storm (rtrue doubles), trigger extra turns. The stack re-converges each time, with the lag the window slider predicts.
Experiment 4 — find the limit. Set BOTH Q and R wrong by the same factor (both 10x). The estimates are fine — chapter 1's ratio invariance; the gain was never wrong. NIS is broken by the common scale, and adaptation fixes NIS — but look at the R̂ readout against truth: the stack lands on A consistent scale, not necessarily THE true split. The identifiability ridge from chapter 4, now experienced: consistency is restorable from innovations alone; the true decomposition is not, without more structure. The display says so explicitly — it reports "consistent" and "correct" separately — rather than pretending.
Innovations constrain the gain (through the ratio) strongly and the overall S level directly, so consistency and tracking heal. But the same innovation statistics are satisfied by a family of (Q, R) pairs — chapter 4's ridge — so the estimated decomposition need not be the true one. The showcase separates "consistent" from "correct" on screen because conflating them is this field's most-shipped bug.
The machinery is built. Now sit the exam: seven questions of the kind that open an estimation quals, each expecting derivation plus limits plus judgment — and the numbers you verified in this lesson are your answer key.
Protocol: attempt each question on paper or the Teach whiteboard BEFORE expanding the worked answer. The graded skill is not recall but the chain derivation → limit → decision — every answer here ends with a judgment call, because every examiner's follow-up is "so what would you ship?"
A map of the seven, so a miss tells you which chapter to revisit: gain-and-identifiability theory (Q1, Q3), the adaptive estimators and their fixed points (Q2, Q4), robustness mechanisms (Q5 fading, Q6 MMAE), and numerics (Q7 Joseph/square-root).
The recursion Ppred = P + q with P = Ppred r/(Ppred + r) closes into Ppred² − q Ppred − qr = 0, giving Ppred = (q + √(q² + 4qr))/2 and K = Ppred/(Ppred + r). For q << r: Ppred ≈ √(qr), so K ≈ √(q/r) — gain depends on the RATIO, and only as a square root. Numbers: q = 0.01, r = 1 gives K = 0.0951 (10.5-step memory); q = 0.0001 gives K = 0.00995 (100.5-step memory) — cutting q by 100 cuts K by ~10. Asymmetry: overestimating q by a factor c inflates steady-state error like c to the 1/4-to-1/2 power (gentle, and the filter stays responsive); underestimating q lengthens memory like an inverse square root, and when reality violates the model the tiny gain cannot recover before the error compounds — variance cost versus divergence risk. Judgment line: when forced to guess Q, guess fat, and let chapter-3 adaptation trim it down from data.
For a consistent filter, E[ννT] = S = H Ppred HT + R — the promise. Read backwards: R̂ = Ĉ − H Ppred HT with Ĉ the windowed sample covariance. (a) The available Ppred is the filter's INTERNAL one, computed under the current wrong R; if assumed R is too small the filter is overconfident, its internal Ppred understates the actual predicted error variance, so too much of Ĉ is attributed to R: with q = 0.09, true r = 4, assumed r = 1, the internal Ppred = 0.3484 while the actual is ~2.65 under that gain; the first solve gives R̂ = 4.4450 — overshoot. (b) At assumed R = true R the filter is consistent, so C = H Ppred HT + R holds exactly and the update maps R to itself; the iteration contracts (4.4450, 3.9684, 4.0024, 3.9998, 4.0000) onto the truth. Follow-up the examiner wants: the deployed algorithm performs this iteration THROUGH TIME with a sliding window, and the window size trades tracking lag against estimator jitter of order √(2/W) relative.
The steady-state innovation variance constrains (approximately) the single level S = H Ppred(q) HT + R — one observable, two unknowns. There is a ridge of (Q, R) pairs producing statistically indistinguishable innovation VARIANCE: raise Q̂ and lower R̂ compatibly and every scale diagnostic stays green while the individual estimates drift; estimation noise random-walks the pair along the ridge until the gain is nonsense — an identifiability failure, not numerics, so no forgetting-factor tuning fixes it. What breaks the tie: innovation AUTOCORRELATIONS at multiple lags depend on q and r differently (a wrong gain colors innovations; a pure R-scale error does not) — offline, autocovariance least squares (Odelson-Rawlings) solves for both from multi-lag correlations of logged data; online, the discipline is freeze-one-adapt-one (bench-calibrate R, adapt Q, or vice versa), optionally with a slow whiteness-driven trim on the frozen side. Judgment: never two free noise matrices funded by one scalar promise.
With Ppred = α² P (q = 0) and the scalar update P = (1−K) Ppred, steady state requires Ppred = α²(1−K) Ppred; substituting K = Ppred/(Ppred + r) gives Ppred + r = α² r, so Ppred = (α² − 1) r and K∞ = (α² − 1)/α² — strictly positive for any α > 1, whatever the model claims. Numbers: α = 1.05 gives K∞ = 0.092971 and effective window Neff = α²/(α² − 1) = 10.76; α = 1.01 gives 0.019704 and 50.75; note K∞ ≈ 1/Neff — a gain floor IS a memory ceiling. Unrolling shows data j steps old carries weight α−2j: fading is exponentially-weighted least squares, i.e., RLS with λ = α−2. Placement: adaptation measures and corrects specific mismatches with lag and estimator variance; fading is measurement-free open-loop insurance against ALL slow mismatches, purchased with a permanent variance floor of (α² − 1) r — production stacks run both.
pi(k) ∝ pi(k−1) N(νi; 0, Si), renormalized. Hand update: L1 = exp(−6.25/2.1)/√(2π × 1.05) = 0.01985; L2 = exp(−6.25/8)/√(2π × 4) = 0.09132; posterior (0.1786, 0.8214). The likelihood scores CALIBRATION, not smallness: the exponent penalizes surprise relative to the promise, and the 1/√(2πS) normalization taxes over-wide promises — an honestly-matched filter beats both a jumpy and a paranoid one. Convergence: with the true model in the bank, its probability converges to 1; if not, MMAE converges to the model closest in KL divergence. Saturation: the update is multiplicative, so log-odds accumulate linearly during a long regime; the wrong-mode probability decays exponentially (10−40-scale after long cruises), and the first real mode change must climb out of that pit — sluggish detection precisely when it matters. Floors and likelihood-discounting are band-aids; modeling the mode as a Markov chain with mixing before each cycle — the IMM — is the principled repair, and its full cycle is the sibling lesson's subject.
For arbitrary K: epost = (I−KH)epred − Kv with independent epred and v, so Ppost = (I−KH) Ppred (I−KH)T + K R KT — pure bookkeeping, no optimality. Substituting the optimal K = PpredHTS−1 and expanding collapses the quadratic to (I−KH)Ppred. Sensitivity: the Joseph value is the actual variance, minimized at Kopt, so its derivative in K vanishes there — second-order in δ (scalar case: 0.5 + 2δ² for Ppred = R = 1); the short form is linear in K — first-order error, and DECREASING through the optimum, so an overshoot claims impossible improvement. Numbers: K = 0.6 gives short 0.40 (below the optimal 0.5 — impossible) versus Joseph 0.52 (Monte Carlo 0.5196); K = 1.2 gives short −0.20 — a negative variance that poisons S, gates, and the next gain — versus Joseph 1.48, awful but true. Deployment corollary: an adaptive filter's gain is perpetually perturbed by estimation noise, so Joseph (plus explicit symmetrization, plus sequential scalar updates for diagonal R) is the mandatory update form, at ~2x flops.
Since P = S ST, P's eigenvalues are the squares of S's singular values, so κ(S) = √κ(P) — log-condition halves, and digits spent on dynamic range halve: square-root filtering in word length L matches covariance filtering in ~2L. Numbers: eigenvalues of the example are 1.999 and 0.001, κ(P) = 1999, κ(S) = 44.71; a 0.1% perturbation of P's off-diagonal (0.999 → 1.000) makes P exactly singular — the weak direction lived in trailing digits — while the same relative perturbation on S's own entry (0.04471 stores the weak direction explicitly) moves the small eigenvalue 0.1%. Structural bonus: S ST is PSD under any rounding — negative variance is unrepresentable. In float32 on the Grewal-Andrews near-collinear pair (ε = 2.5e-4), the naive update collapses the small eigenvalue to exactly 0 while float32 Potter retains 2.7e-8 versus the exact 1.6e-8. UD (Bierman): P = U D UT updated by rank-one modification with NO square-root operations — flight-computer- and fixed-point-friendly, with Thornton's MWGS as the time update. The deep connection: P-arithmetic is forming normal equations (squares the condition number); factor updates by orthogonal transforms are QR on the square-root information — the same principle that makes modern factor-graph smoothers numerically sound.
Defensible positions exist on both sides of each. But they must be argued with the detection-lag / estimator-variance / identifiability vocabulary this lesson built — "it felt more robust" is a failing answer.
Scale statistics (NIS level) and structure statistics (autocorrelation) separate the failure modes: matching variance with patterned residue means the promised S happens to be right while predictable information is being left unextracted — a gain/model problem, chapter 2's directional diagnosis. Turning R first would break the scale to chase the structure.
Strip the algebra and this was a lesson about one signal: the gap between how surprised the filter said it would be and how surprised it was. The innovation promise S = H Ppred HT + R is the filter's testable claim about itself. IAE and Sage-Husa CLOSE THE LOOP on it; fading memory BOUNDS the damage when the loop is too slow; MMAE runs the loop over DISCRETE hypotheses; and Joseph/square-root arithmetic guarantees the claim is at least computed honestly. One signal, five disciplines.
| Method | Corrects | Must know | Reaction time | Cost | Failure mode |
|---|---|---|---|---|---|
| IAE window (ch 3) | Wrong R (or Q level) | The other side, trusted | ~2 window-lengths | One buffer, one mean | Gate-window ratchet; jitter at small W |
| Sage-Husa (ch 4) | Drifting R (or Q) | The other side, trusted | ~1/(1−b) steps | One line/tick | Ridge walk if both adapted; negative evidence needs floor |
| Fading memory (ch 5) | Any slow mismatch (bounds it) | Nothing | Always on | Permanent variance floor | Cannot fix wrong H or bias; O(Neff) response only |
| MMAE bank (ch 6) | Discrete structural hypotheses | A model set containing (or KL-near) truth | Few ticks after switch (if unsaturated) | N full filters | Log-odds saturation; needs IMM for switching modes |
| Joseph + sequential (ch 7) | Update-step arithmetic under gain error | Nothing | Immediate | ~2x update flops | None at sane conditioning |
| Square-root / UD (ch 8) | Representation at extreme conditioning | Nothing | Immediate | ~1.5–2x, more code | Overkill for well-conditioned float64 |
GNSS/INS integration adapts R against multipath and jamming while fading covers IMU aging — sf-14-ins-gnss-coupling.html is the natural habitat. EKF-based VIO inflates Q for linearization error and runs Joseph religiously (ekf.html). Target trackers hand the maneuver problem to full IMM — the sibling lesson imm-estimation.html — with this lesson's MMAE as the vestibule. Flight software still ships UD filtering, and GPU-resident filter banks are re-discovering float32 square-root discipline (chapter 8).
| Go to | For |
|---|---|
| kalman-filter.html | The machine this whole lesson tunes: predict/update, Q/R, and the gain — chapter 0 is what happens after its chapter-8 tracker ships with the wrong numbers |
| sf-21-debugging-consistency.html | The NIS/NEES/whiteness test theory this lesson wields as live instruments — sf-21 detects the lying filter on the bench; this lesson closes the loop in flight |
| imm-estimation.html | Sibling PhD-track lesson: chapter 6's bank saturates because model choice is really a switching STATE — IMM adds the Markov mode dynamics, mixing step, and full worked cycle |
| ekf.html | Where adaptation earns double pay: Q inflation absorbs linearization error, fading keeps re-linearization honest, Joseph is non-negotiable under EKF conditioning |
| robust-estimation.html | Outlier theory beyond chapter 2's 3-σ gate — influence functions, M-estimators, and the Huber loss that generalizes gating into soft downweighting (Huber-KF) |
| rts-smoothing.html | Sibling PhD-track lesson: offline noise identification (EM, ALS) uses SMOOTHED estimates and multi-lag statistics to recover the Q/R identifiability that online innovations alone cannot |
| estimator-eval.html | The empirical evaluation discipline (NEES/ATE benchmarking, error statistics) for judging whether an adaptive filter actually improved anything offline |
| sf-14-ins-gnss-coupling.html | The adaptive filter's natural habitat: GNSS R varies with multipath, jamming, constellation geometry — online R adaptation plus innovation gating is standard practice |
| sf-20-learned-differentiable-filters.html | The frontier extension: learn the noise models — networks mapping sensor context to R, trained end-to-end through a differentiable Kalman filter, replacing chapter 3's hand-built estimators |
| sf-09-information-filter.html | The dual coordinates: sequential scalar updates and the normal-equations-versus-QR numerics story reappear in information form, where square-root information filtering leads to modern smoothers |
The research frontier argues in this lesson's vocabulary: learned noise models trained through differentiable filters (sf-20); adaptive filtering under adversarial spoofing, where the innovation stream itself is attacked; and interacting multiple-model banks over learned dynamics. Promises, identifiability, gain floors — the words do not change.
IAE and Sage-Husa estimate noise from the promised-versus-measured gap; MMAE scores hypotheses by promise-likelihood; fading bounds how wrong the promise can silently become; the numerics chapters keep the promise honestly computed. The innovation promise is the lesson's conserved thread — the only self-audit that needs no ground truth.
You now know how a filter doubts itself. Next door: what happens when the doubt is about which world it lives in.