Kalman Smoother (RTS Smoother)

April 4, 2026 · View on GitHub

Overview & Motivation

The Kalman filter is a causal estimator: its state estimate at time tt uses only observations up to and including time tt. When the full observation sequence is available (offline/batch setting), the Rauch-Tung-Striebel (RTS) smoother refines every filtered estimate by incorporating future measurements. The result is the minimum-mean-square-error (MMSE) state estimate given the entire sequence z1:Tz_{1:T}.

The RTS smoother is the prerequisite for the Expectation-Maximization (EM) algorithm for Kalman Filter parameter identification: the E-step requires smoothed means, smoothed covariances, and the lag-1 cross-covariance Pt,t1TP_{t,t-1|T} in order to form the sufficient statistics used by the M-step.

Mathematical Theory

State-Space Model

xt+1=Fxt+wt,wtN(0,Q)x_{t+1} = F x_t + w_t, \quad w_t \sim \mathcal{N}(0, Q) zt=Hxt+vt,vtN(0,R)z_t = H x_t + v_t, \quad v_t \sim \mathcal{N}(0, R)

with x0N(μ0,P0)x_0 \sim \mathcal{N}(\mu_0, P_0).

Forward Pass (Kalman Filter)

For t=0,1,,T1t = 0, 1, \ldots, T-1:

Initialise (t = 0 only):

x^0=μ0,P0=P0\hat{x}_{0|-} = \mu_0, \quad P_{0|-} = P_0

Update:

St=HPtH+RS_t = H P_{t|-} H^\top + R Kt=PtHSt1K_t = P_{t|-} H^\top S_t^{-1} x^tt=x^t+Kt(ztHx^t)\hat{x}_{t|t} = \hat{x}_{t|-} + K_t (z_t - H \hat{x}_{t|-}) Ptt=(IKtH)Pt(IKtH)+KtRKt(Joseph form)P_{t|t} = (I - K_t H) P_{t|-} (I - K_t H)^\top + K_t R K_t^\top \quad \text{(Joseph form)}

Log-likelihood contribution:

t=12(logdetSt+(ztHx^t)St1(ztHx^t)+mlog2π)\ell_t = -\tfrac{1}{2}\bigl(\log\det S_t + (z_t - H\hat{x}_{t|-})^\top S_t^{-1} (z_t - H\hat{x}_{t|-}) + m\log 2\pi\bigr)

where mm = MeasurementSize. The total is =t=0T1t\ell = \sum_{t=0}^{T-1} \ell_t.

Predict (for t<T1t < T-1):

x^t+1=Fx^tt,Pt+1=FPttF+Q\hat{x}_{t+1|-} = F \hat{x}_{t|t}, \quad P_{t+1|-} = F P_{t|t} F^\top + Q

Backward Pass (RTS Smoother)

Initialise at t=T1t = T-1:

x^T1T=x^T1T1,PT1T=PT1T1\hat{x}_{T-1|T} = \hat{x}_{T-1|T-1}, \quad P_{T-1|T} = P_{T-1|T-1}

For t=T2,T3,,0t = T-2, T-3, \ldots, 0:

Smoother gain:

Gt=PttFPt+1t1G_t = P_{t|t} F^\top P_{t+1|t}^{-1}

Smoothed mean and covariance:

x^tT=x^tt+Gt(x^t+1Tx^t+1t)\hat{x}_{t|T} = \hat{x}_{t|t} + G_t (\hat{x}_{t+1|T} - \hat{x}_{t+1|t}) PtT=Ptt+Gt(Pt+1TPt+1t)GtP_{t|T} = P_{t|t} + G_t (P_{t+1|T} - P_{t+1|t}) G_t^\top

Lag-1 Cross-Covariance

The EM M-step requires Pt,t1T=Cov(xt,xt1z1:T)P_{t,t-1|T} = \mathrm{Cov}(x_t, x_{t-1} \mid z_{1:T}).

Initialisation (Shumway & Stoffer, 1982):

PT1,T2T=(IKT1H)FPT2T2P_{T-1, T-2|T} = (I - K_{T-1} H) F P_{T-2|T-2}

Recursion for t=T3,,0t = T-3, \ldots, 0:

Pt+1,tT=Pt+1t+1Gt+Gt+1(Pt+2,t+1TFPt+1t+1)GtP_{t+1, t|T} = P_{t+1|t+1} G_t^\top + G_{t+1} (P_{t+2, t+1|T} - F P_{t+1|t+1}) G_t^\top

Note: P0,1TP_{0,-1|T} is undefined; index 0 of lagCrossCovariances is always zero.

Complexity Analysis

CaseTimeSpaceNotes
AllO(TN3)O(T \cdot N^3)O(TN2)O(T \cdot N^2) stackDominant cost: N×NN\times N solves at each step

All storage is stack-allocated via std::array. There is no heap usage.

Step-by-Step Walkthrough

Consider a 1-D constant-position model (N=1N=1, M=1M=1, T=3T=3) with F=1F=1, H=1H=1, Q=0.1Q=0.1, R=1R=1, μ0=0\mu_0=0, P0=1P_0=1, and observations z=[0.5,0.3,0.8]z = [0.5, 0.3, 0.8].

Forward pass:

ttPtP_{t\|-}StS_tKtK_tx^tt\hat{x}_{t\|t}PttP_{t\|t}
01.02.00.5000.2500.500
10.6001.6000.3750.3630.375
20.4751.4750.3220.6200.322

Backward pass (t=1t=1 then t=0t=0):

t=1t=1: G1=0.500/0.600=0.833G_1 = 0.500/0.600 = 0.833; x^13=0.363+0.833(0.6200.363)0.577\hat{x}_{1|3} = 0.363 + 0.833(0.620 - 0.363) \approx 0.577

t=0t=0: G0=0.375/0.375=1.000G_0 = 0.375/0.375 = 1.000; x^03=0.250+1.000(0.5770.363)0.464\hat{x}_{0|3} = 0.250 + 1.000(0.577 - 0.363) \approx 0.464

Notice how the smoothed x^03=0.464\hat{x}_{0|3}=0.464 incorporates all three observations, whereas the filtered x^00=0.250\hat{x}_{0|0}=0.250 only used the first.

Pitfalls & Edge Cases

  • Near-singular Pt+1tP_{t+1|t}: The smoother gain is computed via SolveSystem (Gaussian elimination with partial pivoting) rather than explicit matrix inversion. If the predicted covariance becomes near-singular (possible when Q0Q \approx 0), numerical accuracy degrades. Ensure QQ has positive diagonal entries.
  • Joseph-form update: The standard update P=(IKH)PP = (I-KH)P is numerically unstable for finite-precision arithmetic. This implementation uses the Joseph form to maintain positive semi-definiteness.
  • lagCrossCovariances[0] is always zero: The lag-1 cross-covariance for index 0 (P0,1TP_{0,-1|T}) is undefined. The first valid entry is index 1 (P1,0TP_{1,0|T}).
  • Unsigned loop guard: The backward loop uses std::size_t; the break guard if (t == 0) break prevents unsigned wrap-around.
  • Stack usage: For large NN or TT, the five internal std::array members dominate stack usage. Choose MaxSteps conservatively on resource-constrained targets.

Variants & Generalizations

  • Square-root RTS smoother: Propagates Cholesky factors instead of full covariance matrices for improved numerical conditioning.
  • Information filter smoother: Operates in the dual (information) domain; preferable when observations are dense relative to state transitions.
  • Extended / Unscented smoother: Replace the linear forward pass with EKF or UKF; the backward pass equations remain identical in form.

Applications

  • EM parameter identification: Used as the E-step by ExpectationMaximization to compute the sufficient statistics {A,B,C,D,W,Σzz}\{A, B, C, D, W, \Sigma_{zz}\} for the M-step.
  • Offline trajectory estimation: Position/velocity smoothing in GNSS post-processing and inertial navigation.
  • Batch signal denoising: Any application where the full observation sequence is available before estimation begins.

Connections to Other Algorithms

  • filters::KalmanFilter: The forward pass of KalmanSmoother replicates the KF update equations. The smoother is applied after the filter, not instead of it.
  • estimators::ExpectationMaximization: The smoother is owned and invoked by ExpectationMaximization as its E-step. Users who only need EM should interact with ExpectationMaximization directly.
  • solvers::GaussianElimination: Used for all matrix "division" operations (smoother gain, Kalman gain) to avoid explicit matrix inversion.
  • solvers::CholeskyDecomposition: Used to compute logdetSt\log \det S_t for the log-likelihood.

References & Further Reading

  • Rauch, H. E., Tung, F., & Striebel, C. T. (1965). Maximum likelihood estimates of linear dynamic systems. AIAA Journal, 3(8), 1445–1450.
  • Shumway, R. H., & Stoffer, D. S. (1982). An approach to time series smoothing and forecasting using the EM algorithm. Journal of Time Series Analysis, 3(4), 253–264.
  • Shumway, R. H., & Stoffer, D. S. (2000). Time Series Analysis and Its Applications. Springer. (Chapter 6)
  • Särkkä, S. (2013). Bayesian Filtering and Smoothing. Cambridge University Press. (Chapter 8)