Data assimilation: Kalman filter, ensemble Kalman filter and 4D-Var
Before this lesson: lesson 03 (the discrete adjoint of a time stepper; here it becomes the 4D-Var gradient) and lesson 05 (chaos and Lorenz-63: why forecasts drift). From linear algebra: lesson 07 (covariance matrices, the Schur complement and Gaussian conditioning). From the probability track: the multivariate Gaussian and Bayesian inference. A gentler first look at the Kalman filter is time series lesson 07. Time: about 120 minutes, plus the notebook.
Why this matters #
Every weather forecast starts from an analysis: the best estimate of the current state of the atmosphere. A global model state has more than numbers. The observations are far fewer, and many are indirect: a satellite measures radiances, not temperatures. Data assimilation (DA) makes the analysis by Bayes' rule.
- Reanalysis is the training data of ML weather models. ERA5, the global reanalysis of ECMWF, was made with 4D-Var (Hersbach et al. 2020). ML weather models such as GraphCast are trained on ERA5 (lesson 16). So DA choices shape the "truth" that these models learn.
- ML forecasts need an initial state. ML forecast models such as GraphCast start each forecast from an analysis made by a conventional DA system (Lam et al. 2023). Learning DA itself is an active research area (section 6).
- In ML language, the Kalman filter is exact inference in a linear-Gaussian state-space model, the EnKF is a Monte Carlo approximation of it, and 4D-Var is a MAP fit of the initial condition of a differentiable simulator by backpropagation through time.
1. Predict first #
2. The picture #
One variable #
A forecast says that a temperature anomaly is , with error variance . A thermometer says , with error variance . The two errors are independent and Gaussian. Bayes' rule multiplies the two Gaussian densities in :
The exponent is a quadratic in , so the product is again a Gaussian. Completing the square gives its mean and variance :
Read these formulas in three ways.
- The gain says how much we trust the observation. gives : copy the observation. gives : keep the forecast.
- is the innovation: the part of the observation that the forecast did not predict. The analysis increment is the fraction of it.
- Precisions add. The precision (inverse variance) of the analysis is the sum of the two precisions. So the analysis is more certain than each source alone. does not depend on the observed value .
Move the three sliders. In this lesson blue is the forecast (prior), aqua is the observation and orange is the analysis (posterior). With equal variances the analysis sits halfway, at . Make small and the orange curve jumps to the observation; make it large and the orange curve stays with the forecast. Notice that the orange curve is always the narrowest, and its width does not change when you move .
The forecast is with . An observation has error variance . What is the analysis?
, so and . Check with precisions: . The plain average ignores the variances. Taking the observation alone throws away the forecast, which still has information. Variances never add in an update: the analysis variance is smaller than both.
Two variables: the covariance carries the information #
Now the state has two numbers, , for example the temperature at two neighbouring grid points. The forecast error has a covariance matrix . Its off-diagonal entry says how the two errors move together. An instrument observes only : with .
If the forecast errors are positively correlated, a forecast that is too cold at point 1 is probably too cold at point 2 as well. So when the observation says "point 1 is warmer than forecast", Bayes' rule also warms point 2. The amount is set by the correlation. With zero correlation, point 2 does not move at all.
Drag the aqua handle to change the observed value of , and move the correlation slider. The blue ellipses are the forecast (1 and 2 standard deviations), the aqua band is the observation , and the orange ellipses are the analysis. The orange arrow is the analysis increment. Notice: the arrow has a vertical part (a change of the unobserved ) only when , and the ellipse shrinks most along the observed axis.
In the prior, and are uncorrelated (). You observe with a small error. What happens to the estimate of ?
The gain is , and its second entry is . So the increment of is , and . Information reaches an unobserved variable only through the covariance. This is why the forecast covariance is the central object of every DA method.
Cycling: forecast, then analysis, then forecast again #
DA runs in cycles. The forecast step moves the estimate forward with the model. The uncertainty grows: the model moves and stretches the error ellipse, and the model error adds more. The analysis step brings in the new observations. The uncertainty shrinks. Then the next forecast starts from the analysis.
Press the button several times. It alternates the two steps of the Kalman filter on an oscillator whose state is (position, velocity). Only the position is observed. After a forecast step the ellipse has moved and grown. After an analysis step it has shrunk, mostly along the position axis, where the observation line is. The grey dashed curve is the truth. Watch the time chart: after a few cycles the orange band of standard deviations has a steady width, and the truth stays inside it. The velocity is never observed, but its uncertainty also falls, because the model mixes position and velocity.
Three families of methods #
| Method | How it represents uncertainty | What it needs | Used for |
|---|---|---|---|
| Kalman filter (KF) | full covariance () | a linear model | small linear systems, tracking |
| ensemble Kalman filter (EnKF) | model runs; sample covariance | only forward runs of the model | large nonlinear models (weather, ocean) |
| 3D-Var / 4D-Var | a fixed background covariance ; the analysis is an optimum | the adjoint model (4D-Var) | large models (global weather) |
Section 3 builds each one from Bayes' rule.
3. Definitions and main results #
The linear-Gaussian state-space model #
For time steps :
with , and , all and all independent.
- is the state, and the observation (usually ).
- is the model (one forecast step) and the observation operator.
- is the model-error covariance and the observation-error covariance.
- means all observations .
The superscript (forecast) marks the prior at an analysis time, and (analysis) marks the posterior. In variational DA the prior is called the background, with covariance .
Gaussian conditioning #
Why this is true (intuition). Predict from with the best linear rule. What is left over, minus the prediction, is uncorrelated with . For jointly Gaussian variables, uncorrelated means independent. So knowing tells us the prediction exactly and tells us nothing about the leftover.
Theorem 1 (Gaussian conditioning). Let be jointly Gaussian with means and covariance blocks , , , , with invertible. Then
The covariance is the Schur complement of in the joint covariance (linear algebra 07).
Proof.
- Let and .
- . The pair is a linear map of , so it is jointly Gaussian. For jointly Gaussian variables, zero covariance means independence. So and are independent.
- So the law of given is the same as without : mean and covariance . (Expand and use .)
- , and is a constant once is known. Add it to the mean.
The Kalman update #
Theorem 2 (Kalman update). Prior . Observation with independent of . Then with
is the innovation covariance and the Kalman gain.
Proof.
- is a linear map of the independent Gaussians , so it is jointly Gaussian.
- Compute the blocks. . . , because has mean and is independent of .
- Theorem 1 with and gives the mean and the covariance .
For one variable (, ) this is exactly the formula of section 2. Three consequences:
- An observation never increases the uncertainty. , which is positive semidefinite.
- and do not depend on the observed value . They can be computed before the data arrive.
- The cross-covariance moves unobserved variables. Row of is nonzero only if variable is correlated (in the forecast) with what observes.
Information form, Woodbury and the Joseph form
Information form (for invertible and ):
Reason: is quadratic in . Its Hessian is the inverse posterior covariance, and its minimiser is the posterior mean. The Woodbury identity turns one form into the other. The covariance form solves a system; the information form an system. Use the smaller one.
Joseph form. In floating point, can lose symmetry and positive definiteness over many cycles. The Joseph form is symmetric positive semidefinite for every , and it equals when is the Kalman gain. It is also the covariance of the analysis error for any gain ; minimising its trace over gives the Kalman gain again.
The Kalman filter #
Start from , . For :
- Forecast step: and .
- Analysis step: Theorem 2 with prior and observation , which gives and .
Keep the time indices straight: uses the observations , and uses .
Theorem 3 (the Kalman filter is exact). For the linear-Gaussian model, for every , and .
Proof by induction on k
- At there are no data, and .
- Forecast. Assume . The noise is independent of and of . So, given , is a linear map of independent Gaussians: Gaussian with mean and covariance .
- Analysis. , and is independent of and of . So, given , the pair is exactly the setting of Theorem 2 with prior . Conditioning on gives .
Step 2 of the proof (the forecast) is where linearity matters. A nonlinear model maps a Gaussian to a non-Gaussian distribution. Then every Gaussian update (the extended Kalman filter, the EnKF) is an approximation.
Why large models do not use the KF. The KF carries , an matrix: numbers for . Computing needs about runs of the (tangent-)linear model per step. This is why large systems use ensembles or variational methods.
The Rauch–Tung–Striebel smoother
The filter uses only past observations. A smoother also uses future ones: for . For the linear-Gaussian model, run the filter forward and store . Then go backward from , :
This is the RTS smoother (Särkkä and Svensson, §12.2). It gives the exact ; the notebook of this lesson does not need it, but the same forward–backward structure appears in 4D-Var below. In ML terms, filter and smoother are the forward and backward passes of message passing on a chain.
The ensemble Kalman filter #
Keep model states , the ensemble members; typically is 20 to 100. Let be their mean, and let be the anomaly matrix with columns . The sample covariance replaces the exact covariance.
- Forecast: run every member through the full nonlinear model (plus model noise, if used). No tangent-linear model is needed.
- Analysis (stochastic EnKF): draw independent and update each member with its own perturbed observation :
In a large model, never form . Use , where (size ) holds the anomalies in observation space.
Why perturb the observations. Take a linear and write for a forecast anomaly. The analysis anomaly is , where is the perturbation minus the mean perturbation. Its expected covariance is
the correct analysis covariance (the Joseph form above). Without the perturbations the term is missing, and the analysis spread is too small. (Square-root filters get the correct covariance without random numbers; see the folded box below.)
Three properties.
- Linear model, : the EnKF converges to the KF.
- Nonlinear model: the forecast is a Monte Carlo run of the true model, but the analysis is still a linear, Gaussian update. So the EnKF is not exact for non-Gaussian posteriors.
- Rank. with . So every column of , and every increment , is a combination of the columns of . The anomalies sum to zero (), so their span has dimension at most . With and , the analysis can change the state in only 49 directions.
An EnKF has members for a model with variables (Lorenz-96). What is the largest possible rank of the sample covariance ?
has rank at most . The 20 columns of sum to zero, so at most 19 of them are independent. So the EnKF can correct the 40 variables in at most 19 directions, unless localisation changes .
Sampling error and localisation. For Gaussian data, the sample correlation of two uncorrelated variables has a standard deviation of about : for . So an observation in Zurich also "corrects" a grid point in Lisbon, through a correlation that is only noise. These are spurious correlations. Localisation removes them.
- Covariance localisation: replace by (the elementwise product), with , where is the distance between points and , and is a length scale. The taper is 1 at distance 0 and decays to 0. The standard choice is the Gaspari–Cohn function, a piecewise rational function of degree 5 that is exactly 0 beyond . By the Schur product theorem, is positive semidefinite when and are. It usually has full rank, so the analysis is no longer limited to directions.
- Domain localisation (used by the LETKF): analyse each grid point separately, with only the observations within a radius.
- The radius is a trade-off. Too small cuts real correlations; too large lets the noise in. More members allow a larger radius. It is tuned with twin experiments.
Inflation. Even with localisation, the ensemble is usually underdispersive: its spread is smaller than its real error. There are two reasons. First, sampling error in : the same members estimate the gain and are updated with it. Second, model error that the ensemble does not represent. An overconfident ensemble gives too little weight to new observations, shrinks further, and drifts away from the truth. This is filter divergence. Multiplicative inflation
with about 1.01 to 1.1, multiplies by . Additive inflation adds random perturbations, and relaxation methods (RTPS, RTPP) pull the analysis spread back toward the forecast spread. Inflation changes the size of , not its span and not its correlations. So inflation alone cannot fix the rank problem or the spurious correlations. That is the job of localisation.
A twin experiment on Lorenz-63. One model run is the truth (grey); all three variables are observed every 0.15 time units with error variance (aqua). Step through a cycle: the forecast members spread out along the attractor, and the analysis members are pulled toward the observation. The lower chart shows the error of the ensemble mean, the free run (the model without data, which loses the truth after a few cycles), and the ensemble spread (dashed). Then try: ; perturbed observations off with ; and off with . Watch for filter divergence: the spread falls far below the error.
Your EnKF spread is much smaller than its actual error, and the error slowly grows. What does multiplicative inflation fix, and what does it not fix?
Inflation multiplies every anomaly by , so becomes : a larger gain, and the filter listens to the data again. The anomalies keep the same span and the same correlations, so the rank limit and the noise in the correlations stay. Localisation fixes those. Perturbed observations fix a different missing term ().
Square-root filters: ETKF and LETKF
A square-root (deterministic) filter updates the mean with the Kalman formula and transforms the anomalies so that their sample covariance equals exactly, with no random numbers. Let (so ) and . The ensemble transform Kalman filter (ETKF; Hunt, Kostelich and Szunyogh 2007) works in the -dimensional space of ensemble weights:
with the symmetric square root. Then , and the new anomalies still sum to zero. The cost is dominated by matrices, so it is cheap. The LETKF does this for every grid point with only the nearby observations (domain localisation), and the points are independent, so it runs in parallel. Several weather services use LETKF-type systems for their ensembles (for example the KENDA system: Schraff et al. 2016).
Variational DA: 3D-Var and 4D-Var as MAP estimation #
3D-Var. Prior , observation with , and a possibly nonlinear observation operator . Bayes' rule gives with
The first term is the background term , the second the observation term . 3D-Var computes the MAP estimate with a quasi-Newton or conjugate-gradient method. It is Tikhonov regularization with weighted norms (lesson 04).
For linear , the 3D-Var minimum is the Kalman analysis mean. is quadratic. Set its gradient to zero: , so . This is the information form of the Kalman mean with , . The Hessian is . (For a Gaussian posterior the mode and the mean are the same point.)
Strong-constraint 4D-Var. Use all observations of a time window, and assume a perfect model: , where is one (possibly nonlinear) time step and counts the steps. Then every state is a function of the initial state , and is the only control variable. Observations exist at the steps in a set :
is up to a constant, so is the MAP initial state. The forecast then starts from this analysis.
Theorem 4 (4D-Var gradient by the adjoint). Let at (the tangent-linear model) and the Jacobian of at . Set for , and otherwise. Run the adjoint recursion backward in time, with the adjoint state :
for .
Then .
Proof.
- Write the observation term as with . Then .
- depends on through model steps. By the chain rule, its Jacobian is . So .
- Let be the gradient, with respect to , of all observation terms at steps . The terms at steps depend on only through , so their gradient is . The term at step adds . This is the recursion. At it gives . The recursion shares the products of step 2 between all observation times.
- Add the gradient of the background term, .
Cost. One forward run (store , or checkpoints as in lesson 03) and one backward run of the adjoint model. The adjoint run costs a small constant times a forward run, independent of . Finite differences need forward runs. In ML terms, each step is a vector–Jacobian product of reverse-mode AD. If is one RK4 step, is the VJP of that step (the discrete adjoint of lesson 03). It is the exact gradient of the discrete cost, so it agrees with finite differences up to their own error.
Press "Gradient step" a few times. The square plane is the space of the control variable = (initial position, initial velocity). Grey curves are contours of ; the blue point is the background , the grey point the truth, and the orange point the current (drag it anywhere). The violet arrow is the next step along , and comes from one backward adjoint sweep; the readout compares it with finite differences. The time chart shows the trajectories over the window: the orange one bends toward the observations. Then set : with one observation time the contours become a long valley, and the velocity is fixed only by the background.
Practical notes.
- Linear model, no model error (): is quadratic, and the 4D-Var analysis, moved to the end of the window, equals the KF analysis there. Both are the same Gaussian posterior (worked example (c)).
- A 4D-Var analysis has no error bar by itself. It is the mode. The uncertainty needs the Hessian of (its inverse is the posterior covariance in the linear case) or an ensemble.
- Window length. In a chaotic model, small changes of grow like , where is the largest Lyapunov exponent (lesson 05). So gets more and more local minima as the window grows. Operational windows are 6 to 12 hours; ERA5 uses 12-hour windows (Hersbach et al. 2020).
Incremental and weak-constraint 4D-Var
Incremental 4D-Var (Courtier, Thépaut and Hollingsworth 1994; used at ECMWF) is a Gauss–Newton method. Each outer loop runs the full nonlinear model from the current . Each inner loop minimises a quadratic cost for the increment , built with the tangent-linear model and its adjoint , often at a lower resolution. The quadratic inner problem is solved with conjugate gradients, preconditioned by (the control variable is ).
Weak-constraint 4D-Var drops the perfect-model assumption. It adds model-error terms with , so the control variable is the whole trajectory, not only . This allows longer windows.
Ensembles inside 4D-Var. The background covariance is hard to specify. Modern systems estimate part of it from an ensemble of data assimilations (ERA5 uses 10 members; Hersbach et al. 2020), or combine 4D-Var with an ensemble covariance ("hybrid" and "4DEnVar" methods).
Answers to section 1. (1) , so and . With the mean is , but the variance is still : in the linear-Gaussian case the posterior variance does not depend on the data. (A real system would also check whether is a gross error before using it.) (2) The estimate of also increases, by with . Its variance also falls. (3) Finite differences: runs (forward differences) or (central), so about . Adjoint: one forward run plus one adjoint run, whatever is.
4. Worked example #
(a) A Kalman update in two variables. Two neighbouring grid points have forecast temperature anomalies with (correlation ). A station observes point 1 only: , , .
- (the first row of ), and . So .
- (the first column of ). So .
- The innovation is . So . The unobserved point 2 moves by , through the correlation.
- , so . Row 1 of is . Row 2 is . So . The variance at point 1 drops from 2 to ; at point 2 from 2 to .
- Check with the information form. and , so their sum is . Its determinant is , and its inverse is . ✓
- 3D-Var with and has the Hessian , and its gradient at is . So the 3D-Var minimum is . ✓ (Here .)
(b) Why the stochastic EnKF perturbs the observations (one variable, ). Let and , so . A forecast anomaly becomes .
- With perturbed observations, the expected analysis variance is . This equals , the KF value. ✓
- Without them it is : half the correct variance. Repeat this every cycle and the ensemble becomes far too confident.
(c) 4D-Var equals the KF at the end of the window (one variable). Model with no model error. Background , . One observation at step 1 with and .
- , so .
- Adjoint: , , and . So .
- Check at : the adjoint gives . Direct differentiation of gives . ✓
- Minimum: gives . The Hessian is , so the posterior variance of is .
- Move it to step 1: , with variance .
- Kalman filter: forecast , . Then , , . The same. ✓
In worked example (c), the observation is instead of 2 (everything else is the same). What is the 4D-Var analysis ?
Now , so . The gain does not depend on the data, but the analysis does: it is linear in , and doubling doubles here. ignores the background term. is the analysis at step 1 (), which equals the Kalman filter value .
5. In code #
Three short NumPy checks. First, the Kalman formulas and Bayes' rule on a grid (no Kalman formula) give the same answer for worked example (a):
import numpy as np
from scipy.stats import multivariate_normal, norm
# Worked example (a): prior N(x_f, P_f) on (x1, x2); one observation y = x1 + v, v ~ N(0, 1).
x_f, P_f, y = np.zeros(2), np.array([[2.0, 1.0], [1.0, 2.0]]), 3.0
# 1) The Kalman formulas.
H, R = np.array([[1.0, 0.0]]), np.array([[1.0]])
S = H @ P_f @ H.T + R # innovation covariance, here 3
K = np.linalg.solve(S, H @ P_f).T # K = P_f H^T S^-1 (a solve, not inv)
x_a = x_f + K @ (y - H @ x_f)
P_a = (np.eye(2) - K @ H) @ P_f
print(K.ravel().round(4), x_a.round(4)) # [0.6667 0.3333] [2. 1.]
# 2) Bayes' rule on a grid, with no Kalman formula: posterior ∝ prior × likelihood.
g = np.linspace(-8.0, 10.0, 901)
X1, X2 = np.meshgrid(g, g, indexing="ij")
prior = multivariate_normal(x_f, P_f).pdf(np.stack([X1, X2], axis=-1))
likelihood = norm.pdf(y, loc=X1, scale=1.0) # y = x1 + v, v ~ N(0, 1)
post = prior * likelihood
post /= post.sum()
mean = np.array([(post * X1).sum(), (post * X2).sum()])
D = np.stack([X1 - mean[0], X2 - mean[1]]) # shape (2, 901, 901)
cov = np.einsum("aij,bij,ij->ab", D, D, post)
print(mean.round(4)) # [2. 1.]
print(cov.round(4)) # [[0.6667 0.3333]
# [0.3333 1.6667]]
print(np.allclose(cov, P_a, atol=1e-4)) # True
Second, sampling error and localisation. A 10-member ensemble estimates a covariance with correlation length 3 on 60 grid points:
import numpy as np
def gaspari_cohn(r):
"""Gaspari-Cohn taper; r = distance / c. Equal to 1 at r = 0 and exactly 0 for r >= 2."""
r = np.abs(r)
out = np.zeros_like(r)
a, b = r <= 1, (r > 1) & (r < 2)
ra, rb = r[a], r[b]
out[a] = -0.25 * ra**5 + 0.5 * ra**4 + 0.625 * ra**3 - 5 / 3 * ra**2 + 1
out[b] = rb**5 / 12 - 0.5 * rb**4 + 0.625 * rb**3 + 5 / 3 * rb**2 - 5 * rb + 4 - 2 / (3 * rb)
return out
rng = np.random.default_rng(0)
n, N, ell = 60, 10, 3.0 # grid points on a line, members, correlation length
d = np.abs(np.arange(n)[:, None] - np.arange(n)[None, :]).astype(float)
B = np.exp(-d / ell) # true forecast-error covariance
rho = gaspari_cohn(d / (2 * ell)) # taper with c = 2 ell: zero beyond distance 4 ell
print(np.linalg.eigvalsh(rho).min() > 0) # True: the taper is a correlation matrix
err_raw, err_loc, far_corr = [], [], []
for _ in range(200):
X = rng.multivariate_normal(np.zeros(n), B, size=N) # ensemble, one member per row
Pf = np.cov(X, rowvar=False) # sample covariance, divides by N - 1
err_raw.append(np.linalg.norm(Pf - B) / np.linalg.norm(B))
err_loc.append(np.linalg.norm(rho * Pf - B) / np.linalg.norm(B))
far_corr.append(np.corrcoef(X, rowvar=False)[d >= 30]) # true correlation <= e^-10
print(np.linalg.matrix_rank(Pf), np.linalg.matrix_rank(rho * Pf)) # 9 60
print(round(np.std(np.concatenate(far_corr)), 3), round(1 / np.sqrt(N - 1), 3)) # 0.334 0.333
print(round(np.mean(err_raw), 2), round(np.mean(err_loc), 2)) # 1.5 0.59
The raw sample covariance has rank . Its relative error (1.5) is larger than the error of the zero matrix (1.0). The far correlations are pure noise of size . The tapered covariance has full rank and less than half the error.
Third, 4D-Var on a linear oscillator (position and velocity; only the position is observed).
cost_and_grad runs exactly the adjoint recursion of Theorem 4, and the minimiser equals the
solution of one weighted least-squares problem:
import numpy as np
from scipy.linalg import expm
from scipy.optimize import minimize
# Linear oscillator: state (position, velocity); exact one-step propagator M; observe position only.
h, n_steps, every, sig_o = 0.1, 50, 10, 0.1
M = expm(h * np.array([[0.0, 1.0], [-1.0, 0.0]]))
H = np.array([[1.0, 0.0]])
x_b, B_inv, R_inv = np.array([0.5, -0.5]), np.eye(2), np.eye(1) / sig_o**2
obs_steps = range(0, n_steps + 1, every) # observations at steps 0, 10, ..., 50
rng = np.random.default_rng(1)
x, ys = np.array([1.0, 0.5]), {} # the truth starts at (1, 0.5)
for j in range(n_steps + 1):
if j in obs_steps:
ys[j] = H @ x + sig_o * rng.standard_normal(1)
x = M @ x
def cost_and_grad(x0):
"""Strong-constraint 4D-Var: one forward run, then one backward (adjoint) run."""
xs = [x0]
for j in range(n_steps): # forward: store the trajectory
xs.append(M @ xs[-1])
J = 0.5 * (x0 - x_b) @ B_inv @ (x0 - x_b)
lam = np.zeros(2)
for j in range(n_steps, -1, -1): # backward: lam_j = M^T lam_{j+1} + f_j
if j < n_steps:
lam = M.T @ lam
if j in ys:
d = ys[j] - H @ xs[j] # innovation at step j
J += 0.5 * d @ R_inv @ d
lam = lam - H.T @ R_inv @ d # f_j = -H^T R^-1 (y_j - H x_j)
return J, B_inv @ (x0 - x_b) + lam
v, e = np.array([0.2, 0.3]), 1e-6
fd = [(cost_and_grad(v + e * u)[0] - cost_and_grad(v - e * u)[0]) / (2 * e) for u in np.eye(2)]
print(np.abs(cost_and_grad(v)[1] - fd).max() < 1e-5) # True: adjoint gradient = finite differences
x_4dvar = minimize(cost_and_grad, x_b, jac=True, method="L-BFGS-B").x
# The same MAP estimate as one weighted least-squares problem: y_j = H M^j x0 + noise.
G = np.vstack([H @ np.linalg.matrix_power(M, j) for j in obs_steps])
A = np.vstack([np.eye(2), G / sig_o]) # whitened rows (B = I)
b = np.concatenate([x_b, np.concatenate([ys[j] for j in obs_steps]) / sig_o])
x_lsq = np.linalg.lstsq(A, b, rcond=None)[0]
print(x_4dvar.round(3), x_lsq.round(3)) # [1.049 0.484] [1.049 0.484]
The velocity is never observed, but 4D-Var recovers it (0.484; the truth is 0.5). The model carries the information from the observed position to the velocity.
6. Where it shows up #
- Global weather prediction. ECMWF uses incremental 4D-Var together with an ensemble of data assimilations that estimates the background errors (see the IFS documentation, Part II). ERA5 was made with this system: 12-hour 4D-Var windows and a 10-member ensemble of 4D-Vars (Hersbach et al. 2020).
- Kilometre-scale ensembles. The KENDA system (Schraff et al. 2016) is an LETKF, an ensemble Kalman filter with domain localisation. It was built for the COSMO model and is used by national weather services for high-resolution ensemble forecasts.
- ML for DA (as of 2026). An ML emulator makes large ensembles cheap, and automatic differentiation gives its adjoint for 4D-Var (test it against finite differences, as here). Score-based data assimilation (Rozet and Louppe 2023) uses a diffusion-model prior over whole trajectories and adds the observations only at sampling time (lesson 13). End-to-end systems such as Aardvark Weather (Allen et al. 2025) learn the whole path from raw observations to forecasts.
- ML in general. The KF and the RTS smoother are exact inference in linear dynamical systems. 4D-Var is backpropagation through time with respect to an initial condition. Ensemble Kalman inversion uses the EnKF update as a derivative-free optimiser, also for training neural networks (Kovachki and Stuart 2019).
- Seismology. Full-waveform inversion uses the same adjoint method as 4D-Var: a forward wavefield, a backward adjoint wavefield, and the gradient with respect to the earth model (lesson 04). The control is a parameter field, not an initial state. Ensemble data assimilation has also been used to estimate and forecast the stresses on a fault in models of earthquake sequences (van Dinther, Künsch and Fichtner 2019).
7. Common mistakes #
- Using
inv(S)instead of a solve with , and letting drift away from symmetric positive semidefinite over many cycles. Symmetrise, or use the Joseph form. - Mixing up the time indices. is the forecast for time made from ; the observation updates into . Updating with or forecasting from is a common bug.
- A stochastic EnKF without perturbed observations, or one perturbation shared by all members. The spread becomes too small, and the filter diverges.
- instead of in the sample covariance. With this is a 10% error.
- Expecting inflation to fix . Inflation keeps the span of the anomalies. Only localisation (or more members) adds directions and removes spurious correlations.
- Reading a 3D-Var or 4D-Var analysis as having no uncertainty. These methods give the mode. The uncertainty needs the Hessian (its inverse is in the linear case) or an ensemble.
- 4D-Var adjoint bugs: linearising around the wrong state (use the stored forward trajectory), forgetting the observation at the initial time , or using the continuous adjoint and expecting it to match finite differences of the discrete cost to round-off (lesson 03).
8. Check yourself #
- Prior , observation with , . Give , and .
Answer
, , . Check with precisions: . - Show that in Theorem 2: an observation never increases the uncertainty.
Answer
, because . Since , for every . - Your EnKF has members for a model with variables. Why can the analysis increment
lie in only 39 directions, and why does localisation help where inflation does not?
Answer
with , so : every increment is a combination of the anomalies, which sum to zero (rank ). Inflation multiplies the anomalies by : same span, same correlations. Localisation removes the spurious long-range correlations (of size about ) and gives a covariance that usually has full rank, so the analysis can act locally in many more directions. - 4D-Var with the scalar model , , , and one observation at
step 1 with , . (a) Compute with the adjoint recursion. (b) Find the
analysis and its posterior variance.
Answer
(a) , , , so . (b) , so (equal to 1 at ✓). Then , and the variance is . - For a nonlinear model, why is the EnKF forecast step often better than the extended Kalman filter
(EKF), which propagates with the tangent-linear model?
Answer
The EnKF runs the full nonlinear model for each member. It needs no tangent-linear or adjoint code, it needs runs instead of about , and the ensemble feels the nonlinear evolution of the mean and the spread. The EKF needs about tangent-linear runs to propagate , and its linearisation error grows with the forecast length. - Why does strong-constraint 4D-Var become hard when the window is long in a chaotic model?
Answer
Perturbations of grow like , where is the largest Lyapunov exponent. So becomes very rough, with many local minima, and the tangent-linear approximation behind incremental 4D-Var fails. A gradient method then finds a local minimum. Remedies: shorter windows (6 to 12 hours), growing the window step by step, weak-constraint 4D-Var, or ensemble methods.
Give the Kalman gain for prior covariance , observation operator and observation-error covariance .
; the matrix is the innovation covariance.
Give the analysis mean and covariance of the Kalman update.
and .
Scalar case: , , , . Give , and .
, , (precisions add: ).
Write the forecast step of the Kalman filter.
and .
Why does observing change the estimate of the unobserved ?
Through the forecast cross-covariance: . With , does not change.
Why does the stochastic EnKF perturb the observations?
Without perturbations the analysis covariance is , which misses ; the spread is too small and the filter can diverge.
What is the largest rank of the EnKF sample covariance with members, and why?
: and the anomaly columns sum to zero.
What does localisation do in an EnKF?
It multiplies elementwise by a taper that decays with distance (), which removes spurious long-range correlations and usually gives full rank.
What does multiplicative inflation fix, and what does it not fix?
It fixes underdispersion (spread smaller than error) by scaling by ; it does not change the span or the correlations of the anomalies.
How is the 4D-Var gradient computed, and what does it cost?
One forward run, then one backward sweep ; . The cost does not depend on the state size .
9. Interview angle #
- "Derive the Kalman gain." Gaussian conditioning in three lines (Theorem 2). Another route: the that minimises the trace of the Joseph-form covariance .
- "KF, EKF, EnKF, particle filter, 4D-Var: when do you use which?" KF: linear-Gaussian, small . EKF: mildly nonlinear, small . EnKF: large nonlinear models, near-Gaussian errors, no adjoint needed. Particle filter: strongly non-Gaussian, small . 4D-Var: large models with an adjoint; it uses the dynamics over a window and gives a mode without an error bar.
- "What is 4D-Var in ML language?" A MAP fit of the initial condition of a differentiable simulator. The adjoint is backpropagation through time; memory is handled by checkpointing.
- "Your EnKF spread is much smaller than its RMSE. What do you check?" Perturbed observations (or a correct square-root update), the localisation radius, inflation, model error, and the ensemble size.
- "How would you use an ML emulator in DA?" For cheap ensembles in an EnKF, or for its autodiff adjoint in 4D-Var. Treat its error as model error, and test its gradients against finite differences.
10. Practice #
The notebook for this lesson has five exercises, each with a check cell and a folded solution:
- Write the Kalman update and check it against Bayes' rule on a grid for a two-variable Gaussian.
- Run the Kalman filter on a noisy oscillator with only the position observed, and check it against the exact posterior from one large Gaussian conditioning.
- Write the 3D-Var cost and gradient, minimise it, and show that the minimum is the Kalman analysis.
- Write the stochastic EnKF analysis with inflation, and run a twin experiment on Lorenz-63: the error must fall below the free run and below the observation error.
- Write the strong-constraint 4D-Var gradient with the adjoint recursion for Lorenz-63 and check it with finite differences.
11. Watch and read #
- Evensen, Vossepoel and van Leeuwen, Data Assimilation Fundamentals (Springer 2022, open access): ch. 2–3 (DA as Bayesian inference and MAP), ch. 4 (strong-constraint 4D-Var), ch. 6 (Kalman filters and 3D-Var), ch. 8 (ensemble methods), ch. 10 (localisation and inflation).
- Carrassi, Bocquet, Bertino and Evensen, Data assimilation in the geosciences: an overview of methods, issues and perspectives (WIREs Climate Change 2018): §3 (Kalman filter and 4D-Var), §4 (EnKF, square-root filters, localisation and inflation), §5 (chaotic dynamics).
- Särkkä and Svensson, Bayesian Filtering and Smoothing, 2nd ed. (2023, free PDF from the authors): the Kalman filter, the RTS smoother and the Gaussian identities.
- Nansen Environmental and Remote Sensing Center, DA-tutorials (Jupyter notebooks): Bayesian inference, the multivariate Kalman filter, and writing your own EnKF.
- Papers cited in this lesson: Hersbach et al. 2020, "The ERA5 global reanalysis" (Q. J. R. Meteorol. Soc.); Schraff et al. 2016, "Kilometre-scale ensemble data assimilation for the COSMO model (KENDA)" (Q. J. R. Meteorol. Soc.); Hunt, Kostelich and Szunyogh 2007, "Efficient data assimilation for spatiotemporal chaos: a local ensemble transform Kalman filter" (Physica D); Rozet and Louppe 2023, Score-based data assimilation; Allen et al. 2025, Aardvark weather; Kovachki and Stuart 2019, Ensemble Kalman inversion; Lam et al. 2023, GraphCast; van Dinther, Künsch and Fichtner 2019, "Ensemble data assimilation for earthquake sequences" (Geophys. J. Int.).
Runs in your browser with nothing to install. The notebook repeats the key demos and has exercises with checks; solutions are inside, folded.
No Google account needed: Python runs inside your browser. The first start downloads about 40 MB; your changes are saved in this browser only.