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