Skip to content

Commit e683623

Browse files
authored
Merge pull request borglab#2183 from borglab/feature/Sim2_update
Similarity2 and Similarity3 updates
2 parents e205d72 + f8099c1 commit e683623

File tree

7 files changed

+329
-21
lines changed

7 files changed

+329
-21
lines changed

gtsam/geometry/Similarity2.cpp

Lines changed: 85 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -197,27 +197,48 @@ Similarity2 Similarity2::Align(const Pose2Pairs& abPosePairs) {
197197
Matrix2 Similarity2::GetV(double theta, double lambda) {
198198
// Derivation from https://ethaneade.com/lie_groups.pdf page 6
199199
const double lambda2 = lambda * lambda, theta2 = theta * theta;
200+
201+
// SE(2) or near-SE(2) case (|λ| tiny)
202+
if (std::abs(lambda) < 1e-9) {
203+
double A, B;
204+
if (theta2 > 1e-9) {
205+
A = sin(theta) / theta;
206+
B = (1 - cos(theta)) / theta2;
207+
}
208+
else { // θ ≈ 0 → series
209+
A = 1.0 - theta2 / 6.0;
210+
B = 0.5 - theta2 / 24.0;
211+
}
212+
Matrix2 V;
213+
V << A, -theta * B,
214+
theta* B, A;
215+
return V;
216+
}
217+
218+
// general Sim(2) case
219+
const double d2 = lambda2 + theta2;
220+
if (d2 < 1e-15) // both tiny → identity
221+
return Matrix2::Identity();
222+
223+
// rotation scalars (unchanged)
200224
double A, B, C;
201225
if (theta2 > 1e-9) {
202226
A = sin(theta) / theta;
203227
B = (1 - cos(theta)) / theta2;
204228
C = (1 - A) / theta2;
205-
} else {
206-
// Taylor series expansion for theta=0
207-
A = 1.0;
229+
} else { // θ series
230+
A = 1.0 - theta2 / 6.0;
208231
B = 0.5 - theta2 / 24.0;
209232
C = 1.0 / 6.0 - theta2 / 120.0;
210233
}
211-
double alpha = 1.0 / (1.0 + theta2 / lambda2);
212-
const double s = exp(lambda);
213234

214-
double s_inv = 1.0 / s;
215-
double X = alpha * (1 - s_inv) / lambda + (1 - alpha) * (A - lambda * B);
216-
double Y =
217-
alpha * (s_inv - 1 + lambda) / lambda2 + (1 - alpha) * (B - lambda * C);
235+
const double alpha = lambda2 / (lambda2 + theta2);
236+
const double s_inv = exp(-lambda);
237+
const double X = alpha * (1 - s_inv) / lambda + (1 - alpha) * (A - lambda * B);
238+
const double Y = alpha * (s_inv - 1 + lambda) / lambda2 + (1 - alpha) * (B - lambda * C);
218239

219240
Matrix2 V;
220-
V << X, -theta * Y, theta * Y, X;
241+
V << X, -theta * Y, theta* Y, X;
221242
return V;
222243
}
223244

@@ -247,7 +268,25 @@ Similarity2 Similarity2::Expmap(const Vector4& v, //
247268
}
248269

249270
Matrix4 Similarity2::AdjointMap() const {
250-
throw std::runtime_error("Similarity2::AdjointMap not implemented");
271+
const Matrix2& R = R_.matrix();
272+
const Point2& t = t_;
273+
const double& s = s_;
274+
275+
Matrix4 Adj = Matrix4::Identity(); // Start with Identity
276+
277+
// Top-left 2x2 block: s * R
278+
Adj.block<2, 2>(0, 0) = s * R;
279+
280+
// Top-right coupling terms, derived from T*Hat(xi)*T_inv
281+
// Column w.r.t 'w': maps to [-s*J*t]
282+
Adj(0, 2) = s * t.y();
283+
Adj(1, 2) = -s * t.x();
284+
285+
// Column w.r.t 'lambda': maps to [-s*t]
286+
Adj(0, 3) = -s * t.x();
287+
Adj(1, 3) = -s * t.y();
288+
289+
return Adj;
251290
}
252291

253292
Matrix3 Similarity2::Hat(const Vector4 &xi) {
@@ -268,6 +307,7 @@ Vector4 Similarity2::Vee(const Matrix3 &Xi) {
268307
xi[3] = -Xi(2, 2);
269308
return xi;
270309
}
310+
271311
std::ostream& operator<<(std::ostream& os, const Similarity2& p) {
272312
os << "[" << p.rotation().theta() << " " << p.translation().transpose() << " "
273313
<< p.scale() << "]\';";
@@ -281,4 +321,38 @@ Matrix3 Similarity2::matrix() const {
281321
return T;
282322
}
283323

324+
// In Similarity2.cpp, e.g., after the #includes
325+
326+
namespace {
327+
// The 9x4 matrix of vectorized generators for Sim(2).
328+
// The columns correspond to perturbations in [u_x, u_y, w, lambda].
329+
// P_sim2 = [vec(G_ux), vec(G_uy), vec(G_w), vec(G_lambda)]
330+
static const Eigen::Matrix<double, 9, 4> P_sim2 =
331+
(Eigen::Matrix<double, 9, 4>() <<
332+
// u_x u_y w lambda
333+
0, 0, 0, 0, // row 0
334+
0, 0, 1, 0, // row 1
335+
0, 0, 0, 0, // row 2
336+
0, 0, -1, 0, // row 3
337+
0, 0, 0, 0, // row 4
338+
0, 0, 0, 0, // row 5
339+
1, 0, 0, 0, // row 6
340+
0, 1, 0, 0, // row 7
341+
0, 0, 0, -1 // row 8
342+
).finished();
343+
} // namespace
344+
345+
//******************************************************************************
346+
Vector9 Similarity2::vec(OptionalJacobian<9, 4> H) const {
347+
const Matrix3 T = this->matrix();
348+
if (H) {
349+
// The Jacobian is given by the formula H = (I_3 ⊗ T) * P_sim2
350+
// where P_sim2 is the matrix of vectorized generators.
351+
// This can be implemented efficiently with block-wise multiplication.
352+
*H << T * P_sim2.block<3, 4>(0, 0),
353+
T* P_sim2.block<3, 4>(3, 0),
354+
T* P_sim2.block<3, 4>(6, 0);
355+
}
356+
return Eigen::Map<const Vector9>(T.data());
357+
}
284358
} // namespace gtsam

gtsam/geometry/Similarity2.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,9 @@ class GTSAM_EXPORT Similarity2 : public LieGroup<Similarity2, 4> {
185185
/// Calculate 4*4 matrix group equivalent
186186
Matrix3 matrix() const;
187187

188+
/// Return vectorized Similarity2 matrix in column order
189+
Vector9 vec(OptionalJacobian<9, 4> H = {}) const;
190+
188191
/// Return a GTSAM rotation
189192
Rot2 rotation() const { return R_; }
190193

gtsam/geometry/Similarity3.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,4 +295,42 @@ Matrix4 Similarity3::matrix() const {
295295
return T;
296296
}
297297

298+
namespace {
299+
// The 16x7 matrix of vectorized generators for Sim(3).
300+
// The columns correspond to perturbations in [w_x, w_y, w_z, u_x, u_y, u_z, lambda].
301+
// P_sim3 = [vec(G_wx), vec(G_wy), vec(G_wz), vec(G_ux), vec(G_uy), vec(G_uz), vec(G_lambda)]
302+
static const Eigen::Matrix<double, 16, 7> P_sim3 =
303+
(Eigen::Matrix<double, 16, 7>() <<
304+
// wx, wy, wz, ux, uy, uz, la
305+
0, 0, 0, 0, 0, 0, 0,
306+
0, 0, 1, 0, 0, 0, 0,
307+
0, -1, 0, 0, 0, 0, 0,
308+
0, 0, 0, 0, 0, 0, 0,
309+
0, 0, -1, 0, 0, 0, 0,
310+
0, 0, 0, 0, 0, 0, 0,
311+
1, 0, 0, 0, 0, 0, 0,
312+
0, 0, 0, 0, 0, 0, 0,
313+
0, 1, 0, 0, 0, 0, 0,
314+
-1, 0, 0, 0, 0, 0, 0,
315+
0, 0, 0, 0, 0, 0, 0,
316+
0, 0, 0, 0, 0, 0, 0,
317+
0, 0, 0, 1, 0, 0, 0,
318+
0, 0, 0, 0, 1, 0, 0,
319+
0, 0, 0, 0, 0, 1, 0,
320+
0, 0, 0, 0, 0, 0, -1
321+
).finished();
322+
} // namespace
323+
324+
Similarity3::Vector16 Similarity3::vec(OptionalJacobian<16, 7> H) const {
325+
const Matrix4 T = this->matrix();
326+
if (H) {
327+
// The Jacobian is given by the formula H = (I_4 ⊗ T) * P_sim3
328+
// where P_sim3 is the matrix of vectorized generators.
329+
*H << T * P_sim3.block<4, 7>(0, 0), T* P_sim3.block<4, 7>(4, 0),
330+
T* P_sim3.block<4, 7>(8, 0), T* P_sim3.block<4, 7>(12, 0);
331+
}
332+
return Eigen::Map<const Vector16>(T.data());
333+
}
334+
335+
298336
} // namespace gtsam

gtsam/geometry/Similarity3.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,15 @@ class Pose3;
3434
* 3D similarity transform
3535
*/
3636
class GTSAM_EXPORT Similarity3 : public LieGroup<Similarity3, 7> {
37+
public:
3738
/// @name Pose Concept
3839
/// @{
3940
typedef Rot3 Rotation;
4041
typedef Point3 Translation;
4142
/// @}
4243

44+
using Vector16 = Eigen::Matrix<double, 16, 1>;
45+
4346
private:
4447
Rot3 R_;
4548
Point3 t_;
@@ -64,6 +67,9 @@ class GTSAM_EXPORT Similarity3 : public LieGroup<Similarity3, 7> {
6467
/// Construct from matrix [R t; 0 s^-1]
6568
Similarity3(const Matrix4& T);
6669

70+
/// Vectorize 4x4 matrix into a 16-dim vector.
71+
Vector16 vec(OptionalJacobian<16, 7> H = {}) const;
72+
6773
/// @}
6874
/// @name Testable
6975
/// @{

gtsam/geometry/tests/testSimilarity2.cpp

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,17 @@
1515
* @author Varun Agrawal
1616
*/
1717

18-
#include <CppUnitLite/TestHarness.h>
18+
#include <gtsam/geometry/Similarity2.h>
19+
1920
#include <gtsam/base/Testable.h>
2021
#include <gtsam/base/numericalDerivative.h>
2122
#include <gtsam/base/testLie.h>
22-
#include <gtsam/geometry/Similarity2.h>
23+
#include <gtsam/base/Vector.h>
24+
25+
#include <CppUnitLite/TestHarness.h>
2326

2427
#include <functional>
28+
#include <vector>
2529

2630
using namespace std::placeholders;
2731
using namespace gtsam;
@@ -178,6 +182,70 @@ TEST(Similarity2, TransformFrom_Pose2) {
178182
EXPECT(assert_equal(expected, actual, 1e-9));
179183
}
180184

185+
//******************************************************************************
186+
TEST(Similarity2, vec) {
187+
const double theta = 0.3;
188+
const Rot2 R_test = Rot2::fromAngle(theta);
189+
const Point2 t_test(0.2, 0.7);
190+
const double s_test = 4.0;
191+
const Similarity2 sim(R_test, t_test, s_test);
192+
193+
// 1. Test the Value
194+
Vector9 expected_vec;
195+
const double c = cos(theta), si = sin(theta);
196+
expected_vec << c, si, 0, // First column
197+
-si, c, 0, // Second column
198+
t_test.x(), t_test.y(), 1.0 / s_test; // Third column
199+
Vector9 actual_vec = sim.vec();
200+
EXPECT(assert_equal(expected_vec, actual_vec, 1e-9));
201+
202+
// 2. Test the Jacobian
203+
Matrix94 H_actual;
204+
sim.vec(H_actual);
205+
auto vec_fun = [](const Similarity2& sim_arg) -> Vector9 {
206+
return sim_arg.vec();
207+
};
208+
Matrix H_numerical = numericalDerivative11<Vector9, Similarity2, 4>(vec_fun, sim);
209+
EXPECT(assert_equal(H_numerical, H_actual, 1e-7));
210+
}
211+
212+
//******************************************************************************
213+
TEST(Similarity2, AdjointMap) {
214+
// Create a non-trivial Similarity2 object
215+
const Rot2 R = Rot2::fromAngle(-0.7);
216+
const Point2 t(1.5, -2.3);
217+
const double s = 0.5;
218+
const Similarity2 sim(R, t, s);
219+
220+
// 1. Calculate the Adjoint map using the fast, closed-form function.
221+
// This is the "actual" value we want to test.
222+
Matrix4 actual_Adj = sim.AdjointMap();
223+
224+
// 2. Calculate the Adjoint map using the general, "brute force" definition.
225+
// This is the "expected" ground truth.
226+
// Ad_i = Vee(T * Hat(e_i) * T_inv)
227+
228+
// Get the 4 generators G_i = Hat(e_i) for sim(2)
229+
std::vector<Matrix3> G;
230+
for (int i = 0; i < 4; ++i) {
231+
G.push_back(Similarity2::Hat(Vector4::Unit(i)));
232+
}
233+
234+
// Calculate T and its inverse
235+
const Matrix3 T = sim.matrix();
236+
const Matrix3 T_inv = sim.inverse().matrix(); // Or calculate analytically
237+
238+
// Loop through columns to build the expected Adjoint matrix
239+
Matrix4 expected_Adj;
240+
for (int i = 0; i < 4; ++i) {
241+
Matrix3 T_Gi_Tinv = T * G[i] * T_inv;
242+
expected_Adj.col(i) = Similarity2::Vee(T_Gi_Tinv);
243+
}
244+
245+
// 3. Assert that the two matrices are equal.
246+
EXPECT(assert_equal(expected_Adj, actual_Adj, 1e-9));
247+
}
248+
181249
//******************************************************************************
182250
int main() {
183251
TestResult tr;

gtsam/geometry/tests/testSimilarity3.cpp

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,30 @@ TEST(Similarity3, BruteForceExpmap) {
111111

112112
//******************************************************************************
113113
TEST(Similarity3, AdjointMap) {
114+
// 1. Calculate the Adjoint map using the fast, closed-form function.
115+
Matrix7 actual = T2.AdjointMap();
116+
117+
// 2. Calculate the Adjoint map using the general, "brute force" definition.
118+
// Ad_i = Vee(T * Hat(e_i) * T_inv)
119+
// Get the 7 generators G_i = Hat(e_i) for sim(3)
120+
std::vector<Matrix4> G;
121+
for (int i = 0; i < 7; ++i) {
122+
G.push_back(Similarity3::Hat(Vector7::Unit(i)));
123+
}
124+
125+
// Calculate T and its inverse
114126
const Matrix4 T = T2.matrix();
115-
// Check Ad with actual definition
116-
Vector7 delta;
117-
delta << 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7;
118-
Matrix4 W = Similarity3::Hat(delta);
119-
Matrix4 TW = Similarity3::Hat(T2.AdjointMap() * delta);
120-
EXPECT(assert_equal(TW, Matrix4(T * W * T.inverse()), 1e-9));
127+
const Matrix4 T_inv = T2.inverse().matrix();
128+
129+
// Loop through columns to build the expected Adjoint matrix
130+
Matrix7 expected;
131+
for (int i = 0; i < 7; ++i) {
132+
Matrix4 T_Gi_Tinv = T * G[i] * T_inv;
133+
expected.col(i) = Similarity3::Vee(T_Gi_Tinv);
134+
}
135+
136+
// 3. Assert that the two matrices are equal.
137+
EXPECT(assert_equal(expected, actual));
121138
}
122139

123140
//******************************************************************************
@@ -552,6 +569,32 @@ TEST(Similarity3 , LieGroupDerivatives) {
552569
CHECK_LIE_GROUP_DERIVATIVES(T2, T3);
553570
}
554571

572+
//******************************************************************************
573+
TEST(Similarity3, vec) {
574+
const Rot3 R_test = Rot3::Rodrigues(0.1, 0.2, 0.3);
575+
const Point3 t_test(0.4, 0.5, 0.6);
576+
const double s_test = 0.7;
577+
const Similarity3 sim(R_test, t_test, s_test);
578+
579+
// 1. Test the Value
580+
Similarity3::Vector16 expected_vec;
581+
const Matrix3 R = R_test.matrix();
582+
expected_vec << R.col(0), 0.0, R.col(1), 0.0, R.col(2), 0.0, t_test.x(),
583+
t_test.y(), t_test.z(), 1.0 / s_test;
584+
Similarity3::Vector16 actual_vec = sim.vec();
585+
EXPECT(assert_equal(expected_vec, actual_vec, 1e-9));
586+
587+
// 2. Test the Jacobian
588+
Matrix H_actual(16, 7);
589+
sim.vec(H_actual);
590+
auto vec_fun = [](const Similarity3& sim_arg) -> Similarity3::Vector16 {
591+
return sim_arg.vec();
592+
};
593+
Matrix H_numerical = numericalDerivative11<Similarity3::Vector16, Similarity3, 7>(vec_fun, sim);
594+
EXPECT(assert_equal(H_numerical, H_actual, 1e-7));
595+
}
596+
597+
555598
//******************************************************************************
556599
int main() {
557600
TestResult tr;

0 commit comments

Comments
 (0)