|
| 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 | + X̂ = 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 | + X̂ = 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 | + X̂ = 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 | + X̂ = 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