Skip to content

Commit 0b4235c

Browse files
committed
Test covarance with propagation
1 parent 29592b0 commit 0b4235c

1 file changed

Lines changed: 206 additions & 0 deletions

File tree

RoME/test/testPose2Propagate.jl

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
using RoME
2+
using LieGroups
3+
using Test
4+
using ForwardDiff
5+
6+
function propagate_with_sigmapoints(G_domain, G_codomain, p_mean, Xp_mean, Sigma_prior, Sigma_meas)
7+
N_p = size(Sigma_prior, 1)
8+
N_m = size(Sigma_meas, 1)
9+
N = N_p + N_m # Total dimensions = 6
10+
11+
# Stack into a Joint Covariance Matrix
12+
Sigma_joint = zeros(N, N)
13+
Sigma_joint[1:N_p, 1:N_p] = Sigma_prior
14+
Sigma_joint[N_p+1:end, N_p+1:end] = Sigma_meas
15+
16+
# Extract the principal axes of uncertainty via Cholesky
17+
L = cholesky(Sigma_joint).L
18+
19+
# Standard Unscented Transform Weights (2N points)
20+
c = sqrt(N)
21+
weight = 1.0 / (2 * N)
22+
23+
q_sigma_points = []
24+
25+
# Generate and Propagate Sigma Points
26+
for i in 1:N
27+
for sign in [1.0, -1.0]
28+
# 6D perturbation vector
29+
delta = sign * c * L[:, i]
30+
31+
delta_p = delta[1:N_p]
32+
delta_m = delta[N_p+1:end]
33+
34+
# Apply perturbation to the prior pose p
35+
# (Mapping coordinates into the tangent space and taking the exponential)
36+
p_pert = exp(G_domain, p_mean, hat(G_domain, p_mean, delta_p))
37+
38+
# Apply perturbation to the measurement
39+
X_meas_pert = Xp_mean + delta_m
40+
41+
# Propagate to q using the factor's exact forward model
42+
# Since factor is: X_meas = log(M, p, q) => q = exp(M, p, X_meas)
43+
q_pert = exp(G_codomain, p_pert, hat(G_codomain, p_pert, X_meas_pert))
44+
45+
push!(q_sigma_points, q_pert)
46+
end
47+
end
48+
49+
# Reconstruct the Covariance at q
50+
# First, find the expected mean of q
51+
q_mean = exp(G_codomain, p_mean, hat(G_codomain, p_mean, Xp_mean))
52+
53+
Sigma_q = zeros(N_p, N_p)
54+
for q_pert in q_sigma_points
55+
# Pull back each perturbed q into the tangent space of the mean
56+
diff_hat = log(G_domain, q_mean, q_pert)
57+
diff_coords = vee(G_domain, q_mean, diff_hat)
58+
59+
# Standard sample covariance
60+
Sigma_q += weight * (diff_coords * diff_coords')
61+
end
62+
63+
return Sigma_q
64+
end
65+
66+
function propagate_with_jacobians(M_dom, M_cod, p, Xp_coords, Sigma_prior, Sigma_meas)
67+
68+
q = exp(M_cod, p, hat(LieAlgebra(M_cod), Xp_coords, ArrayPartition))
69+
70+
alg = LieAlgebra(M_cod)
71+
72+
J_p = ForwardDiff.jacobian(zeros(3)) do dp
73+
p_pert = exp(M_dom, p, hat(M_dom, p, dp))
74+
75+
= log(M_cod, p_pert, q)
76+
X_tangent = hat(alg, Xp_coords)
77+
return vee(alg, X_tangent - X̂)
78+
end
79+
80+
J_q = ForwardDiff.jacobian(zeros(3)) do dq
81+
# Perturb the variable natively in its ProductManifold space
82+
q_pert = exp(M_dom, q, hat(M_dom, q, dq))
83+
84+
# Evaluate exact factor residual on SE(2)
85+
= log(M_cod, p, q_pert)
86+
X_tangent = hat(alg, Xp_coords)
87+
return vee(alg, X_tangent - X̂)
88+
end
89+
90+
J_X = ForwardDiff.jacobian(zeros(3)) do dX
91+
# Perturb the measurement in its raw Euclidean coordinate space
92+
X_pert_coords = Xp_coords + dX
93+
94+
# Evaluate exact factor residual on SE(2)
95+
= log(M_cod, p, q)
96+
X_tangent = hat(alg, X_pert_coords)
97+
return vee(alg, X_tangent - X̂)
98+
end
99+
100+
q_J_p = -J_q \ J_p # How q changes when prior p changes
101+
q_J_X = -J_q \ J_X # How q changes when measurement X changes
102+
103+
return q_J_p * Sigma_prior * q_J_p' + q_J_X * Sigma_meas * q_J_X'
104+
end
105+
106+
DFG.@defObservationType GroupPose2Pose2 RelativeObservation SpecialEuclideanGroup(2; variant = :right)
107+
function (cf::CalcFactor{<:GroupPose2Pose2})(X, p, q)
108+
M = getManifold(GroupPose2Pose2)
109+
= log(M, p, q)
110+
return vee(M, p, X - X̂)
111+
end
112+
113+
114+
@testset "Propagate covariance on SE(2)" begin
115+
116+
M = getManifold(GroupPose2Pose2)
117+
p = getPointIdentity(M)
118+
119+
# Xp_coords = [10.0, 0.0, pi/4]
120+
Xp_coords = [10.0, 0.1, pi/8]
121+
q = exp(M, p, hat(LieAlgebra(M), Xp_coords, ArrayPartition))
122+
123+
# Define Covariances
124+
Sigma_prior = diagm([0.001, 0.002, 0.003])
125+
Sigma_meas = diagm([0.03, 1.0, 0.01] .^ 2)
126+
127+
# propagate
128+
M_dom = getManifold(Pose2)
129+
M_cod = getManifold(GroupPose2Pose2)
130+
131+
jac_cov_x2 = propagate_with_jacobians(M_dom, M_cod, p, Xp_coords, Sigma_prior, Sigma_meas)
132+
133+
ut_cov_x2 = propagate_with_sigmapoints(M_dom, M_cod, p, Xp_coords, Sigma_prior, Sigma_meas)
134+
135+
# --- Setup and Solve Graph ---
136+
fg = initfg()
137+
getSolverParams(fg).graphinit = false
138+
addVariable!(fg, :x1, Pose2)
139+
addVariable!(fg, :x2, Pose2)
140+
141+
addFactor!(fg, [:x1], PriorPose2(MvNormal([0.0, 0.0, 0.0], Sigma_prior)))
142+
addFactor!(fg, [:x1; :x2], GroupPose2Pose2(MvNormal(Xp_coords, Sigma_meas)))
143+
144+
IIF.solveGraphParametric!(fg)
145+
146+
x1 = getState(fg, :x1, :parametric)
147+
x2 = getState(fg, :x2, :parametric)
148+
149+
# x1 should just match the prior
150+
@test isapprox(DFG.refMeans(x1)[1], p; atol = 1e-6)
151+
@test isapprox(DFG.refCovariances(x1)[1], Sigma_prior; atol = 1e-6)
152+
153+
# x2 should match propagated mean and covariance
154+
@test isapprox(DFG.refCovariances(x2)[1], jac_cov_x2; atol = 1e-6)
155+
@test isapprox(DFG.refCovariances(x2)[1], ut_cov_x2; atol = 3e-3)
156+
157+
@test isapprox(DFG.refMeans(x2)[1], q; atol = 1e-6)
158+
159+
end
160+
161+
@testset "Propagate covariance on LeftInvariantMetricSE(2)" begin
162+
163+
M = getManifold(Pose2Pose2)
164+
p = getPointIdentity(M)
165+
166+
# Xp_coords = [10.0, 0.0, pi/4]
167+
Xp_coords = [10.0, 0.1, pi/8]
168+
q = exp(M, p, hat(LieAlgebra(M), Xp_coords, ArrayPartition))
169+
170+
# Define Covariances
171+
Sigma_prior = diagm([0.001, 0.002, 0.003])
172+
Sigma_meas = diagm([0.03, 1.0, 0.01] .^ 2)
173+
174+
# Propagate to x2
175+
M_dom = getManifold(Pose2)
176+
M_cod = getManifold(Pose2Pose2)
177+
178+
jac_cov_x2 = propagate_with_jacobians(M_dom, M_cod, p, Xp_coords, Sigma_prior, Sigma_meas)
179+
180+
ut_cov_x2 = propagate_with_sigmapoints(M_dom, M_cod, p, Xp_coords, Sigma_prior, Sigma_meas)
181+
182+
# --- Setup and Solve Graph ---
183+
fg = initfg()
184+
getSolverParams(fg).graphinit = false
185+
addVariable!(fg, :x1, Pose2)
186+
addVariable!(fg, :x2, Pose2)
187+
188+
addFactor!(fg, [:x1], PriorPose2(MvNormal([0.0, 0.0, 0.0], Sigma_prior)))
189+
addFactor!(fg, [:x1; :x2], Pose2Pose2(MvNormal(Xp_coords, Sigma_meas)))
190+
191+
IIF.solveGraphParametric!(fg)
192+
193+
x1 = getState(fg, :x1, :parametric)
194+
x2 = getState(fg, :x2, :parametric)
195+
196+
# x1 should just match the prior
197+
@test isapprox(DFG.refMeans(x1)[1], p; atol = 1e-4)
198+
@test isapprox(DFG.refCovariances(x1)[1], Sigma_prior; atol = 1e-4)
199+
200+
# x2 should match propagated mean and covariance
201+
@test isapprox(DFG.refCovariances(x2)[1], ut_cov_x2; atol = 3e-3)
202+
@test isapprox(DFG.refCovariances(x2)[1], jac_cov_x2; atol = 1e-4)
203+
@test isapprox(DFG.refMeans(x2)[1], q; atol = 1e-4)
204+
205+
end
206+

0 commit comments

Comments
 (0)