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.

Certifiable Pose Graph Optimization on SE(2)

This notebook solves an SE(2)SE(2) synchronization problem with GTSAM’s Burer-Monteiro Riemannian Staircase and checks an SDP certificate that, when it passes, establishes global optimality. It extends the rotation averaging example with translations, so the estimate is a full trajectory rather than a set of orientations.

Each relative pose measurement is split across two factors that share a rotation key:

  • FrobeniusBetweenFactorRot2 contributes κij∥Rj−RiRij∥F2\kappa_{ij}\lVert R_j - R_i R_{ij}\rVert_F^2,

  • RelativeTranslationFactor2 contributes τij∥tj−ti−Rit~ij∥2\tau_{ij}\lVert t_j - t_i - R_i \tilde{t}_{ij}\rVert^2.

Both are quadratic, so the staircase can lift the whole problem to a low-rank SDP. See also the SE(3)SE(3) version.

Open In Colab

A grid world

A ring has a single cycle. A grid has many: the robot drives a boustrophedon (lawnmower) path through a 5-by-5 grid, and every pair of grid neighbours the path did not visit consecutively becomes a loop closure. The resulting short cycles all have to be reconciled against each other, which is a more representative test than a ring.

25 poses, 40 edges (24 odometry, 16 loop closures)

Generative model

Measurements follow the noise model that the estimator assumes, so the objective below is the maximum-likelihood one for this model. For an edge (i,j)(i,j) with true relative pose Rij=Ri⊤RjR_{ij} = R_i^\top R_j and tij=Ri⊤(tj−ti)t_{ij} = R_i^\top(t_j - t_i):

R~ij=Rij Exp(ω),ω∼N(0,σR2I),t~ij=tij+ε,ε∼N(0,τ−1I2).\tilde{R}_{ij} = R_{ij}\,\mathrm{Exp}(\omega),\quad \omega \sim \mathcal{N}(0, \sigma_R^2 I), \qquad \tilde{t}_{ij} = t_{ij} + \varepsilon,\quad \varepsilon \sim \mathcal{N}(0, \tau^{-1} I_2).

The rotational law is the small-dispersion limit of the isotropic Langevin distribution with concentration κ\kappa, whose density is proportional to exp⁡(κ tr R)\exp(\kappa\,\mathrm{tr}\,R). Since tr Exp(ω)≈d−∥ω∥2\mathrm{tr}\,\mathrm{Exp}(\omega) \approx d - \lVert\omega\rVert^2, that density behaves like a Gaussian of variance 1/(2κ)1/(2\kappa), so

σR=12κ,σt=1τ.\sigma_R = \frac{1}{\sqrt{2\kappa}},\qquad \sigma_t = \frac{1}{\sqrt{\tau}}.

The factor of two matters. Sampling with σR=1/κ\sigma_R = 1/\sqrt{\kappa} instead injects noise that does not match the weight κ\kappa the factor applies, and the optimal cost then sits well above its χ2\chi^2 expectation. The check after rounding will show it; changing the line below is enough to reproduce.

rotation:    kappa = 100, sigma = 4.05 deg
translation: tau   = 25, sigma = 0.20 m
80 factors over 25 rotation and 25 translation keys

Lifted initial values

The staircase optimizes over a stacked matrix Y∈Rn×pY \in \mathbb{R}^{n \times p}, and each variable occupies a horizontal slice of it. The two variable kinds have different row counts, which is the one thing worth getting right:

variableslice shapecontents
rotation R(i)2×p2 \times pthe lifted Ri⊤R_i^\top; at p=dp = d this is literally rotation.matrix().T
translation T(i)1×p1 \times pthe lifted ti⊤t_i^\top, a single row

We start at p=d=2p = d = 2, from perturbed ground-truth rotations and random translations. The certificate is checked wherever the solver lands, so a poor initialization costs staircase levels rather than correctness.

rotation slice: (2, 2)
translation slice: (1, 2)

Run and certify

The inner augmented-Lagrangian solver enforces the orthogonality constraints that the rotation factors emit. The outer staircase then forms the dual matrix S=Q+A∗(λ)S = Q + \mathcal{A}^*(\lambda) and checks S⪰0S \succeq 0. If the check passes, the factorization Z=YY⊤Z = YY^\top is a global optimum of the SDP relaxation; if it fails, the staircase lifts to rank p+1p+1 along the negative-eigenvalue direction and tries again.

Certified:    True
Final rank:   2
Ranks tried:  [2]
lambda_min:   -1.000e-03
Solver time:  0.0105 s

Round back to SE(2)SE(2)

The staircase optimizes over O(d)O(d), which has a reflected component, so the rounded blocks can come out with det⁡=−1\det = -1. We flip the last column of every block when the reflected blocks are in the majority, then project each rotation slice to the closest proper rotation and read each translation off its single row.

The reported objective is graph.error, which is gauge invariant, so no alignment to ground truth is needed to judge the solution.

Relaxation lower bound: 24.328973
Rounded objective:      24.328975
Relative gap:           1.121e-07
Objective at truth:     61.512158

A relative gap at the level of solver tolerance means the rounded SE(2)SE(2) trajectory matches the SDP lower bound to that tolerance, which is what certifies it as a global minimizer. The objective at ground truth is higher, as it should be: with noisy measurements the maximum-likelihood estimate fits the data better than the poses that generated it.

Is the noise model right?

Because graph.error is one half of a sum of squared whitened residuals, a correctly specified model puts the optimum near 12χ2\tfrac12\chi^2 with degrees of freedom equal to the number of independent residuals minus the number of free parameters. Each edge contributes 1 rotational and 2 translational residual dimensions, and each pose carries the same count of parameters. This is a single draw, so expect the ratio to scatter by roughly ±2/dof\pm\sqrt{2/\mathrm{dof}}.

residuals 120 - parameters 75 = 45 dof
Expected optimal cost (0.5 * chi^2): 22.5
Observed optimal cost:               24.3
Ratio:                               1.08

The estimated grid

Absolute poses are only determined up to a global SE(2)SE(2) transform, so a Procrustes fit is applied here purely so the two graphs can be drawn in one frame. It plays no role in the certificate or the objective above. Every edge is drawn, so the loop closures that tie the grid together are visible.

Loading...
RMS position error after alignment: 0.2149 m

Where the time goes

The staircase reports a per-level breakdown: building the rank-pp QCQP, running the local solver, and verifying the certificate.

Loading...
  QCQP build: 0.00015 s
 local solve: 0.01020 s
      verify: 0.00010 s