Skip to the lesson
FixTheRobot Explore Robotics / Probability for Robotics / M12 Uncertainty on Manifolds, Error-State Filtering, Smoothing and POMDPs / M12-T3

Probability for Robotics · Theory · M12

M12-T3 — Smoothing: Using Future Measurements

A smoother is a filter that is allowed to wait. How much better it gets depends on how long it waits.

  • Lesson 3 of 4
  • Advanced
  • Module 12: Uncertainty on Manifolds, Error-State Filtering, Smoothing and POMDPs
  • Theory
  • About 14 minutes to read

A filter estimates the state at time t from readings up to t, because a controller cannot wait. Many tasks can: mapping, calibration, logging analysis, even a display that runs a second behind. A smoother uses the readings after t as well, and in exchange for the delay it gives better and more honest estimates. This lesson compares the three forms (filter, fixed-lag, fixed-interval), measures how accuracy depends on the lag, and shows that the largest gains are at the points where the filter is slowest: when the world changes.

Learning objectives

After this lesson, you should be able to:

  • Distinguish filtering, fixed-lag smoothing and fixed-interval smoothing by what data each estimate uses and by its latency.
  • Compute a fixed-lag smoothed estimate from the filter output with a backward recursion over a window.
  • Explain the diminishing returns of lag and choose a lag from the filter’s memory.
  • State when smoothing helps most (manoeuvres, initialisation, bias estimation) and what it costs.

The intuition

Suppose a robot travelling at 1 m/s suddenly reverses at step 60. At step 61 the filter has seen one reading that says “less far than expected”. It adjusts a little, being unsure whether this is a manoeuvre or noise, and takes about six steps (3 s) to get its velocity estimate within 0.5 m/s of the truth. A person looking at the whole record afterwards would put the turn at step 60 immediately: the readings at steps 62, 63, 64 all say “going back”.

Smoothing error against lag, and the reaction to a manoeuvreLeft: the position error (blue) and velocity error (orange) of a fixed-lag smoother against its lag. Position error falls from 0.117 metres with no lag to 0.069 at 5 steps and 0.067 at 20; velocity error falls from 0.068 to 0.041 and 0.039. Most of the gain is in the first five steps. Right: after the robot suddenly reverses its velocity at step 60, the rms velocity error is 0.84 for the filter, 0.57 with lag 2, 0.45 with lag 5 and 0.49 with lag 10; the number of steps until the velocity is within 0.5 metres per second of the truth falls from 5.9 to 4.2, 2.5 and 2.0.0510152000.03250.0650.09750.13lag (steps of 0.5 s)rms error position error (m)velocity error (m/s)0123400.250.50.751lag (steps): 0, 2, 5, 10rms velocity error after the reversal0.8365.9 steps0.5744.2 steps0.4532.5 steps0.4852 stepslabels: steps until within 0.5 m/s
Left: error of a fixed-lag smoother against its lag. Right: the reaction to a reversal for lags of 0, 2, 5 and 10 steps; the labels give the steps until the velocity is within 0.5 m/s of the truth.

That is what a smoother does: to estimate step tt it also uses readings t+1,…,t+Lt+1,\dots,t+L. The left panel measures the benefit on a constant-velocity robot. With no lag (the filter) the position error is 11.7 cm and the velocity error 6.8 cm/s. One step of lag (0.5 s) gives 9.7 cm and 6.0; two steps 8.2 and 5.3; five steps (2.5 s) 6.9 cm and 4.1. After that, nothing: ten steps give 6.8 and 3.9, twenty 6.7 and 3.9. The curve flattens at about the time the filter takes to forget: readings further ahead tell the smoother nothing it does not already know about step tt.

The right panel shows where smoothing matters most. The reversal is a surprise to the model (it assumes a nearly constant velocity). The filter's rms velocity error in the 15 steps around it is 0.84 m/s and it needs 5.9 steps to recover; a smoother with a lag of five steps has 0.45 and needs 2.5 steps, and with ten steps 0.49 and 2.0: the same readings, interpreted with hindsight.

Smoothing buys accuracy with latency. The first few steps of lag are worth a lot, more lag is worth little, and nothing is gained for real-time control, which cannot wait.

Definitions

Filtering
The estimate of xtx_t from z1:tz_{1:t}: p(xt∣z1:t)p(x_t\mid z_{1:t}). Causal, no delay.
Fixed-lag smoothing
p(xt−L∣z1:t)p(x_{t-L}\mid z_{1:t}): the estimate of the state LL steps ago using everything up to now. A constant delay LΔtL\Delta t.
Fixed-interval smoothing
p(xt∣z1:T)p(x_t\mid z_{1:T}) for all t≤Tt\le T: offline, once the whole record is available.
Fixed-point smoothing
p(xs∣z1:t)p(x_s\mid z_{1:t}) for a fixed ss as tt grows: refine the estimate of one moment (an initial state, a calibration) as data arrive.
Smoother gain CtC_t
Pt∣tF⊤Pˉt+1−1P_{t|t}F^\top\bar P_{t+1}^{-1}: how much the later estimate corrects the earlier one.
RTS backward step
x^t∣T=x^t∣t+Ct(x^t+1∣T−x^t+1∣t),Ct=Pt∣tF⊤(FPt∣tF⊤+Q)−1\hat x_{t|T}=\hat x_{t|t}+C_t\bigl(\hat x_{t+1|T}-\hat x_{t+1|t}\bigr),\qquad C_t=P_{t|t}F^\top\bigl(F P_{t|t}F^\top+Q\bigr)^{-1}
Each state is corrected by how much the next smoothed state differs from the filter’s prediction of it.
Smoothed covariance
Pt∣T=Pt∣t+Ct(Pt+1∣T−Pˉt+1)Ct⊤P_{t|T}=P_{t|t}+C_t\bigl(P_{t+1|T}-\bar P_{t+1}\bigr)C_t^\top
Never larger than the filtered one: more data cannot increase the uncertainty.
Fixed-lag smoother
x^t−L∣t=RTS backward pass over the window [t−L, t] started from the filter at t\hat x_{t-L|t}=\text{RTS backward pass over the window }[t-L,\,t]\text{ started from the filter at }t
Run the backward recursion over only the last L steps, starting from the filtered estimate at the current time.

Derivation: why the backward pass is correct, and why lag saturates

The backward step

For the linear-Gaussian model the filter gives xt∣z1:t∼N(x^t∣t,Pt∣t)x_t\mid z_{1:t}\sim\mathcal{N}(\hat x_{t|t},P_{t|t}). The next state is xt+1=Fxt+wx_{t+1}=Fx_t+w. Condition on all the readings by first writing the joint of xtx_t and xt+1x_{t+1} given z1:tz_{1:t}.

  1. [xtxt+1]∣z1:t∼N ⁣([x^t∣tFx^t∣t],[Pt∣tPt∣tF⊤FPt∣tPˉt+1])\begin{bmatrix}x_t\\x_{t+1}\end{bmatrix}\Big|z_{1:t}\sim\mathcal{N}\!\left(\begin{bmatrix}\hat x_{t|t}\\F\hat x_{t|t}\end{bmatrix},\begin{bmatrix}P_{t|t}&P_{t|t}F^\top\\FP_{t|t}&\bar P_{t+1}\end{bmatrix}\right)

    The joint of the state and its successor, with Pˉt+1=FPt∣tF⊤+Q\bar P_{t+1}=FP_{t|t}F^\top+Q.

  2. xt∣xt+1,z1:t∼N(x^t∣t+Ct(xt+1−Fx^t∣t),;Pt∣t−CtPˉt+1Ct⊤)x_t\mid x_{t+1},z_{1:t}\sim\mathcal{N}\bigl(\hat x_{t|t}+C_t(x_{t+1}-F\hat x_{t|t}),;P_{t|t}-C_t\bar P_{t+1}C_t^\top\bigr)

    Conditioning a joint Gaussian (Module 4), with the gain Ct=Pt∣tF⊤Pˉt+1−1C_t=P_{t|t}F^\top\bar P_{t+1}^{-1}.

  3. the later readings zt+1:T depend on xt only through xt+1\text{the later readings }z_{t+1:T}\text{ depend on }x_t\text{ only through }x_{t+1}

    The Markov property: once xt+1x_{t+1} is known, the later readings tell nothing more about xtx_t.

  4. x^t∣T=x^t∣t+Ct(x^t+1∣T−Fx^t∣t),Pt∣T=Pt∣t+Ct(Pt+1∣T−Pˉt+1)Ct⊤\hat x_{t|T}=\hat x_{t|t}+C_t(\hat x_{t+1|T}-F\hat x_{t|t}),\quad P_{t|T}=P_{t|t}+C_t(P_{t+1|T}-\bar P_{t+1})C_t^\top

    Average the conditional over xt+1∣z1:T∼N(x^t+1∣T,Pt+1∣T)x_{t+1}\mid z_{1:T}\sim\mathcal{N}(\hat x_{t+1|T},P_{t+1|T}) (the law of total expectation and variance). This is the RTS recursion, run backward from TT.

Why lag saturates

The correction CtC_t multiplies how much the next state moved relative to its prediction. Each backward step multiplies by CtC_t again: the influence of a reading LL steps ahead on step tt is roughly ∏C\prod C, which decays geometrically. For the constant-velocity model of the example the steady-state smoother gain matrix CC has eigenvalues of modulus 0.81 (computed numerically from the converged filter), so the correction carried back from a reading nn steps ahead shrinks like 0.81n0.81^n: 0.35 at 5 steps, 0.12 at 10 and 0.015 at 20. That is why the table flattens between lag 5 and lag 10 and nothing is left to gain at 20.

Fixed-lag in a window

For an online system the backward pass need not go to the beginning: it runs over the window of LL steps and starts from the filter's estimate at the current time, so the cost per step is O(L)O(L). The program does exactly this, using the filtered estimates and covariances already computed.

A smoother estimate is the filter estimate plus a correction from the future, passed back through gains that decay geometrically. A lag of a few filter time constants captures nearly all the benefit.

A worked example

The constant-velocity robot of Module 7: 0.5 s steps, acceleration noise 0.05, position readings with noise 0.2 m, a filter with correct models, 120 steps.

  1. Accuracy against lag (60 runs, steps 10–100). Lag 0: position 0.1173 m, velocity 0.0679 m/s, claimed velocity std 0.0733. Lag 1: 0.0966, 0.0598, 0.0648. Lag 2: 0.0824, 0.0527, 0.0571. Lag 5: 0.0686, 0.0414, 0.0424. Lag 10: 0.0679, 0.0393, 0.0387. Lag 20: 0.0667, 0.0391, 0.0386. The claimed standard deviations track the real errors: the smoother is consistent. Lag 5 captures 96% of the position gain and 92% of the velocity gain of lag 20, for 2.5 s of delay.
  2. A manoeuvre. At step 60 the velocity reverses from +1 to −1 m/s, which the motion model does not expect. The rms velocity error in steps 56 to 70 is 0.836 m/s for the filter, 0.574 with lag 2, 0.453 with lag 5 and 0.485 with lag 10; the mean time until the velocity estimate is within 0.5 m/s of the truth is 5.9, 4.2, 2.5 and 2.0 steps.
  3. Reading the result. Even with hindsight the estimate is not instantaneous: the smoother knows the velocity changed only after enough readings have accumulated, and the model's low process noise resists an abrupt change (a larger Q would react faster, at the price of noise in steady motion: module 7).
import math
from robotprob import Rng, mean, kf_predict, kf_update, rts_smoother

dt, sq, r_ = 0.5, 0.05, 0.04
F = [[1.0, dt], [0.0, 1.0]]; H = [[1.0, 0.0]]
q = sq * sq
Q = [[q * dt**3 / 3, q * dt**2 / 2], [q * dt**2 / 2, q * dt]]
R = [[r_]]
T = 120

def simulate(seed, jump=False):
    rng = Rng(seed)
    pos, vel = 0.0, 1.0
    truth, zs = [], []
    for t in range(T):
        if jump and t == 60: vel = -1.0                     # a manoeuvre the model does not know about: the robot reverses
        vel += rng.gauss(0.0, sq * math.sqrt(dt)); pos += vel * dt
        truth.append((pos, vel)); zs.append(pos + rng.gauss(0.0, math.sqrt(r_)))
    return truth, zs

def filter_run(zs):
    x, P = [0.0, 1.0], [[1.0, 0.0], [0.0, 1.0]]
    xs, Ps = [], []
    for z in zs:
        x, P = kf_predict(x, P, F, Q)
        x, P, y, S, K = kf_update(x, P, [z], H, R)
        xs.append(x); Ps.append(P)
    return xs, Ps

def fixed_lag(xs, Ps, t, lag):
    """The estimate of step t using the measurements up to t + lag: the RTS backward pass over that window only."""
    end = min(len(xs) - 1, t + lag)
    sm_x, sm_P = rts_smoother(xs[t:end + 1], Ps[t:end + 1], F, Q)
    return sm_x[0], sm_P[0]

print('fixed-lag smoothing of the constant-velocity robot: 60 runs, steps 10-100')
print('lag (steps)  delay (s)   rms position error   rms velocity error   claimed velocity std')
for lag in (0, 1, 2, 5, 10, 20):
    ep, ev, cl = [], [], []
    for seed in range(1, 61):
        truth, zs = simulate(seed)
        xs, Ps = filter_run(zs)
        for t in range(10, 101):
            x, P = fixed_lag(xs, Ps, t, lag)
            ep.append((x[0] - truth[t][0]) ** 2); ev.append((x[1] - truth[t][1]) ** 2); cl.append(P[1][1])
    print(str(lag).rjust(8), str(lag * dt).rjust(11), str(round(math.sqrt(mean(ep)), 4)).rjust(16), str(round(math.sqrt(mean(ev)), 4)).rjust(20), str(round(math.sqrt(mean(cl)), 4)).rjust(20))

print('a manoeuvre the model does not know about: at step 60 the velocity changes from +1 to -1 m/s')
print('lag      rms velocity error in steps 56-70    time (steps) until the velocity estimate is within 0.5 m/s of the truth')
for lag in (0, 2, 5, 10):
    errs, reach = [], []
    for seed in range(1, 61):
        truth, zs = simulate(seed, jump=True)
        xs, Ps = filter_run(zs)
        est = [fixed_lag(xs, Ps, t, lag)[0] for t in range(50, 80)]
        for t in range(56, 71): errs.append((est[t - 50][1] - truth[t][1]) ** 2)
        k = next((t for t in range(60, 80) if abs(est[t - 50][1] - truth[t][1]) < 0.5), 80)
        reach.append(k - 60)
    print(str(lag).rjust(3), str(round(math.sqrt(mean(errs)), 3)).rjust(24), str(round(mean(reach), 1)).rjust(40))
fixed-lag smoothing of the constant-velocity robot: 60 runs, steps 10-100
lag (steps)  delay (s)   rms position error   rms velocity error   claimed velocity std
       0         0.0           0.1173               0.0679               0.0733
       1         0.5           0.0966               0.0598               0.0648
       2         1.0           0.0824               0.0527               0.0571
       5         2.5           0.0686               0.0414               0.0424
      10         5.0           0.0679               0.0393               0.0387
      20        10.0           0.0667               0.0391               0.0386
a manoeuvre the model does not know about: at step 60 the velocity changes from +1 to -1 m/s
lag      rms velocity error in steps 56-70    time (steps) until the velocity estimate is within 0.5 m/s of the truth
  0                    0.836                                      5.9
  2                    0.574                                      4.2
  5                    0.453                                      2.5
 10                    0.485                                      2.0
fixed_lag runs rts_smoother over the window [t, t + lag] of the filter results and takes the first element: the state at t given the readings up to t + lag.

In robotics

Where the delay is acceptable

  • Mapping and reconstruction: the map is built from the smoothed trajectory (Module 11); no one needs the pose before the scan is processed.
  • Calibration and system identification: estimating a fixed bias or alignment from a whole run uses the fixed-interval smoother or the batch problem.
  • Offline analysis: flight-log reconstruction, sports tracking, post-processed GNSS.
  • Display and logging: a trajectory shown with a one-second delay can be much smoother than a live one.

Sliding-window smoothers in robotics

Visual–inertial odometry systems optimise the last few seconds of poses, landmarks and IMU biases together (a fixed-lag smoother), marginalising the oldest states with the Schur complement as in Module 11, lesson 3. Each new image improves the estimates of the preceding poses; the control loop reads the newest pose, which is a filter-quality estimate, and the map uses the older, better ones. The SLAM course covers these windowed estimators in its back-end lessons.

Choosing the lag

  • Plot the error (or the claimed standard deviation) against the lag as in the figure and stop where the curve flattens: here at about 5 steps.
  • For a nearly constant-velocity system the memory is of order 1/(1−K)1/(1-K) steps (the filter's gain K sets how fast it forgets); the lag should be a few times that.
  • Budget the latency: if the downstream user can wait LΔtL\Delta t, spend it.

Smoothing and model errors

A smoother is only as good as its model. A wrong process noise gives wrong gains CtC_t and a delayed but biased estimate; a smoother that is told the motion is nearly constant will smooth away a real manoeuvre. Check the smoothed residuals: if the smoothed trajectory is much smoother than the readings imply, the model is too stiff.

Common misconceptions

“A smoother gives the same estimate, just later.”

It gives a different, better estimate of the same moment: here the velocity error at lag 5 was 4.1 cm/s against 6.8 for the filter, and its claimed uncertainty was lower.

“More lag always helps.”

The gain decays geometrically with the number of steps; lag 10 and lag 20 differed by only about 1 mm and 0.2 mm/s. Beyond the filter’s memory, extra lag is only extra delay.

“The smoothed covariance is the filter covariance minus something.”

It is: smoothed P is never larger than the filtered one. But it is correct only if the model is, and in a manoeuvre the model is wrong, so the smoothed claims need the same checking as the filter’s.

“A smoother can be used in a control loop with a small lag.”

It can, but the controller then acts on an estimate that is already a few steps old: with lag 2 that is one second, and the delay can destabilise a fast loop. Control uses the filter estimate and the smoother improves the record.

Key takeaways

  1. Filtering uses readings up to t, fixed-lag smoothing up to t + L, fixed-interval smoothing the whole record.
  2. The RTS backward step corrects each state by the next smoothed state’s deviation from its prediction, through the gain C = P F ᵀ P̄⁻¹; smoothed covariances are never larger than filtered ones.
  3. The gain from lag decays geometrically: lag 5 (2.5 s) gave position error 0.069 against 0.117 for the filter, lag 20 only 0.067.
  4. Smoothing helps most at a surprise: after a reversal the velocity error fell from 0.84 to 0.45 m/s and the recovery from 5.9 to 2.5 steps with lag 5.
  5. Choose the lag where the error curve flattens, a few times the filter’s memory, and keep the filter for control.

References and further reading

Each source is marked free or paid. Free to read does not mean free to reuse: the course resources page lists every source with its licence.

  • S. Särkkä and L. Svensson, Bayesian Filtering and Smoothing, 2nd edition, 2023 , the chapters on Bayesian smoothing and the RTS smoother (including fixed-lag and fixed-point forms). Free to read as the authors’ online PDF; the book is copyrighted.
  • T. D. Barfoot, State Estimation for Robotics, 2nd edition, 2024 , chapter 3, which derives the smoother as the batch solution. Free to read as the author’s PDF; the book is copyrighted.
  • H. E. Rauch, F. Tung and C. T. Striebel, “Maximum Likelihood Estimates of Linear Dynamic Systems”, AIAA Journal 3 (8), 1965. Paid journal article.
  • B. D. O. Anderson and J. B. Moore, Optimal Filtering, Prentice-Hall, 1979, chapter 7 (smoothing, including the fixed-lag smoother). Paid book.

Knowledge check

Three questions. Write an answer, then reveal the explanation. Your answers stay in your browser.

Question 1

A filter has σt|t = 0.20 for a position, and a smoother brings it to σt|T = 0.12. What is the variance reduction, and what does it say about the information from the future readings?

Explanation

The variances are 0.04 and 0.0144. In information terms the filter has 25 per unit variance and the smoother 69.4: the future readings added 44.4, about 1.8 times what the past held. The reduction is 64% of the variance. It can never be negative: the smoothed covariance is never larger than the filtered one.

Question 2

Why is a fixed-lag smoother with lag 20 not noticeably better than one with lag 10 in the constant-velocity experiment?

Explanation

The correction passed back from a later step is multiplied by the smoother gain at every step it travels, and the gain matrix is a contraction (its eigenvalues have modulus 0.81 in the steady state), so the correction from a reading n steps ahead shrinks like 0.81^n: 0.12 at ten steps and 0.015 at twenty. By then the readings tell the smoother nothing about step t that the closer ones did not already. The error curve is flat beyond the filter’s memory, which is a few steps for this model.

Question 3

A robot reverses suddenly. Why does the smoother with lag 5 still need 2.5 steps to get within 0.5 m/s of the truth rather than recognising the reversal at once, and what would make it react faster?

Explanation

The model says the velocity is nearly constant (low process noise), so the smoother treats the first readings that disagree with it as probably noise and needs several consistent readings to move the velocity estimate. Lag lets it use the readings after the reversal, which is why it recovers faster than the filter, but the model’s stiffness still sets how many are needed. A larger process noise makes the model less stubborn (at the price of noisier estimates in steady motion), and a model that includes manoeuvres (a switching or interacting-multiple-model filter) reacts faster still.

What’s next

The last lesson of the course goes from estimating to deciding: a POMDP is the problem of choosing actions when all you have is a belief, and it closes the loop from this module to control.

Next lesson

M12-T4 — POMDPs: Acting on a Belief

Define a partially observable Markov decision process, update its belief, and compare an action that gathers information with one that does not.

Nothing below is required to finish this lesson.

  • Before
    M12-T2: The Error-State Kalman Filter

    Separate a nominal state from a small error state, write the predict, update, inject and reset steps, and say why inertial navigation uses them.

Other courses on this site treat neighbouring topics from their own angle: