1use crate::{LieGroup, Tangent, so2::SO2};
14use nalgebra::{
15 Complex, DVector, Isometry2, Matrix2, Matrix3, Point2, Translation2, UnitComplex, Vector2,
16 Vector3,
17};
18use std::{
19 fmt,
20 fmt::{Display, Formatter},
21};
22
23#[derive(Clone, PartialEq)]
27pub struct SE2 {
28 translation: Vector2<f64>,
30 rotation: UnitComplex<f64>,
32}
33
34impl Display for SE2 {
35 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
36 let t = self.translation();
37 write!(
38 f,
39 "SE2(translation: [{:.4}, {:.4}], rotation: {:.4})",
40 t.x,
41 t.y,
42 self.angle()
43 )
44 }
45}
46
47impl From<DVector<f64>> for SE2 {
49 fn from(data: DVector<f64>) -> Self {
50 SE2::from_xy_angle(data[0], data[1], data[2])
52 }
53}
54
55impl From<SE2> for DVector<f64> {
56 fn from(se2: SE2) -> Self {
57 DVector::from_vec(vec![
58 se2.translation.x, se2.translation.y, se2.rotation.angle(), ])
62 }
63}
64
65impl SE2 {
66 pub const DIM: usize = 2;
68
69 pub const DOF: usize = 3;
71
72 pub const REP_SIZE: usize = 4;
74
75 pub fn identity() -> Self {
79 SE2 {
80 translation: Vector2::zeros(),
81 rotation: UnitComplex::identity(),
82 }
83 }
84
85 pub fn jacobian_identity() -> Matrix3<f64> {
89 Matrix3::<f64>::identity()
90 }
91
92 #[inline]
98 pub fn new(translation: Vector2<f64>, rotation: UnitComplex<f64>) -> Self {
99 SE2 {
100 translation,
101 rotation,
102 }
103 }
104
105 pub fn from_xy_angle(x: f64, y: f64, theta: f64) -> Self {
107 let translation = Vector2::new(x, y);
108 let rotation = UnitComplex::from_angle(theta);
109 Self::new(translation, rotation)
110 }
111
112 pub fn from_xy_complex(x: f64, y: f64, real: f64, imag: f64) -> Self {
114 let translation = Vector2::new(x, y);
115 let complex = Complex::new(real, imag);
116 let rotation = UnitComplex::from_complex(complex);
117 Self::new(translation, rotation)
118 }
119
120 pub fn from_isometry(isometry: Isometry2<f64>) -> Self {
122 SE2 {
123 translation: isometry.translation.vector,
124 rotation: isometry.rotation,
125 }
126 }
127
128 pub fn from_translation_so2(translation: Vector2<f64>, rotation: SO2) -> Self {
130 SE2 {
131 translation,
132 rotation: rotation.complex(),
133 }
134 }
135
136 pub fn translation(&self) -> Vector2<f64> {
138 self.translation
139 }
140
141 pub fn rotation_complex(&self) -> UnitComplex<f64> {
143 self.rotation
144 }
145
146 pub fn rotation_angle(&self) -> f64 {
148 self.rotation.angle()
149 }
150
151 pub fn rotation_so2(&self) -> SO2 {
153 SO2::new(self.rotation)
154 }
155
156 pub fn isometry(&self) -> Isometry2<f64> {
158 Isometry2::from_parts(Translation2::from(self.translation), self.rotation)
159 }
160
161 pub fn matrix(&self) -> Matrix3<f64> {
163 self.isometry().to_homogeneous()
164 }
165
166 pub fn rotation_matrix(&self) -> Matrix2<f64> {
168 self.rotation.to_rotation_matrix().into_inner()
169 }
170
171 #[inline]
173 pub fn x(&self) -> f64 {
174 self.translation.x
175 }
176
177 #[inline]
179 pub fn y(&self) -> f64 {
180 self.translation.y
181 }
182
183 pub fn real(&self) -> f64 {
185 self.rotation.re
186 }
187
188 pub fn imag(&self) -> f64 {
190 self.rotation.im
191 }
192
193 #[inline]
195 pub fn angle(&self) -> f64 {
196 self.rotation.angle()
197 }
198}
199
200impl LieGroup for SE2 {
202 type TangentVector = SE2Tangent;
203 type JacobianMatrix = Matrix3<f64>;
204 type LieAlgebra = Matrix3<f64>;
205
206 fn inverse(&self, jacobian: Option<&mut Self::JacobianMatrix>) -> Self {
214 let rot_inv = self.rotation.inverse();
215 let trans_inv = -(rot_inv * self.translation);
216
217 if let Some(jac) = jacobian {
218 *jac = -self.adjoint();
219 }
220
221 SE2::new(trans_inv, rot_inv)
222 }
223
224 fn compose(
226 &self,
227 other: &Self,
228 jacobian_self: Option<&mut Self::JacobianMatrix>,
229 jacobian_other: Option<&mut Self::JacobianMatrix>,
230 ) -> Self {
231 let composed_rotation = self.rotation * other.rotation;
232 let composed_translation = self
233 .rotation
234 .transform_point(&Point2::from(other.translation))
235 .coords
236 + self.translation;
237
238 let result = SE2::new(composed_translation, composed_rotation);
239
240 if let Some(jac_self) = jacobian_self {
241 *jac_self = other.inverse(None).adjoint();
242 }
243
244 if let Some(jac_other) = jacobian_other {
245 *jac_other = Matrix3::identity();
246 }
247
248 result
249 }
250
251 fn log(&self, jacobian: Option<&mut Self::JacobianMatrix>) -> Self::TangentVector {
253 let theta = self.angle();
254 let cos_theta = theta.cos();
255 let sin_theta = theta.sin();
256 let theta_sq = theta * theta;
257
258 let (a, b) = if theta_sq < crate::SMALL_ANGLE_THRESHOLD {
259 let a = 1.0 - theta_sq / 6.0;
261 let b = 0.5 * theta - theta * theta_sq / 24.0;
262 (a, b)
263 } else {
264 let a = sin_theta / theta;
266 let b = (1.0 - cos_theta) / theta;
267 (a, b)
268 };
269
270 let den = 1.0 / (a * a + b * b);
271 let a_scaled = a * den;
272 let b_scaled = b * den;
273
274 let x = a_scaled * self.x() + b_scaled * self.y();
275 let y = -b_scaled * self.x() + a_scaled * self.y();
276
277 let result = SE2Tangent::new(x, y, theta);
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 point2d = Vector2::new(vector.x, vector.y);
294 let transformed_2d =
295 self.rotation.transform_point(&Point2::from(point2d)).coords + self.translation;
296 let result = Vector3::new(transformed_2d.x, transformed_2d.y, vector.z);
297
298 if let Some(jac_self) = jacobian_self {
299 let r = self.rotation_matrix();
300 jac_self.fixed_view_mut::<2, 2>(0, 0).copy_from(&r);
301 jac_self[(0, 2)] = -point2d.y;
302 jac_self[(1, 2)] = point2d.x;
303 jac_self[(2, 0)] = 0.0;
304 jac_self[(2, 1)] = 0.0;
305 jac_self[(2, 2)] = 1.0;
306 }
307
308 if let Some(jac_vector) = jacobian_vector {
309 *jac_vector = Matrix3::identity();
310 let r = self.rotation_matrix();
311 jac_vector.fixed_view_mut::<2, 2>(0, 0).copy_from(&r);
312 }
313
314 result
315 }
316
317 fn adjoint(&self) -> Self::JacobianMatrix {
318 let mut adjoint_matrix = Matrix3::identity();
319 let rotation_matrix = self.rotation_matrix();
320
321 adjoint_matrix
322 .fixed_view_mut::<2, 2>(0, 0)
323 .copy_from(&rotation_matrix);
324 adjoint_matrix[(0, 2)] = self.y();
325 adjoint_matrix[(1, 2)] = -self.x();
326
327 adjoint_matrix
328 }
329
330 fn random() -> Self {
331 use rand::Rng;
332 let mut rng = rand::rng();
333
334 let translation = Vector2::new(rng.random_range(-1.0..1.0), rng.random_range(-1.0..1.0));
336
337 let angle = rng.random_range(-std::f64::consts::PI..std::f64::consts::PI);
339 let rotation = UnitComplex::from_angle(angle);
340
341 SE2::new(translation, rotation)
342 }
343
344 fn jacobian_identity() -> Self::JacobianMatrix {
345 Matrix3::<f64>::identity()
346 }
347
348 fn zero_jacobian() -> Self::JacobianMatrix {
349 Matrix3::<f64>::zeros()
350 }
351
352 fn normalize(&mut self) {
353 self.rotation.renormalize();
354 }
355
356 fn is_valid(&self, tolerance: f64) -> bool {
357 (self.rotation.norm() - 1.0).abs() < tolerance
358 }
359
360 fn vee(&self) -> Self::TangentVector {
365 self.log(None)
366 }
367
368 fn is_approx(&self, other: &Self, tolerance: f64) -> bool {
374 let difference = self.right_minus(other, None, None);
375 difference.is_zero(tolerance)
376 }
377}
378
379#[derive(Clone, PartialEq)]
385pub struct SE2Tangent {
386 data: Vector3<f64>,
388}
389
390impl fmt::Display for SE2Tangent {
391 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
392 write!(
393 f,
394 "SE2Tangent(x: {:.4}, y: {:.4}, theta: {:.4})",
395 self.x(),
396 self.y(),
397 self.angle()
398 )
399 }
400}
401
402impl From<DVector<f64>> for SE2Tangent {
404 fn from(data_vector: DVector<f64>) -> Self {
405 SE2Tangent {
408 data: Vector3::new(data_vector[0], data_vector[1], data_vector[2]),
409 }
410 }
411}
412
413impl From<SE2Tangent> for DVector<f64> {
414 fn from(se2_tangent: SE2Tangent) -> Self {
415 DVector::from_vec(vec![
416 se2_tangent.data[0], se2_tangent.data[1], se2_tangent.data[2], ])
420 }
421}
422
423impl SE2Tangent {
424 #[inline]
426 pub fn new(x: f64, y: f64, theta: f64) -> Self {
427 SE2Tangent {
428 data: Vector3::new(x, y, theta),
429 }
430 }
431
432 #[inline]
434 pub fn from_components(x: f64, y: f64, theta: f64) -> Self {
435 SE2Tangent::new(x, y, theta)
436 }
437
438 #[inline]
440 pub fn x(&self) -> f64 {
441 self.data[0]
442 }
443
444 #[inline]
446 pub fn y(&self) -> f64 {
447 self.data[1]
448 }
449
450 #[inline]
452 pub fn angle(&self) -> f64 {
453 self.data[2]
454 }
455
456 #[inline]
458 pub fn translation(&self) -> Vector2<f64> {
459 Vector2::new(self.x(), self.y())
460 }
461}
462
463impl Tangent<SE2> for SE2Tangent {
464 const DIM: usize = 3;
466
467 fn exp(&self, jacobian: Option<&mut <SE2 as LieGroup>::JacobianMatrix>) -> SE2 {
469 let theta = self.angle();
470 let cos_theta = theta.cos();
471 let sin_theta = theta.sin();
472 let theta_sq = theta * theta;
473
474 let (a, b) = if theta_sq < crate::SMALL_ANGLE_THRESHOLD {
475 let a = 1.0 - theta_sq / 6.0;
477 let b = 0.5 * theta - theta * theta_sq / 24.0;
478 (a, b)
479 } else {
480 let a = sin_theta / theta;
482 let b = (1.0 - cos_theta) / theta;
483 (a, b)
484 };
485
486 let translation = Vector2::new(a * self.x() - b * self.y(), b * self.x() + a * self.y());
487 let rotation = UnitComplex::from_cos_sin_unchecked(cos_theta, sin_theta);
488
489 if let Some(jac) = jacobian {
490 *jac = self.right_jacobian();
491 }
492
493 SE2::new(translation, rotation)
494 }
495
496 fn right_jacobian(&self) -> <SE2 as LieGroup>::JacobianMatrix {
498 let theta = self.angle();
499 let cos_theta = theta.cos();
500 let sin_theta = theta.sin();
501 let theta_sq = theta * theta;
502
503 let (a, b) = if theta_sq < crate::SMALL_ANGLE_THRESHOLD {
504 let a = 1.0 - theta_sq / 6.0;
506 let b = 0.5 * theta - theta * theta_sq / 24.0;
507 (a, b)
508 } else {
509 let a = sin_theta / theta;
511 let b = (1.0 - cos_theta) / theta;
512 (a, b)
513 };
514
515 let mut jac = Matrix3::identity();
516 jac[(0, 0)] = a;
517 jac[(0, 1)] = b;
518 jac[(1, 0)] = -b;
519 jac[(1, 1)] = a;
520
521 if theta_sq < crate::SMALL_ANGLE_THRESHOLD {
522 jac[(0, 2)] = -self.y() / 2.0 + theta * self.x() / 6.0;
523 jac[(1, 2)] = self.x() / 2.0 + theta * self.y() / 6.0;
524 } else {
525 jac[(0, 2)] = (-self.y() + theta * self.x() + self.y() * cos_theta
526 - self.x() * sin_theta)
527 / theta_sq;
528 jac[(1, 2)] =
529 (self.x() + theta * self.y() - self.x() * cos_theta - self.y() * sin_theta)
530 / theta_sq;
531 }
532
533 jac
534 }
535
536 fn left_jacobian(&self) -> <SE2 as LieGroup>::JacobianMatrix {
538 let theta = self.angle();
539 let cos_theta = theta.cos();
540 let sin_theta = theta.sin();
541 let theta_sq = theta * theta;
542
543 let (a, b) = if theta_sq < crate::SMALL_ANGLE_THRESHOLD {
544 let a = 1.0 - theta_sq / 6.0;
546 let b = 0.5 * theta - theta * theta_sq / 24.0;
547 (a, b)
548 } else {
549 let a = sin_theta / theta;
551 let b = (1.0 - cos_theta) / theta;
552 (a, b)
553 };
554
555 let mut jac = Matrix3::identity();
556 jac[(0, 0)] = a;
557 jac[(0, 1)] = -b;
558 jac[(1, 0)] = b;
559 jac[(1, 1)] = a;
560
561 if theta_sq < crate::SMALL_ANGLE_THRESHOLD {
562 jac[(0, 2)] = self.y() / 2.0 + theta * self.x() / 6.0;
563 jac[(1, 2)] = -self.x() / 2.0 + theta * self.y() / 6.0;
564 } else {
565 jac[(0, 2)] =
566 (self.y() + theta * self.x() - self.y() * cos_theta - self.x() * sin_theta)
567 / theta_sq;
568 jac[(1, 2)] = (-self.x() + theta * self.y() + self.x() * cos_theta
569 - self.y() * sin_theta)
570 / theta_sq;
571 }
572
573 jac
574 }
575
576 fn right_jacobian_inv(&self) -> <SE2 as LieGroup>::JacobianMatrix {
578 let theta = self.angle();
579 let cos_theta = theta.cos();
580 let sin_theta = theta.sin();
581 let theta_sq = theta * theta;
582
583 let mut jac_inv = Matrix3::zeros();
584 jac_inv[(0, 1)] = -theta * 0.5;
585 jac_inv[(1, 0)] = -jac_inv[(0, 1)];
586 jac_inv[(2, 2)] = 1.0;
587
588 if theta_sq > crate::SMALL_ANGLE_THRESHOLD {
589 let a = theta * sin_theta;
590 let b = theta * cos_theta;
591
592 jac_inv[(0, 0)] = -a / (2.0 * cos_theta - 2.0);
593 jac_inv[(1, 1)] = jac_inv[(0, 0)];
594
595 let den = 2.0 * theta * (cos_theta - 1.0);
596 jac_inv[(0, 2)] = (a * self.x() + b * self.y() - theta * self.y()
597 + 2.0 * self.x() * cos_theta
598 - 2.0 * self.x())
599 / den;
600 jac_inv[(1, 2)] =
601 (-b * self.x() + a * self.y() + theta * self.x() + 2.0 * self.y() * cos_theta
602 - 2.0 * self.y())
603 / den;
604 } else {
605 jac_inv[(0, 0)] = 1.0 - theta_sq / 12.0;
606 jac_inv[(1, 1)] = jac_inv[(0, 0)];
607
608 jac_inv[(0, 2)] = self.y() / 2.0 + theta * self.x() / 12.0;
609 jac_inv[(1, 2)] = -self.x() / 2.0 + theta * self.y() / 12.0;
610 }
611
612 jac_inv
613 }
614
615 fn left_jacobian_inv(&self) -> <SE2 as LieGroup>::JacobianMatrix {
617 let theta = self.angle();
618 let cos_theta = theta.cos();
619 let sin_theta = theta.sin();
620 let theta_sq = theta * theta;
621
622 let mut jac_inv = Matrix3::zeros();
623 jac_inv[(0, 1)] = theta * 0.5;
624 jac_inv[(1, 0)] = -jac_inv[(0, 1)];
625 jac_inv[(2, 2)] = 1.0;
626
627 if theta_sq > crate::SMALL_ANGLE_THRESHOLD {
628 let a = theta * sin_theta;
629 let b = theta * cos_theta;
630
631 jac_inv[(0, 0)] = -a / (2.0 * cos_theta - 2.0);
632 jac_inv[(1, 1)] = jac_inv[(0, 0)];
633
634 let den = 2.0 * theta * (cos_theta - 1.0);
635 jac_inv[(0, 2)] =
636 (a * self.x() - b * self.y() + theta * self.y() + 2.0 * self.x() * cos_theta
637 - 2.0 * self.x())
638 / den;
639 jac_inv[(1, 2)] = (b * self.x() + a * self.y() - theta * self.x()
640 + 2.0 * self.y() * cos_theta
641 - 2.0 * self.y())
642 / den;
643 } else {
644 jac_inv[(0, 0)] = 1.0 - theta_sq / 12.0;
645 jac_inv[(1, 1)] = jac_inv[(0, 0)];
646
647 jac_inv[(0, 2)] = -self.y() / 2.0 + theta * self.x() / 12.0;
648 jac_inv[(1, 2)] = self.x() / 2.0 + theta * self.y() / 12.0;
649 }
650
651 jac_inv
652 }
653
654 fn hat(&self) -> <SE2 as LieGroup>::LieAlgebra {
656 Matrix3::new(
657 0.0,
658 -self.angle(),
659 self.x(),
660 self.angle(),
661 0.0,
662 self.y(),
663 0.0,
664 0.0,
665 0.0,
666 )
667 }
668
669 fn zero() -> <SE2 as LieGroup>::TangentVector {
671 SE2Tangent::new(0.0, 0.0, 0.0)
672 }
673
674 fn random() -> <SE2 as LieGroup>::TangentVector {
676 use rand::Rng;
677 let mut rng = rand::rng();
678 SE2Tangent::new(
679 rng.random_range(-1.0..1.0),
680 rng.random_range(-1.0..1.0),
681 rng.random_range(-std::f64::consts::PI..std::f64::consts::PI),
682 )
683 }
684
685 fn is_zero(&self, tolerance: f64) -> bool {
687 self.data.norm() < tolerance
688 }
689
690 fn normalize(&mut self) {
692 let norm = self.data.norm();
693 if norm > f64::EPSILON {
694 self.data /= norm;
695 }
696 }
697
698 fn normalized(&self) -> <SE2 as LieGroup>::TangentVector {
700 let norm = self.data.norm();
701 if norm > f64::EPSILON {
702 SE2Tangent {
703 data: self.data / norm,
704 }
705 } else {
706 SE2Tangent::zero()
707 }
708 }
709
710 fn small_adj(&self) -> <SE2 as LieGroup>::JacobianMatrix {
714 let x = self.x();
715 let y = self.y();
716
717 let mut small_adj = Matrix3::zeros();
718 small_adj[(0, 1)] = -self.angle();
719 small_adj[(1, 0)] = self.angle();
720 small_adj[(0, 2)] = y;
721 small_adj[(1, 2)] = -x;
722
723 small_adj
724 }
725
726 fn lie_bracket(&self, other: &Self) -> <SE2 as LieGroup>::TangentVector {
730 let bracket_result = self.small_adj() * other.data;
731 SE2Tangent {
732 data: bracket_result,
733 }
734 }
735
736 fn is_approx(&self, other: &Self, tolerance: f64) -> bool {
742 (self.data - other.data).norm() < tolerance
743 }
744
745 fn generator(&self, i: usize) -> <SE2 as LieGroup>::LieAlgebra {
753 assert!(i < 3, "SE(2) only has generators for indices 0, 1, 2");
754
755 match i {
756 0 => {
757 Matrix3::new(0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
759 }
760 1 => {
761 Matrix3::new(0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0)
763 }
764 2 => {
765 Matrix3::new(0.0, -1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0)
767 }
768 _ => unreachable!(),
769 }
770 }
771}
772
773#[cfg(test)]
774mod tests {
775 use super::*;
776 use std::f64::consts::PI;
777
778 const TOLERANCE: f64 = 1e-12;
779
780 #[test]
781 fn test_se2_tangent_basic() {
782 let tangent = SE2Tangent::new(4.0, 2.0, PI);
783 assert_eq!(tangent.x(), 4.0);
784 assert_eq!(tangent.y(), 2.0);
785 assert_eq!(tangent.angle(), PI);
786 }
787
788 #[test]
789 fn test_se2_tangent_zero() {
790 let zero = SE2Tangent::zero();
791 assert_eq!(zero.data, Vector3::zeros());
792 assert!(zero.is_zero(1e-10));
793 }
794
795 #[test]
796 fn test_se2_identity() {
797 let identity = SE2::identity();
798 assert!(identity.is_valid(TOLERANCE));
799 assert_eq!(identity.x(), 0.0);
800 assert_eq!(identity.y(), 0.0);
801 assert_eq!(identity.angle(), 0.0);
802 }
803
804 #[test]
805 fn test_se2_new() {
806 let translation = Vector2::new(1.0, 2.0);
807 let rotation = UnitComplex::from_angle(PI / 4.0);
808 let se2 = SE2::new(translation, rotation);
809
810 assert!(se2.is_valid(TOLERANCE));
811 assert_eq!(se2.x(), 1.0);
812 assert_eq!(se2.y(), 2.0);
813 assert!((se2.angle() - PI / 4.0).abs() < TOLERANCE);
814 }
815
816 #[test]
817 fn test_se2_from_xy_angle() {
818 let se2 = SE2::from_xy_angle(4.0, 2.0, 0.0);
819 assert_eq!(se2.x(), 4.0);
820 assert_eq!(se2.y(), 2.0);
821 assert_eq!(se2.angle(), 0.0);
822 }
823
824 #[test]
825 fn test_se2_from_xy_complex() {
826 let se2 = SE2::from_xy_complex(4.0, 2.0, 1.0, 0.0);
827 assert_eq!(se2.x(), 4.0);
828 assert_eq!(se2.y(), 2.0);
829 assert_eq!(se2.real(), 1.0);
830 assert_eq!(se2.imag(), 0.0);
831 assert_eq!(se2.angle(), 0.0);
832 }
833
834 #[test]
835 fn test_se2_inverse() {
836 let se2 = SE2::from_xy_angle(1.0, 1.0, PI);
837 let se2_inv = se2.inverse(None);
838
839 assert!((se2_inv.x() - 1.0).abs() < TOLERANCE);
840 assert!((se2_inv.y() - 1.0).abs() < TOLERANCE);
841 assert!((se2_inv.angle() + PI).abs() < TOLERANCE);
842
843 let composed = se2.compose(&se2_inv, None, None);
845 let identity = SE2::identity();
846
847 assert!((composed.x() - identity.x()).abs() < TOLERANCE);
848 assert!((composed.y() - identity.y()).abs() < TOLERANCE);
849 assert!((composed.angle() - identity.angle()).abs() < TOLERANCE);
850 }
851
852 #[test]
853 fn test_se2_compose() {
854 let se2a = SE2::from_xy_angle(1.0, 1.0, PI / 2.0);
855 let se2b = SE2::from_xy_angle(2.0, 2.0, PI / 2.0);
856 let se2c = se2a.compose(&se2b, None, None);
857
858 assert!((se2c.x() - (-1.0)).abs() < TOLERANCE);
859 assert!((se2c.y() - 3.0).abs() < TOLERANCE);
860 assert!((se2c.angle() - PI).abs() < TOLERANCE);
861 }
862
863 #[test]
864 fn test_se2_exp_log() {
865 let tangent = SE2Tangent::new(4.0, 2.0, PI);
866 let se2 = tangent.exp(None);
867 let recovered_tangent = se2.log(None);
868
869 assert!((tangent.x() - recovered_tangent.x()).abs() < TOLERANCE);
870 assert!((tangent.y() - recovered_tangent.y()).abs() < TOLERANCE);
871 assert!((tangent.angle() - recovered_tangent.angle()).abs() < TOLERANCE);
872 }
873
874 #[test]
875 fn test_se2_exp_zero() {
876 let zero_tangent = SE2Tangent::zero();
877 let se2 = zero_tangent.exp(None);
878 let identity = SE2::identity();
879
880 assert!((se2.x() - identity.x()).abs() < TOLERANCE);
881 assert!((se2.y() - identity.y()).abs() < TOLERANCE);
882 assert!((se2.angle() - identity.angle()).abs() < TOLERANCE);
883 }
884
885 #[test]
886 fn test_se2_log_identity() {
887 let identity = SE2::identity();
888 let tangent = identity.log(None);
889
890 assert!(tangent.data.norm() < TOLERANCE);
891 }
892
893 #[test]
894 fn test_se2_act() {
895 let se2 = SE2::from_xy_angle(1.0, 1.0, PI / 2.0);
896 let point = Vector3::new(1.0, 1.0, 0.0);
897 let transformed_point = se2.act(&point, None, None);
898
899 assert!((transformed_point.x - 0.0).abs() < TOLERANCE);
900 assert!((transformed_point.y - 2.0).abs() < TOLERANCE);
901 assert!((transformed_point.z - 0.0).abs() < TOLERANCE);
902 }
903
904 #[test]
905 fn test_se2_between() {
906 let se2a = SE2::from_xy_angle(1.0, 1.0, PI);
907 let se2b = SE2::from_xy_angle(1.0, 1.0, PI);
908 let se2c = se2a.between(&se2b, None, None);
909
910 assert!((se2c.x() - 0.0).abs() < TOLERANCE);
911 assert!((se2c.y() - 0.0).abs() < TOLERANCE);
912 assert!((se2c.angle() - 0.0).abs() < TOLERANCE);
913 }
914
915 #[test]
916 fn test_se2_adjoint() {
917 let se2 = SE2::random();
918 let adj = se2.adjoint();
919
920 assert_eq!(adj.nrows(), 3);
921 assert_eq!(adj.ncols(), 3);
922 }
923
924 #[test]
925 fn test_se2_manifold_properties() {
926 assert_eq!(SE2::DIM, 2);
927 assert_eq!(SE2::DOF, 3);
928 assert_eq!(SE2::REP_SIZE, 4);
929 }
930
931 #[test]
932 fn test_se2_random() {
933 let se2 = SE2::random();
934 assert!(se2.is_valid(TOLERANCE));
935 }
936
937 #[test]
938 fn test_se2_normalize() {
939 let mut se2 = SE2::from_xy_complex(1.0, 2.0, 0.5, 0.5); se2.normalize();
941 assert!(se2.is_valid(TOLERANCE));
942 }
943
944 #[test]
945 fn test_se2_tangent_exp_jacobians() {
946 let tangent = SE2Tangent::new(1.0, 2.0, 0.1);
947
948 let se2_element = tangent.exp(None);
949 assert!(se2_element.is_valid(TOLERANCE));
950
951 let right_jac = tangent.right_jacobian();
953 let left_jac = tangent.left_jacobian();
954 let right_jac_inv = tangent.right_jacobian_inv();
955 let left_jac_inv = tangent.left_jacobian_inv();
956
957 assert_eq!(right_jac.nrows(), 3);
958 assert_eq!(right_jac.ncols(), 3);
959 assert_eq!(left_jac.nrows(), 3);
960 assert_eq!(left_jac.ncols(), 3);
961 assert_eq!(right_jac_inv.nrows(), 3);
962 assert_eq!(right_jac_inv.ncols(), 3);
963 assert_eq!(left_jac_inv.nrows(), 3);
964 assert_eq!(left_jac_inv.ncols(), 3);
965 }
966
967 #[test]
968 fn test_se2_tangent_hat() {
969 let tangent = SE2Tangent::new(4.0, 2.0, PI);
970 let hat_matrix = tangent.hat();
971
972 assert_eq!(hat_matrix.nrows(), 3);
973 assert_eq!(hat_matrix.ncols(), 3);
974 assert_eq!(hat_matrix[(0, 2)], 4.0);
975 assert_eq!(hat_matrix[(1, 2)], 2.0);
976 assert_eq!(hat_matrix[(1, 0)], PI);
977 assert_eq!(hat_matrix[(0, 1)], -PI);
978 }
979
980 #[test]
981 fn test_se2_consistency() {
982 let se2_1 = SE2::random();
984 let se2_2 = SE2::random();
985 let se2_3 = SE2::random();
986
987 let left_assoc = se2_1
988 .compose(&se2_2, None, None)
989 .compose(&se2_3, None, None);
990 let right_assoc = se2_1.compose(&se2_2.compose(&se2_3, None, None), None, None);
991
992 let translation_diff = (left_assoc.translation() - right_assoc.translation()).norm();
993 let angle_diff = (left_assoc.angle() - right_assoc.angle()).abs();
994
995 assert!(translation_diff < 1e-10);
996 assert!(angle_diff < 1e-10);
997 }
998
999 #[test]
1000 fn test_se2_isometry() {
1001 let translation = Translation2::new(1.0, 2.0);
1002 let rotation = UnitComplex::from_angle(PI / 4.0);
1003 let isometry = Isometry2::from_parts(translation, rotation);
1004
1005 let se2 = SE2::from_isometry(isometry);
1006 let recovered_isometry = se2.isometry();
1007
1008 let translation_diff =
1009 (isometry.translation.vector - recovered_isometry.translation.vector).norm();
1010 let angle_diff = (isometry.rotation.angle() - recovered_isometry.rotation.angle()).abs();
1011
1012 assert!(translation_diff < TOLERANCE);
1013 assert!(angle_diff < TOLERANCE);
1014 }
1015
1016 #[test]
1017 fn test_se2_matrix() {
1018 let se2 = SE2::random();
1019 let matrix = se2.matrix();
1020
1021 assert_eq!(matrix.nrows(), 3);
1023 assert_eq!(matrix.ncols(), 3);
1024
1025 assert!((matrix[(2, 0)]).abs() < TOLERANCE);
1027 assert!((matrix[(2, 1)]).abs() < TOLERANCE);
1028 assert!((matrix[(2, 2)] - 1.0).abs() < TOLERANCE);
1029 }
1030
1031 #[test]
1034 fn test_se2_constructor_copy() {
1035 let se2_original = SE2::from_xy_complex(4.0, 2.0, (PI / 4.0).cos(), (PI / 4.0).sin());
1036 let se2_copy = se2_original.clone();
1037
1038 assert_eq!(se2_copy.x(), 4.0);
1039 assert_eq!(se2_copy.y(), 2.0);
1040 assert!((se2_copy.angle() - PI / 4.0).abs() < TOLERANCE);
1041 }
1042
1043 #[test]
1044 fn test_se2_assign_op() {
1045 let _se2a = SE2::from_xy_angle(0.0, 0.0, 0.0);
1046 let se2b = SE2::from_xy_angle(4.0, 2.0, PI);
1047
1048 let se2a = se2b.clone(); assert_eq!(se2a.x(), 4.0);
1051 assert_eq!(se2a.y(), 2.0);
1052 assert!((se2a.angle() - PI).abs() < TOLERANCE);
1053 }
1054
1055 #[test]
1056 fn test_se2_inverse_detailed() {
1057 let se2 = SE2::identity();
1059 let se2_inv = se2.inverse(None);
1060
1061 assert!((se2_inv.x() - 0.0).abs() < TOLERANCE);
1062 assert!((se2_inv.y() - 0.0).abs() < TOLERANCE);
1063 assert!((se2_inv.angle() - 0.0).abs() < TOLERANCE);
1064 assert!((se2_inv.real() - 1.0).abs() < TOLERANCE);
1065 assert!((se2_inv.imag() - 0.0).abs() < TOLERANCE);
1066
1067 let se2 = SE2::from_xy_angle(0.7, 2.3, PI / 3.0);
1069 let se2_inv = se2.inverse(None);
1070
1071 assert!((se2_inv.x() - (-2.341858428704209)).abs() < 1e-10);
1072 assert!((se2_inv.y() - (-0.543782217350893)).abs() < 1e-10);
1073 assert!((se2_inv.angle() - (-PI / 3.0)).abs() < 1e-10);
1074 }
1075
1076 #[test]
1077 fn test_se2_rplus_zero() {
1078 let se2a = SE2::from_xy_angle(1.0, 1.0, PI / 2.0);
1079 let se2b = SE2Tangent::zero();
1080
1081 let se2c = se2a.right_plus(&se2b, None, None);
1082
1083 assert!((se2c.x() - 1.0).abs() < TOLERANCE);
1084 assert!((se2c.y() - 1.0).abs() < TOLERANCE);
1085 assert!((se2c.angle() - PI / 2.0).abs() < TOLERANCE);
1086 }
1087
1088 #[test]
1089 fn test_se2_rplus() {
1090 let se2a = SE2::from_xy_angle(1.0, 1.0, PI / 2.0);
1091 let se2b = SE2Tangent::new(1.0, 1.0, PI / 2.0);
1092
1093 let se2c = se2a.right_plus(&se2b, None, None);
1094
1095 assert!((se2c.angle() - PI).abs() < TOLERANCE);
1096 }
1097
1098 #[test]
1099 fn test_se2_lplus_zero() {
1100 let se2a = SE2::from_xy_angle(1.0, 1.0, PI / 2.0);
1101 let se2b = SE2Tangent::zero();
1102
1103 let se2c = se2a.left_plus(&se2b, None, None);
1104
1105 assert!((se2c.x() - 1.0).abs() < TOLERANCE);
1106 assert!((se2c.y() - 1.0).abs() < TOLERANCE);
1107 assert!((se2c.angle() - PI / 2.0).abs() < TOLERANCE);
1108 }
1109
1110 #[test]
1111 fn test_se2_lplus() {
1112 let se2a = SE2::from_xy_angle(1.0, 1.0, PI / 2.0);
1113 let se2b = SE2Tangent::new(1.0, 1.0, PI / 2.0);
1114
1115 let se2c = se2a.left_plus(&se2b, None, None);
1116
1117 assert!((se2c.angle() - PI).abs() < TOLERANCE);
1118 }
1119
1120 #[test]
1121 fn test_se2_rminus_zero() {
1122 let se2a = SE2::identity();
1123 let se2b = SE2::identity();
1124
1125 let se2c = se2a.right_minus(&se2b, None, None);
1126
1127 assert!((se2c.x() - 0.0).abs() < TOLERANCE);
1128 assert!((se2c.y() - 0.0).abs() < TOLERANCE);
1129 assert!((se2c.angle() - 0.0).abs() < TOLERANCE);
1130 }
1131
1132 #[test]
1133 fn test_se2_rminus() {
1134 let se2a = SE2::from_xy_angle(1.0, 1.0, PI);
1135 let se2b = SE2::from_xy_angle(2.0, 2.0, PI / 2.0);
1136
1137 let se2c = se2a.right_minus(&se2b, None, None);
1138
1139 assert!((se2c.angle() - PI / 2.0).abs() < TOLERANCE);
1140 }
1141
1142 #[test]
1143 fn test_se2_lminus_identity() {
1144 let se2a = SE2::identity();
1145 let se2b = SE2::identity();
1146
1147 let se2c = se2a.left_minus(&se2b, None, None);
1148
1149 assert!((se2c.x() - 0.0).abs() < TOLERANCE);
1150 assert!((se2c.y() - 0.0).abs() < TOLERANCE);
1151 assert!((se2c.angle() - 0.0).abs() < TOLERANCE);
1152 }
1153
1154 #[test]
1155 fn test_se2_lminus() {
1156 let se2a = SE2::from_xy_angle(1.0, 1.0, PI);
1157 let se2b = SE2::from_xy_angle(2.0, 2.0, PI / 2.0);
1158
1159 let se2c = se2a.left_minus(&se2b, None, None);
1160
1161 assert!((se2c.angle() - PI / 2.0).abs() < TOLERANCE);
1162 }
1163
1164 #[test]
1165 fn test_se2_lift() {
1166 let se2 = SE2::from_xy_angle(1.0, 1.0, PI);
1167 let se2_log = se2.log(None);
1168
1169 assert!((se2_log.angle() - PI).abs() < TOLERANCE);
1170 }
1171
1172 #[test]
1173 fn test_se2_compose_detailed() {
1174 let se2a = SE2::from_xy_angle(1.0, 1.0, PI / 2.0);
1175 let se2b = SE2::from_xy_angle(2.0, 2.0, PI / 2.0);
1176
1177 let se2c = se2a.compose(&se2b, None, None);
1178
1179 assert!((se2c.x() - (-1.0)).abs() < TOLERANCE);
1180 assert!((se2c.y() - 3.0).abs() < TOLERANCE);
1181 assert!((se2c.angle() - PI).abs() < TOLERANCE);
1182 }
1183
1184 #[test]
1185 fn test_se2_between_identity() {
1186 let se2a = SE2::from_xy_angle(1.0, 1.0, PI);
1187 let se2b = SE2::from_xy_angle(1.0, 1.0, PI);
1188
1189 let se2c = se2a.between(&se2b, None, None);
1190
1191 assert!((se2c.x() - 0.0).abs() < TOLERANCE);
1192 assert!((se2c.y() - 0.0).abs() < TOLERANCE);
1193 assert!((se2c.angle() - 0.0).abs() < TOLERANCE);
1194 }
1195
1196 #[test]
1197 fn test_se2_between_detailed() {
1198 let se2a = SE2::from_xy_angle(1.0, 1.0, PI);
1199 let se2b = SE2::from_xy_angle(2.0, 2.0, PI / 2.0);
1200
1201 let se2c = se2a.between(&se2b, None, None);
1202
1203 assert!((se2c.x() - (-1.0)).abs() < TOLERANCE);
1204 assert!((se2c.y() - (-1.0)).abs() < TOLERANCE);
1205 assert!((se2c.angle() - (-PI / 2.0)).abs() < TOLERANCE);
1206 }
1207
1208 #[test]
1209 fn test_se2_act_detailed() {
1210 let se2 = SE2::from_xy_angle(1.0, 1.0, PI / 2.0);
1211 let point = Vector3::new(1.0, 1.0, 0.0);
1212 let transformed_point = se2.act(&point, None, None);
1213
1214 assert!((transformed_point.x - 0.0).abs() < TOLERANCE);
1215 assert!((transformed_point.y - 2.0).abs() < TOLERANCE);
1216
1217 let se2 = SE2::from_xy_angle(1.0, 1.0, -PI / 2.0);
1218 let transformed_point = se2.act(&point, None, None);
1219
1220 assert!((transformed_point.x - 2.0).abs() < TOLERANCE);
1221 assert!((transformed_point.y - 0.0).abs() < TOLERANCE);
1222
1223 let se2 = SE2::identity();
1224 let transformed_point = se2.act(&point, None, None);
1225
1226 assert!((transformed_point.x - 1.0).abs() < TOLERANCE);
1227 assert!((transformed_point.y - 1.0).abs() < TOLERANCE);
1228 }
1229
1230 #[test]
1231 fn test_se2_rotation_matrix() {
1232 let se2 = SE2::identity();
1233 let r = se2.rotation_matrix();
1234
1235 assert_eq!(r.nrows(), 2);
1236 assert_eq!(r.ncols(), 2);
1237
1238 assert!((r[(0, 0)] - 1.0).abs() < TOLERANCE);
1240 assert!((r[(0, 1)] - 0.0).abs() < TOLERANCE);
1241 assert!((r[(1, 0)] - 0.0).abs() < TOLERANCE);
1242 assert!((r[(1, 1)] - 1.0).abs() < TOLERANCE);
1243 }
1244
1245 #[test]
1248 fn test_se2_tangent_data() {
1249 let se2_tan = SE2Tangent::new(4.0, 2.0, PI);
1250
1251 assert_eq!(se2_tan.x(), 4.0);
1253 assert_eq!(se2_tan.y(), 2.0);
1254 assert_eq!(se2_tan.angle(), PI);
1255 }
1256
1257 #[test]
1258 fn test_se2_tangent_retract() {
1259 let se2_tan = SE2Tangent::new(4.0, 2.0, PI);
1260
1261 assert_eq!(se2_tan.x(), 4.0);
1262 assert_eq!(se2_tan.y(), 2.0);
1263 assert_eq!(se2_tan.angle(), PI);
1264
1265 let se2_exp = se2_tan.exp(None);
1266
1267 assert!((se2_exp.real() - PI.cos()).abs() < TOLERANCE);
1268 assert!((se2_exp.imag() - PI.sin()).abs() < TOLERANCE);
1269 assert!((se2_exp.angle() - PI).abs() < TOLERANCE);
1270 }
1271
1272 #[test]
1273 fn test_se2_tangent_retract_jac() {
1274 let se2_tan = SE2Tangent::new(4.0, 2.0, PI);
1275
1276 let mut j_ret = Matrix3::zeros();
1277 let se2_exp = se2_tan.exp(Some(&mut j_ret));
1278
1279 assert!((se2_exp.real() - PI.cos()).abs() < TOLERANCE);
1280 assert!((se2_exp.imag() - PI.sin()).abs() < TOLERANCE);
1281 assert!((se2_exp.angle() - PI).abs() < TOLERANCE);
1282
1283 assert_eq!(j_ret.nrows(), 3);
1285 assert_eq!(j_ret.ncols(), 3);
1286 }
1287
1288 #[test]
1289 fn test_se2_small_angle_approximations() {
1290 let small_tangent = SE2Tangent::new(1e-8, 2e-8, 1e-9);
1292
1293 let se2 = small_tangent.exp(None);
1294 let recovered = se2.log(None);
1295
1296 let diff = (Vector3::new(small_tangent.x(), small_tangent.y(), small_tangent.angle())
1297 - Vector3::new(recovered.x(), recovered.y(), recovered.angle()))
1298 .norm();
1299 assert!(diff < TOLERANCE);
1300 }
1301
1302 #[test]
1303 fn test_se2_tangent_norm() {
1304 let tangent = SE2Tangent::new(3.0, 4.0, 0.0);
1305 let norm = Vector3::new(tangent.x(), tangent.y(), tangent.angle()).norm();
1306 assert!((norm - 5.0).abs() < TOLERANCE); }
1308
1309 #[test]
1312 fn test_se2_vee() {
1313 let se2 = SE2::random();
1314 let tangent_log = se2.log(None);
1315 let tangent_vee = se2.vee();
1316
1317 assert!((tangent_log.data - tangent_vee.data).norm() < 1e-10);
1318 }
1319
1320 #[test]
1321 fn test_se2_is_approx() {
1322 let se2_1 = SE2::random();
1323 let se2_2 = se2_1.clone();
1324
1325 assert!(se2_1.is_approx(&se2_1, 1e-10));
1326 assert!(se2_1.is_approx(&se2_2, 1e-10));
1327
1328 let small_tangent = SE2Tangent::new(1e-12, 1e-12, 1e-12);
1330 let se2_perturbed = se2_1.right_plus(&small_tangent, None, None);
1331 assert!(se2_1.is_approx(&se2_perturbed, 1e-10));
1332 }
1333
1334 #[test]
1335 fn test_se2_tangent_small_adj() {
1336 let tangent = SE2Tangent::new(0.1, 0.2, 0.3);
1337 let small_adj = tangent.small_adj();
1338
1339 assert_eq!(small_adj[(0, 0)], 0.0);
1345 assert_eq!(small_adj[(1, 1)], 0.0);
1346 assert_eq!(small_adj[(2, 2)], 0.0);
1347 assert_eq!(small_adj[(0, 1)], -tangent.angle());
1348 assert_eq!(small_adj[(1, 0)], tangent.angle());
1349 assert_eq!(small_adj[(0, 2)], tangent.y());
1350 assert_eq!(small_adj[(1, 2)], -tangent.x());
1351 }
1352
1353 #[test]
1354 fn test_se2_tangent_lie_bracket() {
1355 let tangent_a = SE2Tangent::new(0.1, 0.0, 0.0); let tangent_b = SE2Tangent::new(0.0, 0.0, 0.2); let bracket_ab = tangent_a.lie_bracket(&tangent_b);
1359 let bracket_ba = tangent_b.lie_bracket(&tangent_a);
1360
1361 assert!((bracket_ab.data + bracket_ba.data).norm() < 1e-10);
1363
1364 let bracket_aa = tangent_a.lie_bracket(&tangent_a);
1366 assert!(bracket_aa.is_zero(1e-10));
1367
1368 let bracket_hat = bracket_ab.hat();
1370 let expected = tangent_a.hat() * tangent_b.hat() - tangent_b.hat() * tangent_a.hat();
1371 assert!((bracket_hat - expected).norm() < 1e-10);
1372 }
1373
1374 #[test]
1375 fn test_se2_tangent_is_approx() {
1376 let tangent_1 = SE2Tangent::new(0.1, 0.2, 0.3);
1377 let tangent_2 = SE2Tangent::new(0.1 + 1e-12, 0.2, 0.3);
1378 let tangent_3 = SE2Tangent::new(0.5, 0.6, 0.7);
1379
1380 assert!(tangent_1.is_approx(&tangent_1, 1e-10));
1381 assert!(tangent_1.is_approx(&tangent_2, 1e-10));
1382 assert!(!tangent_1.is_approx(&tangent_3, 1e-10));
1383 }
1384
1385 #[test]
1386 fn test_se2_generators() {
1387 let tangent = SE2Tangent::new(1.0, 1.0, 1.0);
1388
1389 for i in 0..3 {
1391 let generator = tangent.generator(i);
1392
1393 assert_eq!(generator.nrows(), 3);
1395 assert_eq!(generator.ncols(), 3);
1396 }
1397
1398 let e1 = tangent.generator(0); let e2 = tangent.generator(1); let e3 = tangent.generator(2); let expected_e1 = Matrix3::new(0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
1405 let expected_e2 = Matrix3::new(0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0);
1406 let expected_e3 = Matrix3::new(0.0, -1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0);
1407
1408 assert!((e1 - expected_e1).norm() < 1e-10);
1409 assert!((e2 - expected_e2).norm() < 1e-10);
1410 assert!((e3 - expected_e3).norm() < 1e-10);
1411 }
1412
1413 #[test]
1414 #[should_panic]
1415 fn test_se2_generator_invalid_index() {
1416 let tangent = SE2Tangent::new(1.0, 1.0, 1.0);
1417 let _generator = tangent.generator(3); }
1419
1420 #[test]
1421 fn test_se2_jacobi_identity() {
1422 let x = SE2Tangent::new(0.1, 0.0, 0.0);
1424 let y = SE2Tangent::new(0.0, 0.2, 0.0);
1425 let z = SE2Tangent::new(0.0, 0.0, 0.3);
1426
1427 let term1 = x.lie_bracket(&y.lie_bracket(&z));
1428 let term2 = y.lie_bracket(&z.lie_bracket(&x));
1429 let term3 = z.lie_bracket(&x.lie_bracket(&y));
1430
1431 let jacobi_sum = SE2Tangent {
1432 data: term1.data + term2.data + term3.data,
1433 };
1434 assert!(jacobi_sum.is_zero(1e-10));
1435 }
1436
1437 #[test]
1438 fn test_se2_hat_vee_consistency() {
1439 let tangent = SE2Tangent::new(0.1, 0.2, 0.3);
1440 let hat_matrix = tangent.hat();
1441
1442 assert_eq!(hat_matrix[(0, 2)], tangent.x());
1445 assert_eq!(hat_matrix[(1, 2)], tangent.y());
1446 assert_eq!(hat_matrix[(0, 1)], -tangent.angle());
1447 assert_eq!(hat_matrix[(1, 0)], tangent.angle());
1448 assert_eq!(hat_matrix[(2, 0)], 0.0);
1449 assert_eq!(hat_matrix[(2, 1)], 0.0);
1450 assert_eq!(hat_matrix[(2, 2)], 0.0);
1451 }
1452
1453 #[test]
1456 fn test_se2_circular_path_accumulation() {
1457 let step = SE2::from_xy_angle(0.01, 0.0, (std::f64::consts::PI * 2.0) / 360.0);
1459
1460 let mut pose = SE2::identity();
1461 for _ in 0..360 {
1462 pose = pose.compose(&step, None, None);
1463 }
1464
1465 assert!(pose.is_approx(&SE2::identity(), 1e-2));
1467 }
1468
1469 #[test]
1472 fn test_se2_angle_wrap_around() {
1473 let se2_wrapped = SE2::from_xy_angle(1.0, 2.0, 3.0 * std::f64::consts::PI);
1475 let se2_canonical = SE2::from_xy_angle(1.0, 2.0, std::f64::consts::PI);
1476
1477 let tangent_wrapped = se2_wrapped.log(None);
1479 let tangent_canonical = se2_canonical.log(None);
1480
1481 let angle_diff = (tangent_wrapped.angle() - tangent_canonical.angle()).abs();
1483 assert!(angle_diff < 1e-10 || (angle_diff - 2.0 * std::f64::consts::PI).abs() < 1e-10);
1484 }
1485
1486 #[test]
1487 fn test_se2_negative_angles() {
1488 let se2_neg = SE2::from_xy_angle(0.5, -0.3, -std::f64::consts::PI / 4.0);
1489 let tangent = se2_neg.log(None);
1490 let recovered = tangent.exp(None);
1491 assert!(se2_neg.is_approx(&recovered, 1e-10));
1492 }
1493
1494 #[test]
1497 fn test_se2_right_jacobian_inverse_identity() {
1498 let test_tangents = vec![
1499 SE2Tangent::new(0.1, 0.2, 0.3),
1500 SE2Tangent::new(0.5, -0.3, 1.5),
1501 ];
1502
1503 for tangent in test_tangents {
1504 let jr = tangent.right_jacobian();
1505 let jr_inv = tangent.right_jacobian_inv();
1506 let product = jr * jr_inv;
1507 let identity = Matrix3::identity();
1508
1509 assert!((product - identity).norm() < 1e-10);
1510 }
1511 }
1512
1513 #[test]
1514 fn test_se2_left_jacobian_inverse_identity() {
1515 let tangent = SE2Tangent::new(0.2, 0.3, 0.8);
1517 let jl = tangent.left_jacobian();
1518 let jl_inv = tangent.left_jacobian_inv();
1519 let product = jl * jl_inv;
1520
1521 assert!((product - Matrix3::identity()).norm() < 1e-10);
1522 }
1523
1524 #[test]
1525 fn test_se2_tangent_from_components() {
1526 let t1 = SE2Tangent::new(1.0, 2.0, 0.5);
1527 let t2 = SE2Tangent::from_components(1.0, 2.0, 0.5);
1528 assert!(t1.is_approx(&t2, 1e-15));
1529 assert_eq!(t2.x(), 1.0);
1530 assert_eq!(t2.y(), 2.0);
1531 assert_eq!(t2.angle(), 0.5);
1532 }
1533
1534 #[test]
1535 fn test_se2_small_angle_threshold_exp_log() {
1536 let near_threshold_angles = [1e-6, 1e-5, 1e-4, 1e-3];
1539
1540 for &angle in &near_threshold_angles {
1541 let tangent = SE2Tangent::new(1.0, 2.0, angle);
1542 let se2 = tangent.exp(None);
1543 let recovered = se2.log(None);
1544 assert!(
1545 (tangent.x() - recovered.x()).abs() < 1e-10
1546 && (tangent.y() - recovered.y()).abs() < 1e-10
1547 && (tangent.angle() - recovered.angle()).abs() < 1e-10,
1548 "SE2 exp-log round-trip failed for angle = {angle}"
1549 );
1550 }
1551 }
1552}