1use crate::{
33 LieGroup, Tangent,
34 so3::{SO3, SO3Tangent},
35};
36use nalgebra::{Matrix3, Matrix4, SMatrix, SVector, UnitQuaternion, Vector3};
37
38type Vector7<T> = SVector<T, 7>;
40type Matrix7<T> = SMatrix<T, 7, 7>;
41use std::{
42 fmt,
43 fmt::{Display, Formatter},
44};
45
46#[derive(Clone, PartialEq)]
50pub struct Sim3 {
51 params: SVector<f64, 8>,
53}
54
55impl Display for Sim3 {
56 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
57 let t = self.translation();
58 let s = self.scale();
59 let q = self.rotation_quaternion();
60 write!(
61 f,
62 "Sim3(translation: [{:.4}, {:.4}, {:.4}], scale: {:.4}, rotation: [w: {:.4}, x: {:.4}, y: {:.4}, z: {:.4}])",
63 t.x, t.y, t.z, s, q.w, q.i, q.j, q.k
64 )
65 }
66}
67
68impl Sim3 {
69 pub const DIM: usize = 3;
71
72 pub const DOF: usize = 7;
74
75 pub const REP_SIZE: usize = 8;
77
78 #[inline]
79 fn translation_impl(&self) -> Vector3<f64> {
80 Vector3::new(self.params[0], self.params[1], self.params[2])
81 }
82
83 #[inline]
84 fn rotation_impl(&self) -> SO3 {
85 SO3::from_quaternion_wxyz(
86 self.params[3],
87 self.params[4],
88 self.params[5],
89 self.params[6],
90 )
91 }
92
93 #[inline]
94 fn scale_impl(&self) -> f64 {
95 self.params[7]
96 }
97
98 #[inline]
99 fn from_parts(t: Vector3<f64>, r: &SO3, scale: f64) -> Self {
100 let q = r.params();
101 Sim3 {
102 params: SVector::<f64, 8>::from([t.x, t.y, t.z, q[0], q[1], q[2], q[3], scale]),
103 }
104 }
105
106 pub fn identity() -> Self {
108 Sim3 {
109 params: SVector::<f64, 8>::from([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]),
110 }
111 }
112
113 pub fn jacobian_identity() -> Matrix7<f64> {
115 Matrix7::<f64>::identity()
116 }
117
118 pub fn new(translation: Vector3<f64>, rotation: UnitQuaternion<f64>, scale: f64) -> Self {
125 assert!(scale > 0.0, "Scale must be positive");
126 Sim3::from_parts(translation, &SO3::new(rotation), scale)
127 }
128
129 pub fn from_components(translation: Vector3<f64>, rotation: SO3, scale: f64) -> Self {
131 assert!(scale > 0.0, "Scale must be positive");
132 Sim3::from_parts(translation, &rotation, scale)
133 }
134
135 pub fn translation(&self) -> Vector3<f64> {
137 self.translation_impl()
138 }
139
140 pub fn scale(&self) -> f64 {
142 self.scale_impl()
143 }
144
145 pub fn rotation_so3(&self) -> SO3 {
147 self.rotation_impl()
148 }
149
150 pub fn rotation_quaternion(&self) -> UnitQuaternion<f64> {
152 self.rotation_impl().quaternion()
153 }
154
155 pub fn rotation_matrix(&self) -> Matrix3<f64> {
157 self.rotation_impl().rotation_matrix()
158 }
159
160 pub fn matrix(&self) -> Matrix4<f64> {
162 let mut mat = Matrix4::identity();
163 let rot_mat = self.rotation_matrix();
164 let scale = self.scale_impl();
165
166 for i in 0..3 {
167 for j in 0..3 {
168 mat[(i, j)] = scale * rot_mat[(i, j)];
169 }
170 }
171
172 mat[(0, 3)] = self.params[0];
173 mat[(1, 3)] = self.params[1];
174 mat[(2, 3)] = self.params[2];
175
176 mat
177 }
178
179 pub fn x(&self) -> f64 {
181 self.params[0]
182 }
183
184 pub fn y(&self) -> f64 {
186 self.params[1]
187 }
188
189 pub fn z(&self) -> f64 {
191 self.params[2]
192 }
193
194 pub fn coeffs(&self) -> [f64; 8] {
196 [
197 self.params[0],
198 self.params[1],
199 self.params[2],
200 self.params[3],
201 self.params[4],
202 self.params[5],
203 self.params[6],
204 self.params[7],
205 ]
206 }
207}
208
209impl LieGroup for Sim3 {
210 const NAME: &'static str = "Sim3";
211
212 type TangentVector = Sim3Tangent;
213 type JacobianMatrix = Matrix7<f64>;
214 type LieAlgebra = Matrix4<f64>;
215
216 fn inverse(&self, jacobian: Option<&mut Self::JacobianMatrix>) -> Self {
220 let rot = self.rotation_impl();
221 let rot_inv = rot.inverse(None);
222 let scale_inv = 1.0 / self.scale_impl();
223 let trans_inv = -rot_inv.act(&self.translation_impl(), None, None) * scale_inv;
224
225 if let Some(jac) = jacobian {
226 *jac = -self.adjoint();
227 }
228
229 Sim3::from_parts(trans_inv, &rot_inv, scale_inv)
230 }
231
232 fn compose(
236 &self,
237 other: &Self,
238 jacobian_self: Option<&mut Self::JacobianMatrix>,
239 jacobian_other: Option<&mut Self::JacobianMatrix>,
240 ) -> Self {
241 let rot = self.rotation_impl();
242 let scale = self.scale_impl();
243 let composed_rotation = rot.compose(&other.rotation_impl(), None, None);
244 let composed_translation =
245 scale * rot.act(&other.translation_impl(), None, None) + self.translation_impl();
246 let composed_scale = scale * other.scale_impl();
247
248 let result = Sim3::from_parts(composed_translation, &composed_rotation, composed_scale);
249
250 if let Some(jac_self) = jacobian_self {
251 *jac_self = other.inverse(None).adjoint();
252 }
253
254 if let Some(jac_other) = jacobian_other {
255 *jac_other = Matrix7::identity();
256 }
257
258 result
259 }
260
261 fn log(&self, jacobian: Option<&mut Self::JacobianMatrix>) -> Self::TangentVector {
263 let theta = self.rotation_impl().log(None);
264 let sigma = self.scale_impl().ln();
265 let mut data = Vector7::zeros();
266
267 let theta_tangent = SO3Tangent::new(theta.coeffs());
270 let v_inv = Self::compute_v_inv(&theta_tangent, sigma);
271 let translation_vector = v_inv * self.translation_impl();
272
273 data.fixed_rows_mut::<3>(0).copy_from(&translation_vector);
274 data.fixed_rows_mut::<3>(3).copy_from(&theta.coeffs());
275 data[6] = sigma;
276
277 let result = Sim3Tangent { data };
278
279 if let Some(jac) = jacobian {
280 *jac = result.right_jacobian_inv();
281 }
282
283 result
284 }
285
286 fn act(
287 &self,
288 vector: &Vector3<f64>,
289 jacobian_self: Option<&mut Self::JacobianMatrix>,
290 jacobian_vector: Option<&mut Matrix3<f64>>,
291 ) -> Vector3<f64> {
292 let rot = self.rotation_impl();
293 let scale = self.scale_impl();
294 let rotation_matrix = rot.rotation_matrix();
295 let result = scale * rot.act(vector, None, None) + self.translation_impl();
296
297 if let Some(jac_self) = jacobian_self {
298 let rotated_vector = rot.act(vector, None, None);
299
300 jac_self
301 .fixed_view_mut::<3, 3>(0, 0)
302 .copy_from(&Matrix3::identity());
303
304 jac_self
305 .fixed_view_mut::<3, 3>(0, 3)
306 .copy_from(&(-scale * rotation_matrix * SO3Tangent::new(*vector).hat()));
307
308 jac_self
309 .fixed_view_mut::<3, 1>(0, 6)
310 .copy_from(&rotated_vector);
311 }
312
313 if let Some(jac_vector) = jacobian_vector {
314 *jac_vector = scale * rotation_matrix;
315 }
316
317 result
318 }
319
320 fn adjoint(&self) -> Self::JacobianMatrix {
321 let rotation_matrix = self.rotation_impl().rotation_matrix();
322 let translation = self.translation_impl();
323 let scale = self.scale_impl();
324 let mut adjoint_matrix = Matrix7::zeros();
325
326 adjoint_matrix
333 .fixed_view_mut::<3, 3>(0, 0)
334 .copy_from(&(scale * rotation_matrix));
335
336 let top_middle = SO3Tangent::new(translation).hat() * scale * rotation_matrix;
338 adjoint_matrix
339 .fixed_view_mut::<3, 3>(0, 3)
340 .copy_from(&top_middle);
341
342 adjoint_matrix
344 .fixed_view_mut::<3, 3>(3, 3)
345 .copy_from(&rotation_matrix);
346
347 adjoint_matrix[(6, 6)] = 1.0;
349
350 adjoint_matrix
351 }
352
353 fn random() -> Self {
354 use rand::Rng;
355 let mut rng = rand::rng();
356
357 let translation = Vector3::new(
358 rng.random_range(-1.0..1.0),
359 rng.random_range(-1.0..1.0),
360 rng.random_range(-1.0..1.0),
361 );
362
363 let rotation = SO3::random();
364 let scale = rng.random_range(0.5..2.0);
365
366 Sim3::from_parts(translation, &rotation, scale)
367 }
368
369 fn normalize(&mut self) {
370 let mut rot = self.rotation_impl();
371 rot.normalize();
372 let q = rot.params();
373 self.params[3] = q[0];
374 self.params[4] = q[1];
375 self.params[5] = q[2];
376 self.params[6] = q[3];
377 if self.params[7] <= 0.0 {
378 self.params[7] = 1.0;
379 }
380 }
381
382 fn is_valid(&self, tolerance: f64) -> bool {
383 self.rotation_impl().is_valid(tolerance) && self.params[7] > 0.0
384 }
385
386 fn as_param_slice(&self) -> &[f64] {
387 self.params.as_slice()
388 }
389
390 fn as_param_slice_mut(&mut self) -> &mut [f64] {
391 self.params.as_mut_slice()
392 }
393
394 fn from_param_slice(s: &[f64]) -> Self {
395 debug_assert_eq!(s.len(), 8);
396 Sim3 {
397 params: SVector::from_column_slice(s),
398 }
399 }
400
401 fn vee(&self) -> Self::TangentVector {
402 self.log(None)
403 }
404
405 fn is_approx(&self, other: &Self, tolerance: f64) -> bool {
406 let difference = self.right_minus(other, None, None);
407 difference.is_zero(tolerance)
408 }
409
410 fn jacobian_identity() -> Self::JacobianMatrix {
411 Matrix7::<f64>::identity()
412 }
413
414 fn zero_jacobian() -> Self::JacobianMatrix {
415 Matrix7::<f64>::zeros()
416 }
417}
418
419impl Sim3 {
420 fn compute_v_inv(theta: &SO3Tangent, sigma: f64) -> Matrix3<f64> {
424 let v = Sim3Tangent::v_matrix(theta, sigma);
425 v.try_inverse().unwrap_or(Matrix3::identity())
429 }
430}
431
432#[derive(Clone, PartialEq)]
439pub struct Sim3Tangent {
440 data: Vector7<f64>,
442}
443
444impl fmt::Display for Sim3Tangent {
445 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
446 let rho = self.rho();
447 let theta = self.theta();
448 let sigma = self.sigma();
449 write!(
450 f,
451 "sim3(rho: [{:.4}, {:.4}, {:.4}], theta: [{:.4}, {:.4}, {:.4}], sigma: {:.4})",
452 rho.x, rho.y, rho.z, theta.x, theta.y, theta.z, sigma
453 )
454 }
455}
456
457impl Sim3Tangent {
458 pub fn new(rho: Vector3<f64>, theta: Vector3<f64>, sigma: f64) -> Self {
460 let mut data = Vector7::zeros();
461 data.fixed_rows_mut::<3>(0).copy_from(&rho);
462 data.fixed_rows_mut::<3>(3).copy_from(&theta);
463 data[6] = sigma;
464 Sim3Tangent { data }
465 }
466
467 pub fn rho(&self) -> Vector3<f64> {
469 self.data.fixed_rows::<3>(0).into_owned()
470 }
471
472 pub fn theta(&self) -> Vector3<f64> {
474 self.data.fixed_rows::<3>(3).into_owned()
475 }
476
477 pub fn sigma(&self) -> f64 {
479 self.data[6]
480 }
481
482 pub fn from_components(
484 rho_x: f64,
485 rho_y: f64,
486 rho_z: f64,
487 theta_x: f64,
488 theta_y: f64,
489 theta_z: f64,
490 sigma: f64,
491 ) -> Self {
492 Sim3Tangent {
493 data: Vector7::from_column_slice(&[
494 rho_x, rho_y, rho_z, theta_x, theta_y, theta_z, sigma,
495 ]),
496 }
497 }
498
499 fn v_matrix(theta: &SO3Tangent, sigma: f64) -> Matrix3<f64> {
505 let theta_norm_sq = theta.coeffs().norm_squared();
506
507 if theta_norm_sq < crate::SMALL_ANGLE_THRESHOLD
508 && sigma.abs() < crate::SMALL_ANGLE_THRESHOLD
509 {
510 return Matrix3::identity();
511 }
512
513 let theta_hat = theta.hat();
514
515 if theta_norm_sq < crate::SMALL_ANGLE_THRESHOLD {
516 let a = (sigma.exp() - 1.0) / sigma;
518 return a * Matrix3::identity();
519 }
520
521 let theta_norm = theta_norm_sq.sqrt();
522 let sin_theta = theta_norm.sin();
523 let cos_theta = theta_norm.cos();
524
525 if sigma.abs() < crate::SMALL_ANGLE_THRESHOLD {
526 let a = 1.0;
528 let b = (1.0 - cos_theta) / theta_norm_sq;
529 let c = (theta_norm - sin_theta) / (theta_norm * theta_norm_sq);
530 return a * Matrix3::identity() + b * theta_hat + c * theta_hat * theta_hat;
531 }
532
533 let e_sigma = sigma.exp();
535 let alpha_sq = sigma * sigma + theta_norm_sq;
536
537 let a = (e_sigma - 1.0) / sigma;
538 let b = (e_sigma * (sigma * sin_theta - theta_norm * cos_theta) + theta_norm)
539 / (theta_norm * alpha_sq);
540 let cos_integral =
541 (e_sigma * (sigma * cos_theta + theta_norm * sin_theta) - sigma) / alpha_sq;
542 let c = (a - cos_integral) / theta_norm_sq;
543
544 a * Matrix3::identity() + b * theta_hat + c * theta_hat * theta_hat
545 }
546
547 fn q_matrix(rho: Vector3<f64>, theta: Vector3<f64>, sigma: f64) -> Matrix3<f64> {
549 let rho_skew = SO3Tangent::new(rho).hat();
550 let theta_skew = SO3Tangent::new(theta).hat();
551 let theta_squared = theta.norm_squared();
552
553 if theta_squared < crate::SMALL_ANGLE_THRESHOLD && sigma.abs() < f64::EPSILON {
554 return 0.5 * rho_skew;
555 }
556
557 let a = 0.5;
558 let mut b = 1.0 / 6.0;
559 let mut c = -1.0 / 24.0;
560 let mut d = -1.0 / 60.0;
561
562 if theta_squared > crate::SMALL_ANGLE_THRESHOLD {
563 let theta_norm = theta_squared.sqrt();
564 let theta_norm_3 = theta_norm * theta_squared;
565 let theta_norm_4 = theta_squared * theta_squared;
566 let theta_norm_5 = theta_norm_3 * theta_squared;
567 let sin_theta = theta_norm.sin();
568 let cos_theta = theta_norm.cos();
569
570 b = (theta_norm - sin_theta) / theta_norm_3;
571 c = (1.0 - theta_squared / 2.0 - cos_theta) / theta_norm_4;
572 d = (c - 3.0) * (theta_norm - sin_theta - theta_norm_3 / 6.0) / theta_norm_5;
573 }
574
575 let rho_skew_theta_skew = rho_skew * theta_skew;
576 let theta_skew_rho_skew = theta_skew * rho_skew;
577 let theta_skew_rho_skew_theta_skew = theta_skew * rho_skew * theta_skew;
578 let rho_skew_theta_skew_sq2 = rho_skew * theta_skew * theta_skew;
579
580 let m1 = rho_skew;
581 let m2 = theta_skew_rho_skew + rho_skew_theta_skew + theta_skew_rho_skew_theta_skew;
582 let m3 = rho_skew_theta_skew_sq2
583 - rho_skew_theta_skew_sq2.transpose()
584 - 3.0 * theta_skew_rho_skew_theta_skew;
585 let m4 = theta_skew_rho_skew_theta_skew * theta_skew;
586
587 m1 * a + m2 * b - m3 * c - m4 * d
588 }
589}
590
591impl Tangent<Sim3> for Sim3Tangent {
592 const DIM: usize = 7;
593
594 fn exp(&self, jacobian: Option<&mut <Sim3 as LieGroup>::JacobianMatrix>) -> Sim3 {
596 let rho = self.rho();
597 let theta = self.theta();
598 let sigma = self.sigma();
599
600 let theta_tangent = SO3Tangent::new(theta);
601 let rotation = theta_tangent.exp(None);
602 let v_matrix = Self::v_matrix(&theta_tangent, sigma);
603 let translation = v_matrix * rho;
604 let scale = sigma.exp();
605
606 if let Some(jac) = jacobian {
607 *jac = self.right_jacobian();
608 }
609
610 Sim3::from_components(translation, rotation, scale)
611 }
612
613 fn right_jacobian(&self) -> <Sim3 as LieGroup>::JacobianMatrix {
615 let mut jac = Matrix7::zeros();
616 let rho = self.rho();
617 let theta = self.theta();
618 let sigma = self.sigma();
619
620 let theta_right_jac = SO3Tangent::new(-theta).right_jacobian();
621 let q_block = Self::q_matrix(-rho, -theta, -sigma);
622
623 jac.fixed_view_mut::<3, 3>(0, 0).copy_from(&theta_right_jac);
625 jac.fixed_view_mut::<3, 3>(3, 3).copy_from(&theta_right_jac);
626 jac.fixed_view_mut::<3, 3>(0, 3).copy_from(&q_block);
627
628 jac[(6, 6)] = 1.0;
630
631 let v_deriv = Self::v_matrix(&SO3Tangent::new(-theta), -sigma);
633 jac.fixed_view_mut::<3, 1>(0, 6)
634 .copy_from(&(v_deriv * (-rho)));
635
636 jac
637 }
638
639 fn left_jacobian(&self) -> <Sim3 as LieGroup>::JacobianMatrix {
641 let mut jac = Matrix7::zeros();
642 let rho = self.rho();
643 let theta = self.theta();
644 let sigma = self.sigma();
645
646 let theta_left_jac = SO3Tangent::new(theta).left_jacobian();
647 let q_block = Self::q_matrix(rho, theta, sigma);
648
649 jac.fixed_view_mut::<3, 3>(0, 0).copy_from(&theta_left_jac);
650 jac.fixed_view_mut::<3, 3>(3, 3).copy_from(&theta_left_jac);
651 jac.fixed_view_mut::<3, 3>(0, 3).copy_from(&q_block);
652
653 jac[(6, 6)] = 1.0;
654
655 let v_deriv = Self::v_matrix(&SO3Tangent::new(theta), sigma);
656 jac.fixed_view_mut::<3, 1>(0, 6).copy_from(&(v_deriv * rho));
657
658 jac
659 }
660
661 fn right_jacobian_inv(&self) -> <Sim3 as LieGroup>::JacobianMatrix {
663 self.right_jacobian()
666 .try_inverse()
667 .unwrap_or(Matrix7::identity())
668 }
669
670 fn left_jacobian_inv(&self) -> <Sim3 as LieGroup>::JacobianMatrix {
672 self.left_jacobian()
675 .try_inverse()
676 .unwrap_or(Matrix7::identity())
677 }
678
679 fn hat(&self) -> <Sim3 as LieGroup>::LieAlgebra {
681 let mut lie_alg = Matrix4::zeros();
682
683 let theta_hat = SO3Tangent::new(self.theta()).hat();
684
685 for i in 0..3 {
687 for j in 0..3 {
688 lie_alg[(i, j)] = theta_hat[(i, j)];
689 }
690 lie_alg[(i, i)] += self.sigma();
691 }
692
693 let rho = self.rho();
695 lie_alg[(0, 3)] = rho[0];
696 lie_alg[(1, 3)] = rho[1];
697 lie_alg[(2, 3)] = rho[2];
698
699 lie_alg
700 }
701
702 fn zero() -> <Sim3 as LieGroup>::TangentVector {
703 Sim3Tangent::new(Vector3::zeros(), Vector3::zeros(), 0.0)
704 }
705
706 fn random() -> <Sim3 as LieGroup>::TangentVector {
707 use rand::Rng;
708 let mut rng = rand::rng();
709 Sim3Tangent::new(
710 Vector3::new(
711 rng.random_range(-1.0..1.0),
712 rng.random_range(-1.0..1.0),
713 rng.random_range(-1.0..1.0),
714 ),
715 Vector3::new(
716 rng.random_range(-0.1..0.1),
717 rng.random_range(-0.1..0.1),
718 rng.random_range(-0.1..0.1),
719 ),
720 rng.random_range(-0.5..0.5),
721 )
722 }
723
724 fn is_zero(&self, tolerance: f64) -> bool {
725 self.data.norm() < tolerance
726 }
727
728 fn normalize(&mut self) {
729 let theta_norm = self.theta().norm();
730 if theta_norm > f64::EPSILON {
731 self.data[3] /= theta_norm;
732 self.data[4] /= theta_norm;
733 self.data[5] /= theta_norm;
734 }
735 }
736
737 fn normalized(&self) -> <Sim3 as LieGroup>::TangentVector {
738 let norm = self.theta().norm();
739 if norm > f64::EPSILON {
740 Sim3Tangent::new(self.rho(), self.theta() / norm, self.sigma())
741 } else {
742 Sim3Tangent::new(self.rho(), Vector3::zeros(), self.sigma())
743 }
744 }
745
746 fn as_slice(&self) -> &[f64] {
747 self.data.as_slice()
748 }
749
750 fn from_slice(s: &[f64]) -> Self {
751 debug_assert_eq!(s.len(), 7);
752 Sim3Tangent {
753 data: Vector7::from_column_slice(s),
754 }
755 }
756
757 fn small_adj(&self) -> <Sim3 as LieGroup>::JacobianMatrix {
758 let mut small_adj = Matrix7::zeros();
759 let rho_skew = SO3Tangent::new(self.rho()).hat();
760 let theta_skew = SO3Tangent::new(self.theta()).hat();
761 let sigma = self.sigma();
762
763 for i in 0..3 {
769 for j in 0..3 {
770 small_adj[(i, j)] = theta_skew[(i, j)];
771 }
772 small_adj[(i, i)] += sigma;
773 }
774
775 small_adj.fixed_view_mut::<3, 3>(0, 3).copy_from(&rho_skew);
776 small_adj
777 .fixed_view_mut::<3, 1>(0, 6)
778 .copy_from(&(-self.rho()));
779 small_adj
780 .fixed_view_mut::<3, 3>(3, 3)
781 .copy_from(&theta_skew);
782
783 small_adj
784 }
785
786 fn lie_bracket(&self, other: &Self) -> <Sim3 as LieGroup>::TangentVector {
787 let bracket_result = self.small_adj() * other.data;
788 Sim3Tangent {
789 data: bracket_result,
790 }
791 }
792
793 fn is_approx(&self, other: &Self, tolerance: f64) -> bool {
794 (self.data - other.data).norm() < tolerance
795 }
796
797 fn generator(&self, i: usize) -> <Sim3 as LieGroup>::LieAlgebra {
798 assert!(i < 7, "Sim(3) only has generators for indices 0-6");
799
800 let mut generator = Matrix4::zeros();
801
802 match i {
803 0..=2 => {
804 generator[(i, 3)] = 1.0;
806 }
807 3..=5 => {
808 let idx = i - 3;
810 match idx {
811 0 => {
812 generator[(1, 2)] = -1.0;
813 generator[(2, 1)] = 1.0;
814 }
815 1 => {
816 generator[(0, 2)] = 1.0;
817 generator[(2, 0)] = -1.0;
818 }
819 2 => {
820 generator[(0, 1)] = -1.0;
821 generator[(1, 0)] = 1.0;
822 }
823 _ => unreachable!(),
824 }
825 }
826 6 => {
827 generator[(0, 0)] = 1.0;
829 generator[(1, 1)] = 1.0;
830 generator[(2, 2)] = 1.0;
831 }
832 _ => unreachable!(),
833 }
834
835 generator
836 }
837}
838
839#[cfg(test)]
840mod tests {
841 use super::*;
842
843 const TOLERANCE: f64 = 1e-9;
844
845 #[test]
846 fn test_sim3_identity() {
847 let identity = Sim3::identity();
848 assert!(identity.is_valid(TOLERANCE));
849 assert!(identity.translation().norm() < TOLERANCE);
850 assert!((identity.scale() - 1.0).abs() < TOLERANCE);
851 assert!(identity.rotation_quaternion().angle() < TOLERANCE);
852 }
853
854 #[test]
855 fn test_sim3_new() {
856 let translation = Vector3::new(1.0, 2.0, 3.0);
857 let rotation = UnitQuaternion::from_euler_angles(0.1, 0.2, 0.3);
858 let scale = 1.5;
859
860 let sim3 = Sim3::new(translation, rotation, scale);
861 assert!(sim3.is_valid(TOLERANCE));
862 assert!((sim3.translation() - translation).norm() < TOLERANCE);
863 assert!((sim3.scale() - scale).abs() < TOLERANCE);
864 }
865
866 #[test]
867 #[should_panic(expected = "Scale must be positive")]
868 fn test_sim3_new_negative_scale() {
869 let translation = Vector3::new(1.0, 2.0, 3.0);
870 let rotation = UnitQuaternion::identity();
871 let _sim3 = Sim3::new(translation, rotation, -1.0);
872 }
873
874 #[test]
875 fn test_sim3_random() {
876 let sim3 = Sim3::random();
877 assert!(sim3.is_valid(TOLERANCE));
878 assert!(sim3.scale() > 0.0);
879 }
880
881 #[test]
882 fn test_sim3_inverse() {
883 let sim3 = Sim3::random();
884 let sim3_inv = sim3.inverse(None);
885
886 let composed = sim3.compose(&sim3_inv, None, None);
887 let identity = Sim3::identity();
888
889 assert!(composed.is_approx(&identity, TOLERANCE));
890 }
891
892 #[test]
893 fn test_sim3_compose() {
894 let sim3_1 = Sim3::random();
895 let sim3_2 = Sim3::random();
896
897 let composed = sim3_1.compose(&sim3_2, None, None);
898 assert!(composed.is_valid(TOLERANCE));
899
900 let identity = Sim3::identity();
901 let composed_with_identity = sim3_1.compose(&identity, None, None);
902 assert!(composed_with_identity.is_approx(&sim3_1, TOLERANCE));
903 }
904
905 #[test]
906 fn test_sim3_exp_log() {
907 let tangent = Sim3Tangent::new(
908 Vector3::new(0.1, 0.2, 0.3),
909 Vector3::new(0.01, 0.02, 0.03),
910 0.1,
911 );
912
913 let sim3 = tangent.exp(None);
914 let recovered_tangent = sim3.log(None);
915
916 assert!((tangent.data - recovered_tangent.data).norm() < TOLERANCE);
917 }
918
919 #[test]
920 fn test_sim3_exp_zero() {
921 let zero_tangent = Sim3Tangent::zero();
922 let sim3 = zero_tangent.exp(None);
923 let identity = Sim3::identity();
924
925 assert!(sim3.is_approx(&identity, TOLERANCE));
926 }
927
928 #[test]
929 fn test_sim3_log_identity() {
930 let identity = Sim3::identity();
931 let tangent = identity.log(None);
932
933 assert!(tangent.data.norm() < TOLERANCE);
934 }
935
936 #[test]
937 fn test_sim3_adjoint() {
938 let sim3 = Sim3::random();
939 let adj = sim3.adjoint();
940
941 assert_eq!(adj.nrows(), 7);
942 assert_eq!(adj.ncols(), 7);
943 }
944
945 #[test]
946 fn test_sim3_act() {
947 let sim3 = Sim3::random();
948 let point = Vector3::new(1.0, 2.0, 3.0);
949
950 let _transformed_point = sim3.act(&point, None, None);
951
952 let identity = Sim3::identity();
953 let identity_transformed = identity.act(&point, None, None);
954
955 assert!((identity_transformed - point).norm() < TOLERANCE);
956 }
957
958 #[test]
959 fn test_sim3_between() {
960 let sim3_a = Sim3::random();
961 let sim3_b = sim3_a.clone();
962 let sim3_between_identity = sim3_a.between(&sim3_b, None, None);
963 assert!(sim3_between_identity.is_approx(&Sim3::identity(), TOLERANCE));
964
965 let sim3_c = Sim3::random();
966 let sim3_between = sim3_a.between(&sim3_c, None, None);
967 let expected = sim3_a.inverse(None).compose(&sim3_c, None, None);
968 assert!(sim3_between.is_approx(&expected, TOLERANCE));
969 }
970
971 #[test]
972 fn test_sim3_tangent_zero() {
973 let zero = Sim3Tangent::zero();
974 assert!(zero.data.norm() < TOLERANCE);
975
976 let tangent = Sim3Tangent::new(Vector3::zeros(), Vector3::zeros(), 0.0);
977 assert!(tangent.is_zero(TOLERANCE));
978 }
979
980 #[test]
981 fn test_sim3_manifold_properties() {
982 assert_eq!(Sim3::DIM, 3);
983 assert_eq!(Sim3::DOF, 7);
984 assert_eq!(Sim3::REP_SIZE, 8);
985 }
986
987 #[test]
988 fn test_sim3_consistency() {
989 let sim3_1 = Sim3::random();
990 let sim3_2 = Sim3::random();
991 let sim3_3 = Sim3::random();
992
993 let left_assoc = sim3_1
995 .compose(&sim3_2, None, None)
996 .compose(&sim3_3, None, None);
997 let right_assoc = sim3_1.compose(&sim3_2.compose(&sim3_3, None, None), None, None);
998
999 assert!(left_assoc.is_approx(&right_assoc, 1e-10));
1000 }
1001
1002 #[test]
1003 fn test_sim3_scale_composition() {
1004 let sim3_1 = Sim3::new(Vector3::zeros(), UnitQuaternion::identity(), 2.0);
1005 let sim3_2 = Sim3::new(Vector3::zeros(), UnitQuaternion::identity(), 3.0);
1006
1007 let composed = sim3_1.compose(&sim3_2, None, None);
1008 assert!((composed.scale() - 6.0).abs() < TOLERANCE);
1009 }
1010
1011 #[test]
1012 fn test_sim3_tangent_small_adj() {
1013 let tangent = Sim3Tangent::new(
1014 Vector3::new(0.1, 0.2, 0.3),
1015 Vector3::new(0.4, 0.5, 0.6),
1016 0.1,
1017 );
1018 let small_adj = tangent.small_adj();
1019
1020 assert_eq!(small_adj.nrows(), 7);
1021 assert_eq!(small_adj.ncols(), 7);
1022 }
1023
1024 #[test]
1025 fn test_sim3_tangent_lie_bracket() {
1026 let tangent_a = Sim3Tangent::new(
1027 Vector3::new(0.1, 0.0, 0.0),
1028 Vector3::new(0.0, 0.2, 0.0),
1029 0.1,
1030 );
1031 let tangent_b = Sim3Tangent::new(
1032 Vector3::new(0.0, 0.3, 0.0),
1033 Vector3::new(0.0, 0.0, 0.4),
1034 0.2,
1035 );
1036
1037 let bracket_ab = tangent_a.lie_bracket(&tangent_b);
1038 let bracket_ba = tangent_b.lie_bracket(&tangent_a);
1039
1040 assert!((bracket_ab.data + bracket_ba.data).norm() < 1e-10);
1042
1043 let bracket_aa = tangent_a.lie_bracket(&tangent_a);
1045 assert!(bracket_aa.is_zero(1e-10));
1046 }
1047
1048 #[test]
1049 fn test_sim3_tangent_is_approx() {
1050 let tangent_1 = Sim3Tangent::new(
1051 Vector3::new(0.1, 0.2, 0.3),
1052 Vector3::new(0.4, 0.5, 0.6),
1053 0.1,
1054 );
1055 let tangent_2 = Sim3Tangent::new(
1056 Vector3::new(0.1 + 1e-12, 0.2, 0.3),
1057 Vector3::new(0.4, 0.5, 0.6),
1058 0.1,
1059 );
1060 let tangent_3 = Sim3Tangent::new(
1061 Vector3::new(1.0, 2.0, 3.0),
1062 Vector3::new(4.0, 5.0, 6.0),
1063 1.0,
1064 );
1065
1066 assert!(tangent_1.is_approx(&tangent_1, 1e-10));
1067 assert!(tangent_1.is_approx(&tangent_2, 1e-10));
1068 assert!(!tangent_1.is_approx(&tangent_3, 1e-10));
1069 }
1070
1071 #[test]
1072 fn test_sim3_generators() {
1073 let tangent = Sim3Tangent::new(
1074 Vector3::new(1.0, 1.0, 1.0),
1075 Vector3::new(1.0, 1.0, 1.0),
1076 1.0,
1077 );
1078
1079 for i in 0..7 {
1080 let generator = tangent.generator(i);
1081 assert_eq!(generator.nrows(), 4);
1082 assert_eq!(generator.ncols(), 4);
1083
1084 assert_eq!(generator[(3, 0)], 0.0);
1086 assert_eq!(generator[(3, 1)], 0.0);
1087 assert_eq!(generator[(3, 2)], 0.0);
1088 assert_eq!(generator[(3, 3)], 0.0);
1089 }
1090 }
1091
1092 #[test]
1093 #[should_panic]
1094 fn test_sim3_generator_invalid_index() {
1095 let tangent = Sim3Tangent::new(
1096 Vector3::new(1.0, 1.0, 1.0),
1097 Vector3::new(1.0, 1.0, 1.0),
1098 1.0,
1099 );
1100 let _generator = tangent.generator(7);
1101 }
1102
1103 #[test]
1104 fn test_sim3_vee() {
1105 let sim3 = Sim3::random();
1106 let tangent_log = sim3.log(None);
1107 let tangent_vee = sim3.vee();
1108
1109 assert!((tangent_log.data - tangent_vee.data).norm() < 1e-10);
1110 }
1111
1112 #[test]
1113 fn test_sim3_is_approx() {
1114 let sim3_1 = Sim3::random();
1115 let sim3_2 = sim3_1.clone();
1116
1117 assert!(sim3_1.is_approx(&sim3_1, 1e-10));
1118 assert!(sim3_1.is_approx(&sim3_2, 1e-10));
1119
1120 let small_tangent = Sim3Tangent::new(
1121 Vector3::new(1e-12, 1e-12, 1e-12),
1122 Vector3::new(1e-12, 1e-12, 1e-12),
1123 1e-12,
1124 );
1125 let sim3_perturbed = sim3_1.right_plus(&small_tangent, None, None);
1126 assert!(sim3_1.is_approx(&sim3_perturbed, 1e-10));
1127 }
1128
1129 #[test]
1130 fn test_sim3_small_angle_approximations() {
1131 let small_tangent = Sim3Tangent::new(
1132 Vector3::new(1e-8, 2e-8, 3e-8),
1133 Vector3::new(1e-9, 2e-9, 3e-9),
1134 1e-8,
1135 );
1136
1137 let sim3 = small_tangent.exp(None);
1138 let recovered = sim3.log(None);
1139
1140 assert!((small_tangent.data - recovered.data).norm() < TOLERANCE);
1141 }
1142
1143 #[test]
1144 fn test_sim3_accessors() {
1145 let translation = Vector3::new(1.0, 2.0, 3.0);
1146 let rotation = UnitQuaternion::identity();
1147 let scale = 1.5;
1148
1149 let sim3 = Sim3::new(translation, rotation, scale);
1150
1151 assert_eq!(sim3.x(), 1.0);
1152 assert_eq!(sim3.y(), 2.0);
1153 assert_eq!(sim3.z(), 3.0);
1154 assert_eq!(sim3.scale(), 1.5);
1155 }
1156
1157 #[test]
1158 fn test_sim3_matrix() {
1159 let translation = Vector3::new(1.0, 2.0, 3.0);
1160 let rotation = UnitQuaternion::identity();
1161 let scale = 2.0;
1162
1163 let sim3 = Sim3::new(translation, rotation, scale);
1164 let mat = sim3.matrix();
1165
1166 for i in 0..3 {
1168 for j in 0..3 {
1169 if i == j {
1170 assert!((mat[(i, j)] - 2.0).abs() < TOLERANCE);
1171 } else {
1172 assert!(mat[(i, j)].abs() < TOLERANCE);
1173 }
1174 }
1175 }
1176
1177 assert!((mat[(0, 3)] - 1.0).abs() < TOLERANCE);
1179 assert!((mat[(1, 3)] - 2.0).abs() < TOLERANCE);
1180 assert!((mat[(2, 3)] - 3.0).abs() < TOLERANCE);
1181
1182 assert!(mat[(3, 0)].abs() < TOLERANCE);
1184 assert!(mat[(3, 1)].abs() < TOLERANCE);
1185 assert!(mat[(3, 2)].abs() < TOLERANCE);
1186 assert!((mat[(3, 3)] - 1.0).abs() < TOLERANCE);
1187 }
1188
1189 #[test]
1190 fn test_sim3_scale_action() {
1191 let scale = 2.0;
1192 let sim3 = Sim3::new(Vector3::zeros(), UnitQuaternion::identity(), scale);
1193 let point = Vector3::new(1.0, 2.0, 3.0);
1194
1195 let transformed = sim3.act(&point, None, None);
1196
1197 assert!((transformed.x - 2.0).abs() < TOLERANCE);
1198 assert!((transformed.y - 4.0).abs() < TOLERANCE);
1199 assert!((transformed.z - 6.0).abs() < TOLERANCE);
1200 }
1201
1202 #[test]
1203 fn test_sim3_tangent_basic() {
1204 let rho = Vector3::new(0.1, 0.2, 0.3);
1205 let theta = Vector3::new(0.4, 0.5, 0.6);
1206 let sigma = 0.1;
1207 let tangent = Sim3Tangent::new(rho, theta, sigma);
1208 assert!((tangent.rho() - rho).norm() < TOLERANCE);
1209 assert!((tangent.theta() - theta).norm() < TOLERANCE);
1210 assert!((tangent.sigma() - sigma).abs() < TOLERANCE);
1211 }
1212
1213 #[test]
1214 fn test_sim3_tangent_from_components() {
1215 let t1 = Sim3Tangent::new(
1216 Vector3::new(1.0, 2.0, 3.0),
1217 Vector3::new(0.4, 0.5, 0.6),
1218 0.1,
1219 );
1220 let t2 = Sim3Tangent::from_components(1.0, 2.0, 3.0, 0.4, 0.5, 0.6, 0.1);
1221 assert!(t1.is_approx(&t2, TOLERANCE));
1222 }
1223
1224 #[test]
1225 fn test_sim3_normalize() {
1226 let mut sim3 = Sim3::random();
1227 sim3.normalize();
1228 assert!(sim3.is_valid(TOLERANCE));
1229 }
1230
1231 #[test]
1232 fn test_sim3_coeffs() {
1233 let translation = Vector3::new(1.0, 2.0, 3.0);
1234 let rotation = UnitQuaternion::identity();
1235 let scale = 1.5;
1236 let sim3 = Sim3::new(translation, rotation, scale);
1237 let c = sim3.coeffs();
1238 assert!((c[0] - 1.0).abs() < TOLERANCE);
1239 assert!((c[1] - 2.0).abs() < TOLERANCE);
1240 assert!((c[2] - 3.0).abs() < TOLERANCE);
1241 assert!((c[3] - 1.0).abs() < TOLERANCE); assert!((c[7] - 1.5).abs() < TOLERANCE); }
1244
1245 #[test]
1246 fn test_sim3_manif_like_operations() {
1247 let a = Sim3::random();
1248 let b = Sim3::random();
1249
1250 let a_to_b = a.between(&b, None, None);
1251 let recovered_b = a.compose(&a_to_b, None, None);
1252 assert!(recovered_b.is_approx(&b, TOLERANCE));
1253
1254 let tangent = Sim3Tangent::new(
1255 Vector3::new(0.01, 0.02, 0.03),
1256 Vector3::new(0.001, 0.002, 0.003),
1257 0.01,
1258 );
1259 let perturbed = a.plus(&tangent, None, None);
1260 let recovered_tangent = perturbed.minus(&a, None, None);
1261 assert!((tangent.data - recovered_tangent.data).norm() < 1e-6);
1262 }
1263
1264 #[test]
1265 fn test_sim3_right_jacobian_inverse_identity() {
1266 let tangent = Sim3Tangent::new(
1267 Vector3::new(0.1, 0.2, 0.3),
1268 Vector3::new(0.01, 0.02, 0.03),
1269 0.1,
1270 );
1271 let jr = tangent.right_jacobian();
1272 let jr_inv = tangent.right_jacobian_inv();
1273 let product = jr * jr_inv;
1274 let identity = Matrix7::<f64>::identity();
1275 assert!((product - identity).norm() < 0.01);
1276 }
1277
1278 #[test]
1279 fn test_sim3_left_jacobian_inverse_identity() {
1280 let tangent = Sim3Tangent::new(
1281 Vector3::new(0.1, 0.2, 0.3),
1282 Vector3::new(0.01, 0.02, 0.03),
1283 0.1,
1284 );
1285 let jl = tangent.left_jacobian();
1286 let jl_inv = tangent.left_jacobian_inv();
1287 let product = jl * jl_inv;
1288 let identity = Matrix7::<f64>::identity();
1289 assert!((product - identity).norm() < 0.01);
1290 }
1291
1292 #[test]
1293 fn test_sim3_jacobi_identity() {
1294 let a = Sim3Tangent::random();
1295 let b = Sim3Tangent::random();
1296 let c = Sim3Tangent::random();
1297
1298 let term1 = a.lie_bracket(&b.lie_bracket(&c));
1299 let term2 = b.lie_bracket(&c.lie_bracket(&a));
1300 let term3 = c.lie_bracket(&a.lie_bracket(&b));
1301
1302 assert!((term1.data + term2.data + term3.data).norm() < 1e-8);
1303 }
1304
1305 #[test]
1306 fn test_sim3_hat_matrix_structure() {
1307 let tangent = Sim3Tangent::new(
1308 Vector3::new(1.0, 2.0, 3.0),
1309 Vector3::new(0.1, 0.2, 0.3),
1310 0.5,
1311 );
1312 let hat = tangent.hat();
1313
1314 for j in 0..4 {
1315 assert_eq!(hat[(3, j)], 0.0);
1316 }
1317
1318 assert!((hat[(0, 3)] - 1.0).abs() < TOLERANCE);
1319 assert!((hat[(1, 3)] - 2.0).abs() < TOLERANCE);
1320 assert!((hat[(2, 3)] - 3.0).abs() < TOLERANCE);
1321
1322 assert!((hat[(0, 0)] - 0.5).abs() < 0.5); }
1325
1326 #[test]
1327 fn test_sim3_param_slice_round_trip() {
1328 let sim3 = Sim3::random();
1329 let recovered = Sim3::from_param_slice(sim3.as_param_slice());
1330 assert!(sim3.is_approx(&recovered, TOLERANCE));
1331 }
1332
1333 #[test]
1336 fn test_sim3_display() {
1337 let sim3 = Sim3::identity();
1338 let s = format!("{}", sim3);
1339 assert!(s.contains("Sim3"));
1340
1341 let tangent = Sim3Tangent::new(
1342 Vector3::new(1.0, 2.0, 3.0),
1343 Vector3::new(0.1, 0.2, 0.3),
1344 0.5,
1345 );
1346 let ts = format!("{}", tangent);
1347 assert!(ts.contains("sim3"));
1348 }
1349
1350 #[test]
1351 fn test_sim3_jacobian_identity_static() {
1352 let jac = Sim3::jacobian_identity();
1353 assert!((jac - Matrix7::identity()).norm() < TOLERANCE);
1354 }
1355
1356 #[test]
1357 fn test_sim3_rotation_so3() {
1358 let sim3 = Sim3::random();
1359 let so3 = sim3.rotation_so3();
1360 assert!(so3.is_valid(TOLERANCE));
1361 }
1362
1363 #[test]
1364 fn test_sim3_inverse_with_jacobian() {
1365 let sim3 = Sim3::random();
1366 let mut jac = Matrix7::zeros();
1367 let inv = sim3.inverse(Some(&mut jac));
1368 let expected_jac = -sim3.adjoint();
1369 assert!((jac - expected_jac).norm() < TOLERANCE);
1370 let composed = sim3.compose(&inv, None, None);
1371 assert!(composed.is_approx(&Sim3::identity(), TOLERANCE));
1372 }
1373
1374 #[test]
1375 fn test_sim3_compose_with_jacobians() {
1376 let a = Sim3::random();
1377 let b = Sim3::random();
1378 let mut jac_a = Matrix7::zeros();
1379 let mut jac_b = Matrix7::zeros();
1380 let _composed = a.compose(&b, Some(&mut jac_a), Some(&mut jac_b));
1381 assert!(jac_a.norm() > 0.0);
1382 assert!(jac_b.norm() > 0.0);
1383 }
1384
1385 #[test]
1386 fn test_sim3_act_with_jacobians() {
1387 let sim3 = Sim3::random();
1388 let point = Vector3::new(1.0, 2.0, 3.0);
1389 let mut jac_self = Matrix7::zeros();
1390 let mut jac_point = Matrix3::<f64>::zeros();
1391 let result = sim3.act(&point, Some(&mut jac_self), Some(&mut jac_point));
1392 assert!(result.norm() > 0.0);
1393 assert!(jac_self.norm() > 0.0);
1394 assert!(jac_point.norm() > 0.0);
1395 }
1396
1397 #[test]
1398 fn test_sim3_liegroup_jacobian_identity() {
1399 let jac = <Sim3 as LieGroup>::jacobian_identity();
1400 assert!((jac - Matrix7::identity()).norm() < TOLERANCE);
1401
1402 let zero = <Sim3 as LieGroup>::zero_jacobian();
1403 assert!(zero.norm() < TOLERANCE);
1404 }
1405
1406 #[test]
1407 fn test_sim3_tangent_slice_round_trip() {
1408 let tangent = Sim3Tangent::new(
1409 Vector3::new(1.0, 2.0, 3.0),
1410 Vector3::new(0.1, 0.2, 0.3),
1411 0.5,
1412 );
1413 let recovered = Sim3Tangent::from_slice(tangent.as_slice());
1414 assert!(tangent.is_approx(&recovered, TOLERANCE));
1415 }
1416
1417 #[test]
1418 fn test_sim3_exp_with_jacobian() {
1419 let tangent = Sim3Tangent::new(
1420 Vector3::new(0.1, 0.2, 0.3),
1421 Vector3::new(0.01, 0.02, 0.03),
1422 0.1,
1423 );
1424 let mut jac = Matrix7::zeros();
1425 let _result = tangent.exp(Some(&mut jac));
1426 assert!(jac.norm() > 0.0);
1427 }
1428
1429 #[test]
1430 fn test_sim3_exp_log_stress() {
1431 for _ in 0..100 {
1432 let tangent = Sim3Tangent::random();
1433 let sim3 = tangent.exp(None);
1434 let recovered = sim3.log(None);
1435 assert!(
1436 tangent.is_approx(&recovered, 1e-6),
1437 "exp/log round-trip failed: error = {}",
1438 (tangent.data - recovered.data).norm()
1439 );
1440 }
1441 }
1442
1443 #[test]
1444 fn test_sim3_right_plus_minus_round_trip() {
1445 let a = Sim3::random();
1446 let b = Sim3::random();
1447 let diff = a.right_minus(&b, None, None);
1448 let recovered = b.right_plus(&diff, None, None);
1449 assert!(a.is_approx(&recovered, 1e-6));
1450 }
1451
1452 #[test]
1453 fn test_sim3_left_plus_minus_round_trip() {
1454 let a = Sim3::random();
1455 let b = Sim3::random();
1456 let diff = a.left_minus(&b, None, None);
1457 let recovered = b.left_plus(&diff, None, None);
1458 assert!(a.is_approx(&recovered, 1e-6));
1459 }
1460
1461 #[test]
1462 fn test_sim3_compose_associativity() {
1463 let a = Sim3::random();
1464 let b = Sim3::random();
1465 let c = Sim3::random();
1466 let ab_c = a.compose(&b, None, None).compose(&c, None, None);
1467 let a_bc = a.compose(&b.compose(&c, None, None), None, None);
1468 assert!(ab_c.is_approx(&a_bc, 1e-6));
1469 }
1470
1471 #[test]
1472 fn test_sim3_inverse_twice() {
1473 let g = Sim3::random();
1474 let g_inv_inv = g.inverse(None).inverse(None);
1475 assert!(g.is_approx(&g_inv_inv, 1e-6));
1476 }
1477
1478 #[test]
1479 fn test_sim3_pure_scale() {
1480 let tangent = Sim3Tangent::new(Vector3::zeros(), Vector3::zeros(), 0.5);
1482 let sim3 = tangent.exp(None);
1483 let recovered = sim3.log(None);
1484 assert!(tangent.is_approx(&recovered, TOLERANCE));
1485 assert!((sim3.scale() - 0.5_f64.exp()).abs() < TOLERANCE);
1486 }
1487
1488 #[test]
1489 fn test_sim3_pure_rotation() {
1490 let tangent = Sim3Tangent::new(
1492 Vector3::new(0.1, 0.2, 0.3),
1493 Vector3::new(0.05, 0.1, 0.15),
1494 0.0,
1495 );
1496 let sim3 = tangent.exp(None);
1497 let recovered = sim3.log(None);
1498 assert!(tangent.is_approx(&recovered, 1e-6));
1499 assert!((sim3.scale() - 1.0).abs() < TOLERANCE);
1500 }
1501
1502 #[test]
1503 fn test_sim3_scale_compose() {
1504 let a = Sim3::from_components(Vector3::zeros(), SO3::identity(), 2.0);
1505 let b = Sim3::from_components(Vector3::zeros(), SO3::identity(), 3.0);
1506 let ab = a.compose(&b, None, None);
1507 assert!((ab.scale() - 6.0).abs() < TOLERANCE);
1508 }
1509
1510 #[test]
1511 fn test_sim3_tangent_normalize_zero_theta() {
1512 let tangent = Sim3Tangent::new(Vector3::new(1.0, 2.0, 3.0), Vector3::zeros(), 0.5);
1513 let normalized = tangent.normalized();
1514 assert!(normalized.theta().norm() < TOLERANCE);
1515 }
1516
1517 #[test]
1518 fn test_sim3_tangent_generator_all() {
1519 let tangent = Sim3Tangent::zero();
1520 for i in 0..7 {
1521 let g = tangent.generator(i);
1522 assert!(g.norm() > 0.0, "Generator {} should be non-zero", i);
1523 }
1524 }
1525
1526 #[test]
1527 fn test_sim3_v_matrix_sigma_zero_matches_so3_jl() {
1528 let theta = SO3Tangent::new(Vector3::new(0.3, 0.4, 0.5));
1530 let v = Sim3Tangent::v_matrix(&theta, 0.0);
1531 let jl = theta.left_jacobian();
1532 assert!(
1533 (v - jl).norm() < 1e-10,
1534 "V(θ,0) should equal Jl(θ), error = {}",
1535 (v - jl).norm()
1536 );
1537 }
1538
1539 #[test]
1540 fn test_sim3_v_matrix_theta_zero() {
1541 let theta = SO3Tangent::new(Vector3::zeros());
1543 let sigma = 0.5;
1544 let v = Sim3Tangent::v_matrix(&theta, sigma);
1545 let expected = (sigma.exp() - 1.0) / sigma * Matrix3::<f64>::identity();
1546 assert!(
1547 (v - expected).norm() < 1e-10,
1548 "V(0,σ) should equal (e^σ-1)/σ * I, error = {}",
1549 (v - expected).norm()
1550 );
1551 }
1552
1553 #[test]
1554 fn sim3_param_slice_round_trip() {
1555 let g = Sim3::random();
1556 let recovered = Sim3::from_param_slice(g.as_param_slice());
1557 assert!(g.is_approx(&recovered, 1e-14));
1558 }
1559
1560 #[test]
1561 fn sim3_tangent_slice_round_trip() {
1562 let t = Sim3Tangent::random();
1563 let recovered = Sim3Tangent::from_slice(t.as_slice());
1564 assert!(t.is_approx(&recovered, 1e-14));
1565 }
1566}