Type at least 2 letters. Enter opens the first result, Esc closes.

    ML for Science · Lesson 6 · Chaos, data and uncertainty

    Data assimilation: Kalman filter, ensemble Kalman filter and 4D-Var

    Advanced 120 min 5 interactive figures 4 videos Colab notebook

    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 10810^8 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 xfx^f, with error variance PfP^f. A thermometer says yy, with error variance RR. The two errors are independent and Gaussian. Bayes' rule multiplies the two Gaussian densities in xx:

    p(x∣y)  ∝  p(x)  p(y∣x),p(x \mid y) \;\propto\; p(x)\; p(y \mid x),
    p(x)∝exp⁡ ⁣(−(x−xf)22Pf),p(y∣x)∝exp⁡ ⁣(−(y−x)22R).p(x) \propto \exp\!\Big(-\frac{(x - x^f)^2}{2P^f}\Big), \qquad p(y \mid x) \propto \exp\!\Big(-\frac{(y - x)^2}{2R}\Big).

    The exponent is a quadratic in xx, so the product is again a Gaussian. Completing the square gives its mean xax^a and variance PaP^a:

    xa=xf+K (y−xf),K=PfPf+R,x^a = x^f + K\,(y - x^f), \qquad K = \frac{P^f}{P^f + R},
    Pa=(1−K) Pf=PfRPf+R,P^a = (1 - K)\,P^f = \frac{P^fR}{P^f + R},
    1Pa=1Pf+1R.\frac{1}{P^a} = \frac{1}{P^f} + \frac{1}{R}.

    Read these formulas in three ways.

    • The gain K∈[0,1]K \in [0, 1] says how much we trust the observation. R→0R \to 0 gives K→1K \to 1: copy the observation. R→∞R \to \infty gives K→0K \to 0: keep the forecast.
    • y−xfy - x^f is the innovation: the part of the observation that the forecast did not predict. The analysis increment xa−xfx^a - x^f is the fraction KK 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. PaP^a does not depend on the observed value yy.
    Interactive figure: loading…

    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 K=0.5K = 0.5. Make RR 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 yy.

    Quick check

    The forecast is xf=2x^f = 2 with Pf=4P^f = 4. An observation y=7y = 7 has error variance R=1R = 1. What is the analysis?

    Two variables: the covariance carries the information #

    Now the state has two numbers, x=(x1,x2)x = (x_1, x_2), for example the temperature at two neighbouring grid points. The forecast error has a covariance matrix PfP^f. Its off-diagonal entry says how the two errors move together. An instrument observes only x1x_1: y=Hx+vy = Hx + v with H=(1    0)H = (1 \;\; 0).

    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.

    Interactive figure: loading…

    Drag the aqua handle to change the observed value of x1x_1, and move the correlation slider. The blue ellipses are the forecast (1 and 2 standard deviations), the aqua band is the observation ±R\pm\sqrt R, 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 x2x_2) only when ρ≠0\rho \ne 0, and the ellipse shrinks most along the observed axis.

    Quick check

    In the prior, x1x_1 and x2x_2 are uncorrelated (P12f=0P^f_{12} = 0). You observe x1x_1 with a small error. What happens to the estimate of x2x_2?

    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.

    Interactive figure: loading…

    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 ±2\pm 2 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 PP (n×nn \times n) a linear model small linear systems, tracking
    ensemble Kalman filter (EnKF) NN model runs; P≈P \approx sample covariance only forward runs of the model large nonlinear models (weather, ocean)
    3D-Var / 4D-Var a fixed background covariance BB; 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 k=1,2,…k = 1, 2, \dots:

    xk=Mxk−1+wk,wk∼N(0,Q),x_k = M x_{k-1} + w_k,\qquad w_k \sim \mathcal N(0, Q),
    yk=Hxk+vk,vk∼N(0,R),y_k = H x_k + v_k,\qquad v_k \sim \mathcal N(0, R),

    with x0∼N(x0a,P0a)x_0 \sim \mathcal N(x^a_0, P^a_0), and x0x_0, all wkw_k and all vkv_k independent.

    • xk∈Rnx_k \in \R^n is the state, and yk∈Rpy_k \in \R^p the observation (usually p≪np \ll n).
    • MM is the model (one forecast step) and HH the observation operator.
    • QQ is the model-error covariance and RR the observation-error covariance.
    • y1:ky_{1:k} means all observations y1,…,yky_1, \dots, y_k.

    The superscript ff (forecast) marks the prior at an analysis time, and aa (analysis) marks the posterior. In variational DA the prior is called the background, xbx^b with covariance BB.

    Gaussian conditioning #

    Why this is true (intuition). Predict xx from yy with the best linear rule. What is left over, xx minus the prediction, is uncorrelated with yy. For jointly Gaussian variables, uncorrelated means independent. So knowing yy tells us the prediction exactly and tells us nothing about the leftover.

    Theorem 1 (Gaussian conditioning). Let (x,y)(x, y) be jointly Gaussian with means μx,μy\mu_x, \mu_y and covariance blocks Σxx\Sigma_{xx}, Σxy\Sigma_{xy}, Σyx=Σxy⊤\Sigma_{yx} = \Sigma_{xy}^\top, Σyy\Sigma_{yy}, with Σyy\Sigma_{yy} invertible. Then

    x∣y  ∼  N(μx∣y, Σx∣y),x \mid y \;\sim\; \mathcal N\big(\mu_{x \mid y},\, \Sigma_{x \mid y}\big),
    μx∣y=μx+ΣxyΣyy−1(y−μy),\mu_{x \mid y} = \mu_x + \Sigma_{xy}\Sigma_{yy}^{-1}(y - \mu_y),
    Σx∣y=Σxx−ΣxyΣyy−1Σyx.\Sigma_{x \mid y} = \Sigma_{xx} - \Sigma_{xy}\Sigma_{yy}^{-1}\Sigma_{yx}.

    The covariance is the Schur complement of Σyy\Sigma_{yy} in the joint covariance (linear algebra 07).

    Proof.

    1. Let G=ΣxyΣyy−1G = \Sigma_{xy}\Sigma_{yy}^{-1} and z=x−Gyz = x - Gy.
    2. Cov⁡(z,y)=Σxy−GΣyy=0\operatorname{Cov}(z, y) = \Sigma_{xy} - G\Sigma_{yy} = 0. The pair (z,y)(z, y) is a linear map of (x,y)(x, y), so it is jointly Gaussian. For jointly Gaussian variables, zero covariance means independence. So zz and yy are independent.
    3. So the law of zz given yy is the same as without yy: mean μx−Gμy\mu_x - G\mu_y and covariance Σxx−GΣyx\Sigma_{xx} - G\Sigma_{yx}. (Expand Cov⁡(x−Gy)\operatorname{Cov}(x - Gy) and use GΣyyG⊤=ΣxyG⊤=GΣyxG\Sigma_{yy}G^\top = \Sigma_{xy}G^\top = G\Sigma_{yx}.)
    4. x=z+Gyx = z + Gy, and GyGy is a constant once yy is known. Add it to the mean. ■\blacksquare

    The Kalman update #

    Theorem 2 (Kalman update). Prior x∼N(xf,Pf)x \sim \mathcal N(x^f, P^f). Observation y=Hx+vy = Hx + v with v∼N(0,R)v \sim \mathcal N(0, R) independent of xx. Then x∣y∼N(xa,Pa)x \mid y \sim \mathcal N(x^a, P^a) with

    S=HPfH⊤+R,K=PfH⊤S−1,S = HP^fH^\top + R, \qquad K = P^fH^\top S^{-1},
    xa=xf+K (y−Hxf),x^a = x^f + K\,(y - Hx^f),
    Pa=(I−KH) Pf.P^a = (I - KH)\,P^f .

    SS is the innovation covariance and KK the Kalman gain.

    Proof.

    1. (x,y)(x, y) is a linear map of the independent Gaussians (x,v)(x, v), so it is jointly Gaussian.
    2. Compute the blocks. E[y]=Hxf\mathbb E[y] = Hx^f. Cov⁡(y)=HPfH⊤+R=S\operatorname{Cov}(y) = HP^fH^\top + R = S. Cov⁡(x,y)=E[(x−xf)(H(x−xf)+v)⊤]=PfH⊤\operatorname{Cov}(x, y) = \mathbb E\big[(x - x^f)(H(x - x^f) + v)^\top\big] = P^fH^\top, because vv has mean 00 and is independent of xx.
    3. Theorem 1 with Σxy=PfH⊤\Sigma_{xy} = P^fH^\top and Σyy=S\Sigma_{yy} = S gives the mean xf+PfH⊤S−1(y−Hxf)x^f + P^fH^\top S^{-1}(y - Hx^f) and the covariance Pf−PfH⊤S−1HPf=(I−KH)PfP^f - P^fH^\top S^{-1}HP^f = (I - KH)P^f. ■\blacksquare

    For one variable (n=p=1n = p = 1, H=1H = 1) this is exactly the formula of section 2. Three consequences:

    • An observation never increases the uncertainty. Pf−Pa=KSK⊤P^f - P^a = KSK^\top, which is positive semidefinite.
    • KK and PaP^a do not depend on the observed value yy. They can be computed before the data arrive.
    • The cross-covariance moves unobserved variables. Row ii of K=PfH⊤S−1K = P^fH^\top S^{-1} is nonzero only if variable ii is correlated (in the forecast) with what HH observes.
    Information form, Woodbury and the Joseph form

    Information form (for invertible PfP^f and RR):

    (Pa)−1=(Pf)−1+H⊤R−1H,xa=Pa((Pf)−1xf+H⊤R−1y),K=PaH⊤R−1.\begin{aligned} (P^a)^{-1} &= (P^f)^{-1} + H^\top R^{-1}H, \\ x^a &= P^a\big((P^f)^{-1}x^f + H^\top R^{-1}y\big), \\ K &= P^aH^\top R^{-1}. \end{aligned}

    Reason: −log⁡p(x∣y)=12(x−xf)⊤(Pf)−1(x−xf)+12(y−Hx)⊤R−1(y−Hx)+const-\log p(x \mid y) = \tfrac12 (x - x^f)^\top (P^f)^{-1}(x - x^f) + \tfrac12 (y - Hx)^\top R^{-1}(y - Hx) + \text{const} is quadratic in xx. Its Hessian is the inverse posterior covariance, and its minimiser is the posterior mean. The Woodbury identity ((Pf)−1+H⊤R−1H)−1=Pf−PfH⊤(HPfH⊤+R)−1HPf\big((P^f)^{-1} + H^\top R^{-1}H\big)^{-1} = P^f - P^fH^\top(HP^fH^\top + R)^{-1}HP^f turns one form into the other. The covariance form solves a p×pp \times p system; the information form an n×nn \times n system. Use the smaller one.

    Joseph form. In floating point, (I−KH)Pf(I - KH)P^f can lose symmetry and positive definiteness over many cycles. The Joseph form (I−KH)Pf(I−KH)⊤+KRK⊤(I - KH)P^f(I - KH)^\top + KRK^\top is symmetric positive semidefinite for every KK, and it equals PaP^a when KK is the Kalman gain. It is also the covariance of the analysis error xa−xx^a - x for any gain KK; minimising its trace over KK gives the Kalman gain again.

    The Kalman filter #

    Start from x0ax^a_0, P0aP^a_0. For k=1,2,…k = 1, 2, \dots:

    • Forecast step: xkf=Mxk−1ax^f_k = M x^a_{k-1} and Pkf=MPk−1aM⊤+QP^f_k = M P^a_{k-1} M^\top + Q.
    • Analysis step: Theorem 2 with prior N(xkf,Pkf)\mathcal N(x^f_k, P^f_k) and observation yky_k, which gives xkax^a_k and PkaP^a_k.

    Keep the time indices straight: xkfx^f_k uses the observations y1:k−1y_{1:k-1}, and xkax^a_k uses y1:ky_{1:k}.

    Theorem 3 (the Kalman filter is exact). For the linear-Gaussian model, p(xk∣y1:k)=N(xka,Pka)p(x_k \mid y_{1:k}) = \mathcal N(x^a_k, P^a_k) for every kk, and p(xk∣y1:k−1)=N(xkf,Pkf)p(x_k \mid y_{1:k-1}) = \mathcal N(x^f_k, P^f_k).

    Proof by induction on k
    1. At k=0k = 0 there are no data, and p(x0)=N(x0a,P0a)p(x_0) = \mathcal N(x^a_0, P^a_0).
    2. Forecast. Assume xk−1∣y1:k−1∼N(xk−1a,Pk−1a)x_{k-1} \mid y_{1:k-1} \sim \mathcal N(x^a_{k-1}, P^a_{k-1}). The noise wkw_k is independent of xk−1x_{k-1} and of y1:k−1y_{1:k-1}. So, given y1:k−1y_{1:k-1}, xk=Mxk−1+wkx_k = Mx_{k-1} + w_k is a linear map of independent Gaussians: Gaussian with mean Mxk−1aMx^a_{k-1} and covariance MPk−1aM⊤+QMP^a_{k-1}M^\top + Q.
    3. Analysis. yk=Hxk+vky_k = Hx_k + v_k, and vkv_k is independent of xkx_k and of y1:k−1y_{1:k-1}. So, given y1:k−1y_{1:k-1}, the pair (xk,yk)(x_k, y_k) is exactly the setting of Theorem 2 with prior N(xkf,Pkf)\mathcal N(x^f_k, P^f_k). Conditioning on yky_k gives p(xk∣y1:k)p(x_k \mid y_{1:k}). ■\blacksquare

    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 PP, an n×nn \times n matrix: 101610^{16} numbers for n=108n = 10^8. Computing MPM⊤MPM^\top needs about 2n2n 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: p(xk∣y1:K)p(x_k \mid y_{1:K}) for k<Kk < K. For the linear-Gaussian model, run the filter forward and store xkf,Pkf,xka,Pkax^f_k, P^f_k, x^a_k, P^a_k. Then go backward from xKs=xKax^s_K = x^a_K, PKs=PKaP^s_K = P^a_K:

    Gk=PkaM⊤(Pk+1f)−1,xks=xka+Gk (xk+1s−xk+1f),Pks=Pka+Gk (Pk+1s−Pk+1f) Gk⊤.\begin{aligned} G_k &= P^a_k M^\top (P^f_{k+1})^{-1}, \\ x^s_k &= x^a_k + G_k\,(x^s_{k+1} - x^f_{k+1}), \\ P^s_k &= P^a_k + G_k\,(P^s_{k+1} - P^f_{k+1})\,G_k^\top . \end{aligned}

    This is the RTS smoother (Särkkä and Svensson, §12.2). It gives the exact p(xk∣y1:K)p(x_k \mid y_{1:K}); 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 NN model states x(1),…,x(N)x^{(1)}, \dots, x^{(N)}, the ensemble members; typically NN is 20 to 100. Let xˉ\bar x be their mean, and let A∈Rn×NA \in \R^{n \times N} be the anomaly matrix with columns x(i)−xˉx^{(i)} - \bar x. The sample covariance Pf=AA⊤/(N−1)P^f = AA^\top/(N - 1) 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 εi∼N(0,R)\varepsilon_i \sim \mathcal N(0, R) and update each member with its own perturbed observation y+εiy + \varepsilon_i:
    xa(i)=x(i)+K(y+εi−Hx(i)),x^{(i)}_a = x^{(i)} + K\big(y + \varepsilon_i - Hx^{(i)}\big),
    K=PfH⊤(HPfH⊤+R)−1.K = P^fH^\top(HP^fH^\top + R)^{-1}.

    In a large model, never form PfP^f. Use PfH⊤=A (HA)⊤/(N−1)P^fH^\top = A\,(HA)^\top/(N - 1), where HAHA (size p×Np \times N) holds the anomalies in observation space.

    Why perturb the observations. Take a linear HH and write aia_i for a forecast anomaly. The analysis anomaly is (I−KH) ai+Kεi′(I - KH)\,a_i + K\varepsilon_i', where εi′\varepsilon_i' is the perturbation minus the mean perturbation. Its expected covariance is

    (I−KH)Pf(I−KH)⊤+KRK⊤=(I−KH)Pf,\begin{aligned} &(I - KH)P^f(I - KH)^\top + KRK^\top \\ &\quad = (I - KH)P^f , \end{aligned}

    the correct analysis covariance (the Joseph form above). Without the perturbations the term KRK⊤KRK^\top 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, N→∞N \to \infty: 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. K=PfH⊤S−1=A CK = P^fH^\top S^{-1} = A\,C with C=(HA)⊤S−1/(N−1)C = (HA)^\top S^{-1}/(N - 1). So every column of KK, and every increment xa(i)−x(i)x^{(i)}_a - x^{(i)}, is a combination of the columns of AA. The anomalies sum to zero (A1=0A\mathbf 1 = 0), so their span has dimension at most N−1N - 1. With N=50N = 50 and n=108n = 10^8, the analysis can change the state in only 49 directions.
    Quick check

    An EnKF has N=20N = 20 members for a model with n=40n = 40 variables (Lorenz-96). What is the largest possible rank of the sample covariance PfP^f?

    Sampling error and localisation. For Gaussian data, the sample correlation of two uncorrelated variables has a standard deviation of about 1/N−11/\sqrt{N - 1}: 0.330.33 for N=10N = 10. 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 PfP^f by ρ∘Pf\rho \circ P^f (the elementwise product), with ρjl=ρ(djl/c)\rho_{jl} = \rho(d_{jl}/c), where djld_{jl} is the distance between points jj and ll, and cc is a length scale. The taper ρ\rho 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 2c2c. By the Schur product theorem, ρ∘Pf\rho \circ P^f is positive semidefinite when ρ\rho and PfP^f are. It usually has full rank, so the analysis is no longer limited to N−1N - 1 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 KK: 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

    x(i)←xˉ+λ (x(i)−xˉ),x^{(i)} \leftarrow \bar x + \lambda\,(x^{(i)} - \bar x),

    with λ\lambda about 1.01 to 1.1, multiplies PfP^f by λ2\lambda^2. Additive inflation adds random perturbations, and relaxation methods (RTPS, RTPP) pull the analysis spread back toward the forecast spread. Inflation changes the size of PfP^f, 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.

    Interactive figure: loading…

    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 R=2R = 2 (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: N=3N = 3; perturbed observations off with λ=1\lambda = 1; and off with λ=1.15\lambda = 1.15. Watch for filter divergence: the spread falls far below the error.

    Quick check

    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?

    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 (I−KH)Pf(I - KH)P^f exactly, with no random numbers. Let X=A/N−1X = A/\sqrt{N - 1} (so Pf=XX⊤P^f = XX^\top) and Y=HXY = HX. The ensemble transform Kalman filter (ETKF; Hunt, Kostelich and Szunyogh 2007) works in the NN-dimensional space of ensemble weights:

    P~a=(IN+Y⊤R−1Y)−1,xˉa=xˉ+XP~aY⊤R−1(y−Hxˉ),Xa=X (P~a)1/2,\begin{aligned} \tilde P^a &= \big(I_N + Y^\top R^{-1} Y\big)^{-1}, \\ \bar x^a &= \bar x + X\tilde P^aY^\top R^{-1}\big(y - H\bar x\big), \\ X^a &= X\,(\tilde P^a)^{1/2}, \end{aligned}

    with the symmetric square root. Then Xa(Xa)⊤=XP~aX⊤=(I−KH)PfX^a(X^a)^\top = X\tilde P^aX^\top = (I - KH)P^f, and the new anomalies still sum to zero. The cost is dominated by N×NN \times N 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 N(xb,B)\mathcal N(x^b, B), observation y=h(x)+vy = h(x) + v with v∼N(0,R)v \sim \mathcal N(0, R), and a possibly nonlinear observation operator hh. Bayes' rule gives −log⁡p(x∣y)=J(x)+const-\log p(x \mid y) = J(x) + \text{const} with

    J(x)=12 (x−xb)⊤B−1(x−xb)+12 (y−h(x))⊤R−1(y−h(x)).\begin{aligned} J(x) = {}& \tfrac12\,(x - x^b)^\top B^{-1}(x - x^b) \\ &+ \tfrac12\,\big(y - h(x)\big)^\top R^{-1}\big(y - h(x)\big). \end{aligned}

    The first term is the background term JbJ_b, the second the observation term JoJ_o. 3D-Var computes the MAP estimate arg⁡min⁡xJ(x)\arg\min_x J(x) with a quasi-Newton or conjugate-gradient method. It is Tikhonov regularization with weighted norms (lesson 04).

    For linear h(x)=Hxh(x) = Hx, the 3D-Var minimum is the Kalman analysis mean. JJ is quadratic. Set its gradient to zero: ∇J=B−1(x−xb)−H⊤R−1(y−Hx)=0\nabla J = B^{-1}(x - x^b) - H^\top R^{-1}(y - Hx) = 0, so (B−1+H⊤R−1H) x=B−1xb+H⊤R−1y(B^{-1} + H^\top R^{-1}H)\,x = B^{-1}x^b + H^\top R^{-1}y. This is the information form of the Kalman mean with xf=xbx^f = x^b, Pf=BP^f = B. The Hessian B−1+H⊤R−1HB^{-1} + H^\top R^{-1}H is (Pa)−1(P^a)^{-1}. (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: xj+1=M(xj)x_{j+1} = \mathcal M(x_j), where M\mathcal M is one (possibly nonlinear) time step and j=0,…,sj = 0, \dots, s counts the steps. Then every state xjx_j is a function of the initial state x0x_0, and x0x_0 is the only control variable. Observations yjy_j exist at the steps jj in a set O\mathcal O:

    J(x0)=12 (x0−xb)⊤B−1(x0−xb)+12∑j∈O(yj−h(xj))⊤R−1(yj−h(xj)).\begin{aligned} J(x_0) = {}& \tfrac12\,(x_0 - x^b)^\top B^{-1}(x_0 - x^b) \\ &+ \tfrac12\sum_{j \in \mathcal O}\big(y_j - h(x_j)\big)^\top R^{-1}\big(y_j - h(x_j)\big). \end{aligned}

    JJ is −log⁡p(x0∣all yj)-\log p(x_0 \mid \text{all } y_j) up to a constant, so arg⁡min⁡J\arg\min J is the MAP initial state. The forecast then starts from this analysis.

    Theorem 4 (4D-Var gradient by the adjoint). Let Mj=∂M/∂xM_j = \partial\mathcal M/\partial x at xjx_j (the tangent-linear model) and HjH_j the Jacobian of hh at xjx_j. Set fj=−Hj⊤R−1(yj−h(xj))f_j = -H_j^\top R^{-1}\big(y_j - h(x_j)\big) for j∈Oj \in \mathcal O, and fj=0f_j = 0 otherwise. Run the adjoint recursion backward in time, with the adjoint state λj∈Rn\lambda_j \in \R^n:

    λs=fs,λj=Mj⊤λj+1+fj\lambda_s = f_s, \qquad \lambda_j = M_j^\top\lambda_{j+1} + f_j

    for j=s−1,…,0j = s - 1, \dots, 0.

    Then ∇J(x0)=B−1(x0−xb)+λ0\nabla J(x_0) = B^{-1}(x_0 - x^b) + \lambda_0.

    Proof.

    1. Write the observation term as Jo=∑j∈Oϕj(xj)J_o = \sum_{j \in \mathcal O} \phi_j(x_j) with ϕj(x)=12(yj−h(x))⊤R−1(yj−h(x))\phi_j(x) = \tfrac12 (y_j - h(x))^\top R^{-1}(y_j - h(x)). Then ∇ϕj(xj)=fj\nabla\phi_j(x_j) = f_j.
    2. xjx_j depends on x0x_0 through jj model steps. By the chain rule, its Jacobian is Mj−1⋯M1M0M_{j-1}\cdots M_1M_0. So ∇x0 ϕj(xj)=M0⊤M1⊤⋯Mj−1⊤fj\nabla_{x_0}\,\phi_j(x_j) = M_0^\top M_1^\top \cdots M_{j-1}^\top f_j.
    3. Let λj\lambda_j be the gradient, with respect to xjx_j, of all observation terms at steps ≥j\ge j. The terms at steps >j> j depend on xjx_j only through xj+1=M(xj)x_{j+1} = \mathcal M(x_j), so their gradient is Mj⊤λj+1M_j^\top\lambda_{j+1}. The term at step jj adds fjf_j. This is the recursion. At j=0j = 0 it gives λ0=∇x0Jo\lambda_0 = \nabla_{x_0} J_o. The recursion shares the products of step 2 between all observation times.
    4. Add the gradient of the background term, B−1(x0−xb)B^{-1}(x_0 - x^b). ■\blacksquare

    Cost. One forward run (store x0,…,xsx_0, \dots, x_s, 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 nn. Finite differences need n+1n + 1 forward runs. In ML terms, each step λj=Mj⊤λj+1\lambda_j = M_j^\top\lambda_{j+1} is a vector–Jacobian product of reverse-mode AD. If M\mathcal M is one RK4 step, Mj⊤λM_j^\top\lambda 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.

    Interactive figure: loading…

    Press "Gradient step" a few times. The square plane is the space of the control variable x0x_0 = (initial position, initial velocity). Grey curves are contours of JJ; the blue point is the background xbx^b, the grey point the truth, and the orange point the current x0x_0 (drag it anywhere). The violet arrow is the next step along −∇J-\nabla J, and ∇J\nabla J 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 m=1m = 1: 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 (Q=0Q = 0): JJ 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 JJ (its inverse is the posterior covariance in the linear case) or an ensemble.
    • Window length. In a chaotic model, small changes of x0x_0 grow like eλ1te^{\lambda_1 t}, where λ1\lambda_1 is the largest Lyapunov exponent (lesson 05). So J(x0)J(x_0) 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 x0x_0. Each inner loop minimises a quadratic cost for the increment δx0\delta x_0, built with the tangent-linear model MjM_j and its adjoint Mj⊤M_j^\top, often at a lower resolution. The quadratic inner problem is solved with conjugate gradients, preconditioned by B1/2B^{1/2} (the control variable is B−1/2δx0B^{-1/2}\delta x_0).

    Weak-constraint 4D-Var drops the perfect-model assumption. It adds model-error terms 12∑jηj⊤Q−1ηj\tfrac12\sum_j \eta_j^\top Q^{-1}\eta_j with xj+1=M(xj)+ηjx_{j+1} = \mathcal M(x_j) + \eta_j, so the control variable is the whole trajectory, not only x0x_0. This allows longer windows.

    Ensembles inside 4D-Var. The background covariance BB 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) K=4/5K = 4/5, so xa=0.8⋅5=4x^a = 0.8 \cdot 5 = 4 and Pa=0.2⋅4=0.8P^a = 0.2 \cdot 4 = 0.8. With y=50y = 50 the mean is 4040, but the variance is still 0.80.8: in the linear-Gaussian case the posterior variance does not depend on the data. (A real system would also check whether y=50y = 50 is a gross error before using it.) (2) The estimate of x2x_2 also increases, by K2(y−x1f)K_2(y - x^f_1) with K2=P21f/S>0K_2 = P^f_{21}/S > 0. Its variance also falls. (3) Finite differences: n+1n + 1 runs (forward differences) or 2n2n (central), so about 10810^8. Adjoint: one forward run plus one adjoint run, whatever nn is.

    4. Worked example #

    (a) A Kalman update in two variables. Two neighbouring grid points have forecast temperature anomalies xf=(0,0)x^f = (0, 0) with Pf=(2112)P^f = \begin{pmatrix} 2 & 1 \\ 1 & 2 \end{pmatrix} (correlation 1/2⋅2=0.51/\sqrt{2 \cdot 2} = 0.5). A station observes point 1 only: H=(1    0)H = (1\;\;0), R=1R = 1, y=3y = 3.

    1. HPf=(2    1)HP^f = (2\;\;1) (the first row of PfP^f), and HPfH⊤=2HP^fH^\top = 2. So S=2+1=3S = 2 + 1 = 3.
    2. PfH⊤=(2,1)⊤P^fH^\top = (2, 1)^\top (the first column of PfP^f). So K=(2,1)⊤/3=(2/3, 1/3)⊤K = (2, 1)^\top/3 = (2/3,\ 1/3)^\top.
    3. The innovation is y−Hxf=3−0=3y - Hx^f = 3 - 0 = 3. So xa=xf+3K=(2,1)x^a = x^f + 3K = (2, 1). The unobserved point 2 moves by 11, through the correlation.
    4. KH=(2/301/30)KH = \begin{pmatrix} 2/3 & 0 \\ 1/3 & 0 \end{pmatrix}, so I−KH=(1/30−1/31)I - KH = \begin{pmatrix} 1/3 & 0 \\ -1/3 & 1 \end{pmatrix}. Row 1 of Pa=(I−KH)PfP^a = (I - KH)P^f is 13(2,1)=(2/3,1/3)\tfrac13 (2, 1) = (2/3, 1/3). Row 2 is −13(2,1)+(1,2)=(1/3,5/3)-\tfrac13(2, 1) + (1, 2) = (1/3, 5/3). So Pa=(2/31/31/35/3)P^a = \begin{pmatrix} 2/3 & 1/3 \\ 1/3 & 5/3 \end{pmatrix}. The variance at point 1 drops from 2 to 2/32/3; at point 2 from 2 to 5/35/3.
    5. Check with the information form. (Pf)−1=13(2−1−12)(P^f)^{-1} = \frac13\begin{pmatrix} 2 & -1 \\ -1 & 2 \end{pmatrix} and H⊤R−1H=(1000)H^\top R^{-1}H = \begin{pmatrix} 1 & 0 \\ 0 & 0 \end{pmatrix}, so their sum is (5/3−1/3−1/32/3)\begin{pmatrix} 5/3 & -1/3 \\ -1/3 & 2/3 \end{pmatrix}. Its determinant is 10/9−1/9=110/9 - 1/9 = 1, and its inverse is (2/31/31/35/3)=Pa\begin{pmatrix} 2/3 & 1/3 \\ 1/3 & 5/3 \end{pmatrix} = P^a. ✓
    6. 3D-Var with xb=xfx^b = x^f and B=PfB = P^f has the Hessian (Pf)−1+H⊤R−1H=(Pa)−1(P^f)^{-1} + H^\top R^{-1}H = (P^a)^{-1}, and its gradient at (2,1)(2, 1) is B−1(2,1)⊤−H⊤(3−2)=(1,0)⊤−(1,0)⊤=0B^{-1}(2, 1)^\top - H^\top(3 - 2) = (1, 0)^\top - (1, 0)^\top = 0. So the 3D-Var minimum is (2,1)=xa(2, 1) = x^a. ✓ (Here B−1(2,1)⊤=13(4−1, −2+2)⊤=(1,0)⊤B^{-1}(2, 1)^\top = \frac13(4 - 1,\ -2 + 2)^\top = (1, 0)^\top.)

    (b) Why the stochastic EnKF perturbs the observations (one variable, H=1H = 1). Let Pf=1P^f = 1 and R=1R = 1, so K=1/2K = 1/2. A forecast anomaly aa becomes (1−K)a+Kε′(1 - K)a + K\varepsilon'.

    • With perturbed observations, the expected analysis variance is (1−K)2Pf+K2R=0.25+0.25=0.5(1 - K)^2P^f + K^2R = 0.25 + 0.25 = 0.5. This equals (1−K)Pf=0.5(1 - K)P^f = 0.5, the KF value. ✓
    • Without them it is (1−K)2Pf=0.25(1 - K)^2P^f = 0.25: 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 xj+1=2xjx_{j+1} = 2x_j with no model error. Background xb=0x^b = 0, B=1B = 1. One observation y1=2y_1 = 2 at step 1 with R=1R = 1 and h(x)=xh(x) = x.

    1. x1=2x0x_1 = 2x_0, so J(x0)=12x02+12(2−2x0)2J(x_0) = \tfrac12 x_0^2 + \tfrac12 (2 - 2x_0)^2.
    2. Adjoint: f1=−(y1−x1)=−(2−2x0)f_1 = -(y_1 - x_1) = -(2 - 2x_0), λ1=f1\lambda_1 = f_1, and λ0=M0⊤λ1+f0=2λ1+0\lambda_0 = M_0^\top\lambda_1 + f_0 = 2\lambda_1 + 0. So ∇J=x0+2(2x0−2)=5x0−4\nabla J = x_0 + 2(2x_0 - 2) = 5x_0 - 4.
    3. Check at x0=0.5x_0 = 0.5: the adjoint gives ∇J=2.5−4=−1.5\nabla J = 2.5 - 4 = -1.5. Direct differentiation of JJ gives x0−2(2−2x0)=0.5−2=−1.5x_0 - 2(2 - 2x_0) = 0.5 - 2 = -1.5. ✓
    4. Minimum: 5x0−4=05x_0 - 4 = 0 gives x0∗=0.8x_0^\ast = 0.8. The Hessian is 55, so the posterior variance of x0x_0 is 1/5=0.21/5 = 0.2.
    5. Move it to step 1: x1=2⋅0.8=1.6x_1 = 2 \cdot 0.8 = 1.6, with variance 22⋅0.2=0.82^2 \cdot 0.2 = 0.8.
    6. Kalman filter: forecast x1f=2⋅0=0x^f_1 = 2 \cdot 0 = 0, P1f=22⋅1+0=4P^f_1 = 2^2 \cdot 1 + 0 = 4. Then K=4/5K = 4/5, x1a=0+0.8⋅2=1.6x^a_1 = 0 + 0.8 \cdot 2 = 1.6, P1a=0.2⋅4=0.8P^a_1 = 0.2 \cdot 4 = 0.8. The same. ✓
    Quick check

    In worked example (c), the observation is y1=4y_1 = 4 instead of 2 (everything else is the same). What is the 4D-Var analysis x0∗x_0^\ast?

    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 N−1=9N - 1 = 9. Its relative error (1.5) is larger than the error of the zero matrix (1.0). The far correlations are pure noise of size 1/N−11/\sqrt{N - 1}. 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 SS, and letting (I−KH)P(I - KH)P drift away from symmetric positive semidefinite over many cycles. Symmetrise, or use the Joseph form.
    • Mixing up the time indices. xkfx^f_k is the forecast for time kk made from xk−1ax^a_{k-1}; the observation yky_k updates xkfx^f_k into xkax^a_k. Updating with yk−1y_{k-1} or forecasting from xk−1fx^f_{k-1} 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.
    • 1/N1/N instead of 1/(N−1)1/(N - 1) in the sample covariance. With N=10N = 10 this is a 10% error.
    • Expecting inflation to fix N≪nN \ll n. 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 PaP^a 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 j=0j = 0, or using the continuous adjoint and expecting it to match finite differences of the discrete cost to round-off (lesson 03).

    8. Check yourself #

    1. Prior N(10,4)\mathcal N(10, 4), observation y=13y = 13 with R=2R = 2, H=1H = 1. Give KK, xax^a and PaP^a.
      AnswerK=4/6=2/3K = 4/6 = 2/3, xa=10+23⋅3=12x^a = 10 + \tfrac23 \cdot 3 = 12, Pa=13⋅4=4/3P^a = \tfrac13 \cdot 4 = 4/3. Check with precisions: 1/4+1/2=3/4=1/Pa1/4 + 1/2 = 3/4 = 1/P^a.
    2. Show that Pa⪯PfP^a \preceq P^f in Theorem 2: an observation never increases the uncertainty.
      AnswerPf−Pa=PfH⊤S−1HPf=KSK⊤P^f - P^a = P^fH^\top S^{-1}HP^f = KSK^\top, because KSK⊤=PfH⊤S−1SS−1HPfKSK^\top = P^fH^\top S^{-1}SS^{-1}HP^f. Since S≻0S \succ 0, z⊤KSK⊤z=(K⊤z)⊤S (K⊤z)≥0z^\top KSK^\top z = (K^\top z)^\top S\,(K^\top z) \ge 0 for every zz.
    3. Your EnKF has N=40N = 40 members for a model with n=106n = 10^6 variables. Why can the analysis increment lie in only 39 directions, and why does localisation help where inflation does not?
      AnswerK=PfH⊤S−1K = P^fH^\top S^{-1} with Pf=AA⊤/(N−1)P^f = AA^\top/(N-1), so K=ACK = AC: every increment is a combination of the anomalies, which sum to zero (rank ≤N−1=39\le N - 1 = 39). Inflation multiplies the anomalies by λ\lambda: same span, same correlations. Localisation ρ∘Pf\rho \circ P^f removes the spurious long-range correlations (of size about 1/N−1≈0.161/\sqrt{N-1} \approx 0.16) and gives a covariance that usually has full rank, so the analysis can act locally in many more directions.
    4. 4D-Var with the scalar model xj+1=3xjx_{j+1} = 3x_j, xb=1x^b = 1, B=1B = 1, and one observation y=0y = 0 at step 1 with R=9R = 9, h(x)=xh(x) = x. (a) Compute ∇J(1)\nabla J(1) with the adjoint recursion. (b) Find the analysis x0∗x_0^\ast and its posterior variance.
      Answer(a) x1=3x_1 = 3, f1=−(0−3)/9=1/3=λ1f_1 = -(0 - 3)/9 = 1/3 = \lambda_1, λ0=3λ1=1\lambda_0 = 3\lambda_1 = 1, so ∇J(1)=(1−1)+1=1\nabla J(1) = (1 - 1) + 1 = 1. (b) J=12(x0−1)2+12(3x0)2/9=12(x0−1)2+12x02J = \tfrac12(x_0 - 1)^2 + \tfrac12 (3x_0)^2/9 = \tfrac12(x_0 - 1)^2 + \tfrac12 x_0^2, so J′=2x0−1J' = 2x_0 - 1 (equal to 1 at x0=1x_0 = 1 ✓). Then x0∗=1/2x_0^\ast = 1/2, and the variance is 1/J′′=1/21/J'' = 1/2.
    5. For a nonlinear model, why is the EnKF forecast step often better than the extended Kalman filter (EKF), which propagates PP with the tangent-linear model?
      AnswerThe EnKF runs the full nonlinear model for each member. It needs no tangent-linear or adjoint code, it needs NN runs instead of about nn, and the ensemble feels the nonlinear evolution of the mean and the spread. The EKF needs about nn tangent-linear runs to propagate PP, and its linearisation error grows with the forecast length.
    6. Why does strong-constraint 4D-Var become hard when the window is long in a chaotic model?
      AnswerPerturbations of x0x_0 grow like eλ1te^{\lambda_1 t}, where λ1\lambda_1 is the largest Lyapunov exponent. So J(x0)J(x_0) 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.
    Recall cardsAnswer in your head first, then reveal. Add the cards to your review deck to see them again just before you would forget.
    1. Give the Kalman gain for prior covariance PfP^f, observation operator HH and observation-error covariance RR.

    2. Give the analysis mean and covariance of the Kalman update.

    3. Scalar case: Pf=4P^f = 4, R=1R = 1, xf=0x^f = 0, y=5y = 5. Give KK, xax^a and PaP^a.

    4. Write the forecast step of the Kalman filter.

    5. Why does observing x1x_1 change the estimate of the unobserved x2x_2?

    6. Why does the stochastic EnKF perturb the observations?

    7. What is the largest rank of the EnKF sample covariance with NN members, and why?

    8. What does localisation do in an EnKF?

    9. What does multiplicative inflation fix, and what does it not fix?

    10. How is the 4D-Var gradient computed, and what does it cost?

    Open the review →

    9. Interview angle #

    • "Derive the Kalman gain." Gaussian conditioning in three lines (Theorem 2). Another route: the KK that minimises the trace of the Joseph-form covariance (I−KH)P(I−KH)⊤+KRK⊤(I - KH)P(I - KH)^\top + KRK^\top.
    • "KF, EKF, EnKF, particle filter, 4D-Var: when do you use which?" KF: linear-Gaussian, small nn. EKF: mildly nonlinear, small nn. EnKF: large nonlinear models, near-Gaussian errors, no adjoint needed. Particle filter: strongly non-Gaussian, small nn. 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:

    1. Write the Kalman update and check it against Bayes' rule on a grid for a two-variable Gaussian.
    2. 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.
    3. Write the 3D-Var cost and gradient, minimise it, and show that the minimum is the Kalman analysis.
    4. 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.
    5. Write the strong-constraint 4D-Var gradient with the adjoint recursion for Lorenz-63 and check it with finite differences.

    11. Watch and read #

    The forecast and update steps as Gaussian pictures; a good first view before section 2.
    Open on YouTube · the player loads from youtube-nocookie.com when you press play
    The Kalman filter as an optimal state estimator from a control point of view.
    Open on YouTube · the player loads from youtube-nocookie.com when you press play
    A lecture on 3D-Var, 4D-Var and the EnKF for Earth-system models; it matches sections 3 and 6.
    Open on YouTube · the player loads from youtube-nocookie.com when you press play
    A full university lecture; watch the parts on 3D-Var and 4D-Var and the equivalence with the Kalman analysis.
    Open on YouTube · the player loads from youtube-nocookie.com when you press play