Gauss-Newton iteration: \[
H \, \Delta \mathbf{x} = -\mathbf{b}
\] where \(H = J^T \Omega J\) (sparse!) and \(\mathbf{b} = J^T \Omega \, \mathbf{e}\)
Building the Linear System
def build_linear_system(poses, edges, measurements):""" Build H and b for Gauss-Newton. poses: list of (x, y, theta) edges: list of (i, j) pairs measurements: list of (dx, dy, dtheta) for each edge """ n =len(poses) H = np.zeros((3*n, 3*n)) b = np.zeros(3*n)for (i, j), z_ij inzip(edges, measurements): xi, xj = poses[i], poses[j] e_ij = compute_residual(xi, xj, z_ij) A, B = compute_jacobians(xi, xj, z_ij)# Add to H (using Omega = I) H[3*i:3*i+3, 3*i:3*i+3] += A.T @ A H[3*i:3*i+3, 3*j:3*j+3] += A.T @ B H[3*j:3*j+3, 3*i:3*i+3] += B.T @ A H[3*j:3*j+3, 3*j:3*j+3] += B.T @ B# Add to b b[3*i:3*i+3] += A.T @ e_ij b[3*j:3*j+3] += B.T @ e_ijreturn H, b
Fixing the Gauge
The cost function is invariant to rigid transformations of all poses.
Problem: \(H\) is rank-deficient (3 degrees of freedom)
Solution: Fix the first pose \(x_0 = (0, 0, 0)\)
Remove rows/columns for \(x_0\) from \(H\)
Or add large diagonal entries: \(H_{0:3, 0:3} += \lambda I\)
Part 4: Implementation
Full Optimization Loop
def optimize_pose_graph(initial_poses, edges, measurements, n_iter=10, fix_first=True):""" Optimize pose graph using Gauss-Newton. """ poses = [np.array(p) for p in initial_poses] n =len(poses)for iteration inrange(n_iter): H, b = build_linear_system(poses, edges, measurements)# Fix first pose (add large weight)if fix_first: H[0:3, 0:3] +=1e6* np.eye(3)# Solve dx = np.linalg.solve(H, -b)# Update posesfor i inrange(n): poses[i] = poses[i] + dx[3*i:3*i+3]# Normalize angle poses[i][2] = np.arctan2(np.sin(poses[i][2]), np.cos(poses[i][2]))return poses