Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Fixed-Lag Smoother Example

Most factor-graph examples keep every variable in the problem forever. That doesn’t scale to a robot that runs for hours: the graph, and the cost of solving it, would grow without bound.

A fixed-lag smoother fixes this by only keeping variables whose timestamp falls within a trailing window (the “lag”). As new measurements arrive, anything older than the lag is automatically marginalized out -- summarized into the remaining variables and then dropped -- so the problem size stays bounded no matter how long the robot runs.

The scenario: a robot drives in a straight line at 2 m/s. Two independent odometry-like sensors each measure the motion between consecutive poses (simulating sensor fusion), sampled every 0.25 seconds for 3 seconds, with a 2-second lag.

Open In Colab

GTSAM Copyright 2010-2026, Georgia Tech Research Corporation, Atlanta, Georgia 30332-0415 All Rights Reserved

Authors: Frank Dellaert (C++), Jeremy Aguilon (Python), et al. (see THANKS for the full author list)

See LICENSE for the license information

1. Configure the smoother

BatchFixedLagSmoother re-solves with Levenberg-Marquardt on every update() call (GTSAM also has an IncrementalFixedLagSmoother, based on iSAM2, which is more efficient but not covered in this notebook). lag=2.0 means only the most recent 2 seconds of poses are kept.

Each update needs three staging containers: new factors, new variable values, and a mapping from key to timestamp -- the timestamps are how the smoother knows which variables have aged out of the window.

2. Prior on the first pose

As in the odometry example, a prior anchors pose key 0 at the origin. We also record its timestamp as 0.0 seconds.

3. Simulate two odometry sensors over time

Every 0.25 seconds we add a new pose key, derived directly from the timestamp (int(1000 * time)), and a constant-velocity guess (time * 2 meters along x). Two BetweenFactorPose2s connect it to the previous pose -- one per “sensor”, each with its own measurement and noise model, simulating fusion of two independently noisy odometry sources.

update() is only called once every two steps (time >= 0.50), so the very first call batches two hops of factors at once, while later calls process one hop at a time. The staging containers are cleared after each update(), but the smoother’s own internal window keeps sliding forward regardless.

Timestamp = 0.5, Key = 500
(0.995821, 0.0231012, 0.0300001)

Timestamp = 0.75, Key = 750
(1.49284, 0.0457247, 0.045)

Timestamp = 1.0, Key = 1000
(1.98981, 0.0758879, 0.06)

Timestamp = 1.25, Key = 1250
(2.48627, 0.113502, 0.075)

Timestamp = 1.5, Key = 1500
(2.98211, 0.158558, 0.09)

Timestamp = 1.75, Key = 1750
(3.47722, 0.211047, 0.105)

Timestamp = 2.0, Key = 2000
(3.97149, 0.270956, 0.12)

Timestamp = 2.25, Key = 2250
(4.4648, 0.338272, 0.135)

Timestamp = 2.5, Key = 2500
(4.95705, 0.41298, 0.15)

Timestamp = 2.75, Key = 2750
(5.44812, 0.495063, 0.165)

Timestamp = 3.0, Key = 3000
(5.9379, 0.584503, 0.18)

4. What’s kept in the window

smoother_batch.timestamps() returns the key-to-timestamp map for every variable currently in the smoother. After 3 seconds have passed with a 2-second lag, pose 0 (timestamp 0.0) should be long gone -- marginalized out, not just cosmetically hidden.

Key: 1000  Time: 1.0
Key: 1250  Time: 1.25
Key: 1500  Time: 1.5
Key: 1750  Time: 1.75
Key: 2000  Time: 2.0
Key: 2250  Time: 2.25
Key: 2500  Time: 2.5
Key: 2750  Time: 2.75
Key: 3000  Time: 3.0

Only the poses from the last ~2 seconds remain; pose 0 and the earliest handful of poses are no longer part of the problem at all. This bounded-memory property is exactly why fixed-lag smoothing -- and its incremental, iSAM2-based cousin -- is the tool of choice for real-time or long-duration SLAM, where a robot cannot afford to keep re-optimizing its entire history forever.