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.iter().all(|p| p.is_finite())
337 }
338
339 fn as_param_slice(&self) -> &[f64] {
340 self.params.as_slice()
341 }
342
343 fn as_param_slice_mut(&mut self) -> &mut [f64] {
344 self.params.as_mut_slice()
345 }
346
347 fn from_param_slice(s: &[f64]) -> Self {
348 debug_assert_eq!(s.len(), 3);
349 SE2 {
350 params: SVector::from_column_slice(s),
351 }
352 }
353
354 fn vee(&self) -> Self::TangentVector {
359 self.log(None)
360 }
361
362 fn is_approx(&self, other: &Self, tolerance: f64) -> bool {
368 let difference = self.right_minus(other, None, None);
369 difference.is_zero(tolerance)
370 }
371}
372
373#[derive(Clone, PartialEq)]
379pub struct SE2Tangent {
380 data: Vector3<f64>,
382}
383
384impl fmt::Display for SE2Tangent {
385 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
386 write!(
387 f,
388 "SE2Tangent(x: {:.4}, y: {:.4}, theta: {:.4})",
389 self.x(),
390 self.y(),
391 self.angle()
392 )
393 }
394}
395
396impl SE2Tangent {
397 #[inline]
399 pub fn new(x: f64, y: f64, theta: f64) -> Self {
400 SE2Tangent {
401 data: Vector3::new(x, y, theta),
402 }
403 }
404
405 #[inline]
407 pub fn from_components(x: f64, y: f64, theta: f64) -> Self {
408 SE2Tangent::new(x, y, theta)
409 }
410
411 #[inline]
413 pub fn x(&self) -> f64 {
414 self.data[0]
415 }
416
417 #[inline]
419 pub fn y(&self) -> f64 {
420 self.data[1]
421 }
422
423 #[inline]
425 pub fn angle(&self) -> f64 {
426 self.data[2]
427 }
428
429 #[inline]
431 pub fn translation(&self) -> Vector2<f64> {
432 Vector2::new(self.x(), self.y())
433 }
434}
435
436impl Tangent<SE2> for SE2Tangent {
437 const DIM: usize = 3;
439
440 fn exp(&self, jacobian: Option<&mut <SE2 as LieGroup>::JacobianMatrix>) -> SE2 {
442 let theta = self.angle();
443 let cos_theta = theta.cos();
444 let sin_theta = theta.sin();
445 let theta_sq = theta * theta;
446
447 let (a, b) = if theta_sq < crate::SMALL_ANGLE_THRESHOLD {
448 let a = 1.0 - theta_sq / 6.0;
450 let b = 0.5 * theta - theta * theta_sq / 24.0;
451 (a, b)
452 } else {
453 let a = sin_theta / theta;
455 let b = (1.0 - cos_theta) / theta;
456 (a, b)
457 };
458
459 let translation = Vector2::new(a * self.x() - b * self.y(), b * self.x() + a * self.y());
460 let rotation = UnitComplex::from_cos_sin_unchecked(cos_theta, sin_theta);
461
462 if let Some(jac) = jacobian {
463 *jac = self.right_jacobian();
464 }
465
466 SE2::new(translation, rotation)
467 }
468
469 fn right_jacobian(&self) -> <SE2 as LieGroup>::JacobianMatrix {
471 let theta = self.angle();
472 let cos_theta = theta.cos();
473 let sin_theta = theta.sin();
474 let theta_sq = theta * theta;
475
476 let (a, b) = if theta_sq < crate::SMALL_ANGLE_THRESHOLD {
477 let a = 1.0 - theta_sq / 6.0;
479 let b = 0.5 * theta - theta * theta_sq / 24.0;
480 (a, b)
481 } else {
482 let a = sin_theta / theta;
484 let b = (1.0 - cos_theta) / theta;
485 (a, b)
486 };
487
488 let mut jac = Matrix3::identity();
489 jac[(0, 0)] = a;
490 jac[(0, 1)] = b;
491 jac[(1, 0)] = -b;
492 jac[(1, 1)] = a;
493
494 if theta_sq < crate::SMALL_ANGLE_THRESHOLD {
495 jac[(0, 2)] = -self.y() / 2.0 + theta * self.x() / 6.0;
496 jac[(1, 2)] = self.x() / 2.0 + theta * self.y() / 6.0;
497 } else {
498 jac[(0, 2)] = (-self.y() + theta * self.x() + self.y() * cos_theta
499 - self.x() * sin_theta)
500 / theta_sq;
501 jac[(1, 2)] =
502 (self.x() + theta * self.y() - self.x() * cos_theta - self.y() * sin_theta)
503 / theta_sq;
504 }
505
506 jac
507 }
508
509 fn left_jacobian(&self) -> <SE2 as LieGroup>::JacobianMatrix {
511 let theta = self.angle();
512 let cos_theta = theta.cos();
513 let sin_theta = theta.sin();
514 let theta_sq = theta * theta;
515
516 let (a, b) = if theta_sq < crate::SMALL_ANGLE_THRESHOLD {
517 let a = 1.0 - theta_sq / 6.0;
519 let b = 0.5 * theta - theta * theta_sq / 24.0;
520 (a, b)
521 } else {
522 let a = sin_theta / theta;
524 let b = (1.0 - cos_theta) / theta;
525 (a, b)
526 };
527
528 let mut jac = Matrix3::identity();
529 jac[(0, 0)] = a;
530 jac[(0, 1)] = -b;
531 jac[(1, 0)] = b;
532 jac[(1, 1)] = a;
533
534 if theta_sq < crate::SMALL_ANGLE_THRESHOLD {
535 jac[(0, 2)] = self.y() / 2.0 + theta * self.x() / 6.0;
536 jac[(1, 2)] = -self.x() / 2.0 + theta * self.y() / 6.0;
537 } else {
538 jac[(0, 2)] =
539 (self.y() + theta * self.x() - self.y() * cos_theta - self.x() * sin_theta)
540 / theta_sq;
541 jac[(1, 2)] = (-self.x() + theta * self.y() + self.x() * cos_theta
542 - self.y() * sin_theta)
543 / theta_sq;
544 }
545
546 jac
547 }
548
549 fn right_jacobian_inv(&self) -> <SE2 as LieGroup>::JacobianMatrix {
551 let theta = self.angle();
552 let cos_theta = theta.cos();
553 let sin_theta = theta.sin();
554 let theta_sq = theta * theta;
555
556 let mut jac_inv = Matrix3::zeros();
557 jac_inv[(0, 1)] = -theta * 0.5;
558 jac_inv[(1, 0)] = -jac_inv[(0, 1)];
559 jac_inv[(2, 2)] = 1.0;
560
561 if theta_sq > crate::SMALL_ANGLE_THRESHOLD {
562 let a = theta * sin_theta;
563 let b = theta * cos_theta;
564
565 jac_inv[(0, 0)] = -a / (2.0 * cos_theta - 2.0);
566 jac_inv[(1, 1)] = jac_inv[(0, 0)];
567
568 let den = 2.0 * theta * (cos_theta - 1.0);
569 jac_inv[(0, 2)] = (a * self.x() + b * self.y() - theta * self.y()
570 + 2.0 * self.x() * cos_theta
571 - 2.0 * self.x())
572 / den;
573 jac_inv[(1, 2)] =
574 (-b * self.x() + a * self.y() + theta * self.x() + 2.0 * self.y() * cos_theta
575 - 2.0 * self.y())
576 / den;
577 } else {
578 jac_inv[(0, 0)] = 1.0 - theta_sq / 12.0;
579 jac_inv[(1, 1)] = jac_inv[(0, 0)];
580
581 jac_inv[(0, 2)] = self.y() / 2.0 + theta * self.x() / 12.0;
582 jac_inv[(1, 2)] = -self.x() / 2.0 + theta * self.y() / 12.0;
583 }
584
585 jac_inv
586 }
587
588 fn left_jacobian_inv(&self) -> <SE2 as LieGroup>::JacobianMatrix {
590 let theta = self.angle();
591 let cos_theta = theta.cos();
592 let sin_theta = theta.sin();
593 let theta_sq = theta * theta;
594
595 let mut jac_inv = Matrix3::zeros();
596 jac_inv[(0, 1)] = theta * 0.5;
597 jac_inv[(1, 0)] = -jac_inv[(0, 1)];
598 jac_inv[(2, 2)] = 1.0;
599
600 if theta_sq > crate::SMALL_ANGLE_THRESHOLD {
601 let a = theta * sin_theta;
602 let b = theta * cos_theta;
603
604 jac_inv[(0, 0)] = -a / (2.0 * cos_theta - 2.0);
605 jac_inv[(1, 1)] = jac_inv[(0, 0)];
606
607 let den = 2.0 * theta * (cos_theta - 1.0);
608 jac_inv[(0, 2)] =
609 (a * self.x() - b * self.y() + theta * self.y() + 2.0 * self.x() * cos_theta
610 - 2.0 * self.x())
611 / den;
612 jac_inv[(1, 2)] = (b * self.x() + a * self.y() - theta * self.x()
613 + 2.0 * self.y() * cos_theta
614 - 2.0 * self.y())
615 / den;
616 } else {
617 jac_inv[(0, 0)] = 1.0 - theta_sq / 12.0;
618 jac_inv[(1, 1)] = jac_inv[(0, 0)];
619
620 jac_inv[(0, 2)] = -self.y() / 2.0 + theta * self.x() / 12.0;
621 jac_inv[(1, 2)] = self.x() / 2.0 + theta * self.y() / 12.0;
622 }
623
624 jac_inv
625 }
626
627 fn hat(&self) -> <SE2 as LieGroup>::LieAlgebra {
629 Matrix3::new(
630 0.0,
631 -self.angle(),
632 self.x(),
633 self.angle(),
634 0.0,
635 self.y(),
636 0.0,
637 0.0,
638 0.0,
639 )
640 }
641
642 fn zero() -> <SE2 as LieGroup>::TangentVector {
644 SE2Tangent::new(0.0, 0.0, 0.0)
645 }
646
647 fn random() -> <SE2 as LieGroup>::TangentVector {
649 use rand::Rng;
650 let mut rng = rand::rng();
651 SE2Tangent::new(
652 rng.random_range(-1.0..1.0),
653 rng.random_range(-1.0..1.0),
654 rng.random_range(-std::f64::consts::PI..std::f64::consts::PI),
655 )
656 }
657
658 fn is_zero(&self, tolerance: f64) -> bool {
660 self.data.norm() < tolerance
661 }
662
663 fn normalize(&mut self) {
665 let norm = self.data.norm();
666 if norm > f64::EPSILON {
667 self.data /= norm;
668 }
669 }
670
671 fn normalized(&self) -> <SE2 as LieGroup>::TangentVector {
673 let norm = self.data.norm();
674 if norm > f64::EPSILON {
675 SE2Tangent {
676 data: self.data / norm,
677 }
678 } else {
679 SE2Tangent::zero()
680 }
681 }
682
683 fn as_slice(&self) -> &[f64] {
684 self.data.as_slice()
685 }
686
687 fn from_slice(s: &[f64]) -> Self {
688 debug_assert_eq!(s.len(), 3);
689 SE2Tangent {
690 data: Vector3::from_column_slice(s),
691 }
692 }
693
694 fn small_adj(&self) -> <SE2 as LieGroup>::JacobianMatrix {
698 let x = self.x();
699 let y = self.y();
700
701 let mut small_adj = Matrix3::zeros();
702 small_adj[(0, 1)] = -self.angle();
703 small_adj[(1, 0)] = self.angle();
704 small_adj[(0, 2)] = y;
705 small_adj[(1, 2)] = -x;
706
707 small_adj
708 }
709
710 fn lie_bracket(&self, other: &Self) -> <SE2 as LieGroup>::TangentVector {
714 let bracket_result = self.small_adj() * other.data;
715 SE2Tangent {
716 data: bracket_result,
717 }
718 }
719
720 fn is_approx(&self, other: &Self, tolerance: f64) -> bool {
726 (self.data - other.data).norm() < tolerance
727 }
728
729 fn generator(&self, i: usize) -> <SE2 as LieGroup>::LieAlgebra {
737 assert!(i < 3, "SE(2) only has generators for indices 0, 1, 2");
738
739 match i {
740 0 => {
741 Matrix3::new(0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
743 }
744 1 => {
745 Matrix3::new(0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0)
747 }
748 2 => {
749 Matrix3::new(0.0, -1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0)
751 }
752 _ => unreachable!(),
753 }
754 }
755}
756
757#[cfg(test)]
758mod tests {
759 use super::*;
760 use std::f64::consts::PI;
761
762 const TOLERANCE: f64 = 1e-12;
763
764 #[test]
765 fn test_se2_tangent_basic() {
766 let tangent = SE2Tangent::new(4.0, 2.0, PI);
767 assert_eq!(tangent.x(), 4.0);
768 assert_eq!(tangent.y(), 2.0);
769 assert_eq!(tangent.angle(), PI);
770 }
771
772 #[test]
773 fn test_se2_tangent_zero() {
774 let zero = SE2Tangent::zero();
775 assert_eq!(zero.data, Vector3::zeros());
776 assert!(zero.is_zero(1e-10));
777 }
778
779 #[test]
780 fn test_se2_identity() {
781 let identity = SE2::identity();
782 assert!(identity.is_valid(TOLERANCE));
783 assert_eq!(identity.x(), 0.0);
784 assert_eq!(identity.y(), 0.0);
785 assert_eq!(identity.angle(), 0.0);
786 }
787
788 #[test]
791 fn test_se2_is_valid_rejects_nonfinite_at_every_index() {
792 let base = SE2::identity();
793 for idx in 0..SE2::REP_SIZE {
794 for bad in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
795 let mut params: Vec<f64> = base.as_param_slice().to_vec();
796 params[idx] = bad;
797 let perturbed = SE2::from_param_slice(¶ms);
798 assert!(
799 !perturbed.is_valid(TOLERANCE),
800 "index {idx} = {bad} must be rejected"
801 );
802 }
803 }
804 }
805
806 #[test]
807 fn test_se2_new() {
808 let translation = Vector2::new(1.0, 2.0);
809 let rotation = UnitComplex::from_angle(PI / 4.0);
810 let se2 = SE2::new(translation, rotation);
811
812 assert!(se2.is_valid(TOLERANCE));
813 assert_eq!(se2.x(), 1.0);
814 assert_eq!(se2.y(), 2.0);
815 assert!((se2.angle() - PI / 4.0).abs() < TOLERANCE);
816 }
817
818 #[test]
819 fn test_se2_from_xy_angle() {
820 let se2 = SE2::from_xy_angle(4.0, 2.0, 0.0);
821 assert_eq!(se2.x(), 4.0);
822 assert_eq!(se2.y(), 2.0);
823 assert_eq!(se2.angle(), 0.0);
824 }
825
826 #[test]
827 fn test_se2_from_xy_complex() {
828 let se2 = SE2::from_xy_complex(4.0, 2.0, 1.0, 0.0);
829 assert_eq!(se2.x(), 4.0);
830 assert_eq!(se2.y(), 2.0);
831 assert_eq!(se2.real(), 1.0);
832 assert_eq!(se2.imag(), 0.0);
833 assert_eq!(se2.angle(), 0.0);
834 }
835
836 #[test]
837 fn test_se2_inverse() {
838 let se2 = SE2::from_xy_angle(1.0, 1.0, PI);
839 let se2_inv = se2.inverse(None);
840
841 assert!((se2_inv.x() - 1.0).abs() < TOLERANCE);
842 assert!((se2_inv.y() - 1.0).abs() < TOLERANCE);
843 assert!((se2_inv.angle() + PI).abs() < TOLERANCE);
844
845 let composed = se2.compose(&se2_inv, None, None);
847 let identity = SE2::identity();
848
849 assert!((composed.x() - identity.x()).abs() < TOLERANCE);
850 assert!((composed.y() - identity.y()).abs() < TOLERANCE);
851 assert!((composed.angle() - identity.angle()).abs() < TOLERANCE);
852 }
853
854 #[test]
855 fn test_se2_compose() {
856 let se2a = SE2::from_xy_angle(1.0, 1.0, PI / 2.0);
857 let se2b = SE2::from_xy_angle(2.0, 2.0, PI / 2.0);
858 let se2c = se2a.compose(&se2b, None, None);
859
860 assert!((se2c.x() - (-1.0)).abs() < TOLERANCE);
861 assert!((se2c.y() - 3.0).abs() < TOLERANCE);
862 assert!((se2c.angle() - PI).abs() < TOLERANCE);
863 }
864
865 #[test]
866 fn test_se2_exp_log() {
867 let tangent = SE2Tangent::new(4.0, 2.0, PI);
868 let se2 = tangent.exp(None);
869 let recovered_tangent = se2.log(None);
870
871 assert!((tangent.x() - recovered_tangent.x()).abs() < TOLERANCE);
872 assert!((tangent.y() - recovered_tangent.y()).abs() < TOLERANCE);
873 assert!((tangent.angle() - recovered_tangent.angle()).abs() < TOLERANCE);
874 }
875
876 #[test]
877 fn test_se2_exp_zero() {
878 let zero_tangent = SE2Tangent::zero();
879 let se2 = zero_tangent.exp(None);
880 let identity = SE2::identity();
881
882 assert!((se2.x() - identity.x()).abs() < TOLERANCE);
883 assert!((se2.y() - identity.y()).abs() < TOLERANCE);
884 assert!((se2.angle() - identity.angle()).abs() < TOLERANCE);
885 }
886
887 #[test]
888 fn test_se2_log_identity() {
889 let identity = SE2::identity();
890 let tangent = identity.log(None);
891
892 assert!(tangent.data.norm() < TOLERANCE);
893 }
894
895 #[test]
896 fn test_se2_act() {
897 let se2 = SE2::from_xy_angle(1.0, 1.0, PI / 2.0);
898 let point = Vector3::new(1.0, 1.0, 0.0);
899 let transformed_point = se2.act(&point, None, None);
900
901 assert!((transformed_point.x - 0.0).abs() < TOLERANCE);
902 assert!((transformed_point.y - 2.0).abs() < TOLERANCE);
903 assert!((transformed_point.z - 0.0).abs() < TOLERANCE);
904 }
905
906 #[test]
907 fn test_se2_between() {
908 let se2a = SE2::from_xy_angle(1.0, 1.0, PI);
909 let se2b = SE2::from_xy_angle(1.0, 1.0, PI);
910 let se2c = se2a.between(&se2b, None, None);
911
912 assert!((se2c.x() - 0.0).abs() < TOLERANCE);
913 assert!((se2c.y() - 0.0).abs() < TOLERANCE);
914 assert!((se2c.angle() - 0.0).abs() < TOLERANCE);
915 }
916
917 #[test]
918 fn test_se2_adjoint() {
919 let se2 = SE2::random();
920 let adj = se2.adjoint();
921
922 assert_eq!(adj.nrows(), 3);
923 assert_eq!(adj.ncols(), 3);
924 }
925
926 #[test]
927 fn test_se2_manifold_properties() {
928 assert_eq!(SE2::DIM, 2);
929 assert_eq!(SE2::DOF, 3);
930 assert_eq!(SE2::REP_SIZE, 3);
931 }
932
933 #[test]
934 fn test_se2_random() {
935 let se2 = SE2::random();
936 assert!(se2.is_valid(TOLERANCE));
937 }
938
939 #[test]
940 fn test_se2_normalize() {
941 let mut se2 = SE2::from_xy_complex(1.0, 2.0, 0.5, 0.5); se2.normalize();
943 assert!(se2.is_valid(TOLERANCE));
944 }
945
946 #[test]
947 fn test_se2_tangent_exp_jacobians() {
948 let tangent = SE2Tangent::new(1.0, 2.0, 0.1);
949
950 let se2_element = tangent.exp(None);
951 assert!(se2_element.is_valid(TOLERANCE));
952
953 let right_jac = tangent.right_jacobian();
955 let left_jac = tangent.left_jacobian();
956 let right_jac_inv = tangent.right_jacobian_inv();
957 let left_jac_inv = tangent.left_jacobian_inv();
958
959 assert_eq!(right_jac.nrows(), 3);
960 assert_eq!(right_jac.ncols(), 3);
961 assert_eq!(left_jac.nrows(), 3);
962 assert_eq!(left_jac.ncols(), 3);
963 assert_eq!(right_jac_inv.nrows(), 3);
964 assert_eq!(right_jac_inv.ncols(), 3);
965 assert_eq!(left_jac_inv.nrows(), 3);
966 assert_eq!(left_jac_inv.ncols(), 3);
967 }
968
969 #[test]
970 fn test_se2_tangent_hat() {
971 let tangent = SE2Tangent::new(4.0, 2.0, PI);
972 let hat_matrix = tangent.hat();
973
974 assert_eq!(hat_matrix.nrows(), 3);
975 assert_eq!(hat_matrix.ncols(), 3);
976 assert_eq!(hat_matrix[(0, 2)], 4.0);
977 assert_eq!(hat_matrix[(1, 2)], 2.0);
978 assert_eq!(hat_matrix[(1, 0)], PI);
979 assert_eq!(hat_matrix[(0, 1)], -PI);
980 }
981
982 #[test]
983 fn test_se2_consistency() {
984 let se2_1 = SE2::random();
986 let se2_2 = SE2::random();
987 let se2_3 = SE2::random();
988
989 let left_assoc = se2_1
990 .compose(&se2_2, None, None)
991 .compose(&se2_3, None, None);
992 let right_assoc = se2_1.compose(&se2_2.compose(&se2_3, None, None), None, None);
993
994 let translation_diff = (left_assoc.translation() - right_assoc.translation()).norm();
995 let angle_diff = (left_assoc.angle() - right_assoc.angle()).abs();
996
997 assert!(translation_diff < 1e-10);
998 assert!(angle_diff < 1e-10);
999 }
1000
1001 #[test]
1002 fn test_se2_isometry() {
1003 let translation = Translation2::new(1.0, 2.0);
1004 let rotation = UnitComplex::from_angle(PI / 4.0);
1005 let isometry = Isometry2::from_parts(translation, rotation);
1006
1007 let se2 = SE2::from_isometry(isometry);
1008 let recovered_isometry = se2.isometry();
1009
1010 let translation_diff =
1011 (isometry.translation.vector - recovered_isometry.translation.vector).norm();
1012 let angle_diff = (isometry.rotation.angle() - recovered_isometry.rotation.angle()).abs();
1013
1014 assert!(translation_diff < TOLERANCE);
1015 assert!(angle_diff < TOLERANCE);
1016 }
1017
1018 #[test]
1019 fn test_se2_matrix() {
1020 let se2 = SE2::random();
1021 let matrix = se2.matrix();
1022
1023 assert_eq!(matrix.nrows(), 3);
1025 assert_eq!(matrix.ncols(), 3);
1026
1027 assert!((matrix[(2, 0)]).abs() < TOLERANCE);
1029 assert!((matrix[(2, 1)]).abs() < TOLERANCE);
1030 assert!((matrix[(2, 2)] - 1.0).abs() < TOLERANCE);
1031 }
1032
1033 #[test]
1036 fn test_se2_constructor_copy() {
1037 let se2_original = SE2::from_xy_complex(4.0, 2.0, (PI / 4.0).cos(), (PI / 4.0).sin());
1038 let se2_copy = se2_original.clone();
1039
1040 assert_eq!(se2_copy.x(), 4.0);
1041 assert_eq!(se2_copy.y(), 2.0);
1042 assert!((se2_copy.angle() - PI / 4.0).abs() < TOLERANCE);
1043 }
1044
1045 #[test]
1046 fn test_se2_assign_op() {
1047 let _se2a = SE2::from_xy_angle(0.0, 0.0, 0.0);
1048 let se2b = SE2::from_xy_angle(4.0, 2.0, PI);
1049
1050 let se2a = se2b.clone(); assert_eq!(se2a.x(), 4.0);
1053 assert_eq!(se2a.y(), 2.0);
1054 assert!((se2a.angle() - PI).abs() < TOLERANCE);
1055 }
1056
1057 #[test]
1058 fn test_se2_inverse_detailed() {
1059 let se2 = SE2::identity();
1061 let se2_inv = se2.inverse(None);
1062
1063 assert!((se2_inv.x() - 0.0).abs() < TOLERANCE);
1064 assert!((se2_inv.y() - 0.0).abs() < TOLERANCE);
1065 assert!((se2_inv.angle() - 0.0).abs() < TOLERANCE);
1066 assert!((se2_inv.real() - 1.0).abs() < TOLERANCE);
1067 assert!((se2_inv.imag() - 0.0).abs() < TOLERANCE);
1068
1069 let se2 = SE2::from_xy_angle(0.7, 2.3, PI / 3.0);
1071 let se2_inv = se2.inverse(None);
1072
1073 assert!((se2_inv.x() - (-2.341858428704209)).abs() < 1e-10);
1074 assert!((se2_inv.y() - (-0.543782217350893)).abs() < 1e-10);
1075 assert!((se2_inv.angle() - (-PI / 3.0)).abs() < 1e-10);
1076 }
1077
1078 #[test]
1079 fn test_se2_rplus_zero() {
1080 let se2a = SE2::from_xy_angle(1.0, 1.0, PI / 2.0);
1081 let se2b = SE2Tangent::zero();
1082
1083 let se2c = se2a.right_plus(&se2b, None, None);
1084
1085 assert!((se2c.x() - 1.0).abs() < TOLERANCE);
1086 assert!((se2c.y() - 1.0).abs() < TOLERANCE);
1087 assert!((se2c.angle() - PI / 2.0).abs() < TOLERANCE);
1088 }
1089
1090 #[test]
1091 fn test_se2_rplus() {
1092 let se2a = SE2::from_xy_angle(1.0, 1.0, PI / 2.0);
1093 let se2b = SE2Tangent::new(1.0, 1.0, PI / 2.0);
1094
1095 let se2c = se2a.right_plus(&se2b, None, None);
1096
1097 assert!((se2c.angle() - PI).abs() < TOLERANCE);
1098 }
1099
1100 #[test]
1101 fn test_se2_lplus_zero() {
1102 let se2a = SE2::from_xy_angle(1.0, 1.0, PI / 2.0);
1103 let se2b = SE2Tangent::zero();
1104
1105 let se2c = se2a.left_plus(&se2b, None, None);
1106
1107 assert!((se2c.x() - 1.0).abs() < TOLERANCE);
1108 assert!((se2c.y() - 1.0).abs() < TOLERANCE);
1109 assert!((se2c.angle() - PI / 2.0).abs() < TOLERANCE);
1110 }
1111
1112 #[test]
1113 fn test_se2_lplus() {
1114 let se2a = SE2::from_xy_angle(1.0, 1.0, PI / 2.0);
1115 let se2b = SE2Tangent::new(1.0, 1.0, PI / 2.0);
1116
1117 let se2c = se2a.left_plus(&se2b, None, None);
1118
1119 assert!((se2c.angle() - PI).abs() < TOLERANCE);
1120 }
1121
1122 #[test]
1123 fn test_se2_rminus_zero() {
1124 let se2a = SE2::identity();
1125 let se2b = SE2::identity();
1126
1127 let se2c = se2a.right_minus(&se2b, None, None);
1128
1129 assert!((se2c.x() - 0.0).abs() < TOLERANCE);
1130 assert!((se2c.y() - 0.0).abs() < TOLERANCE);
1131 assert!((se2c.angle() - 0.0).abs() < TOLERANCE);
1132 }
1133
1134 #[test]
1135 fn test_se2_rminus() {
1136 let se2a = SE2::from_xy_angle(1.0, 1.0, PI);
1137 let se2b = SE2::from_xy_angle(2.0, 2.0, PI / 2.0);
1138
1139 let se2c = se2a.right_minus(&se2b, None, None);
1140
1141 assert!((se2c.angle() - PI / 2.0).abs() < TOLERANCE);
1142 }
1143
1144 #[test]
1145 fn test_se2_lminus_identity() {
1146 let se2a = SE2::identity();
1147 let se2b = SE2::identity();
1148
1149 let se2c = se2a.left_minus(&se2b, None, None);
1150
1151 assert!((se2c.x() - 0.0).abs() < TOLERANCE);
1152 assert!((se2c.y() - 0.0).abs() < TOLERANCE);
1153 assert!((se2c.angle() - 0.0).abs() < TOLERANCE);
1154 }
1155
1156 #[test]
1157 fn test_se2_lminus() {
1158 let se2a = SE2::from_xy_angle(1.0, 1.0, PI);
1159 let se2b = SE2::from_xy_angle(2.0, 2.0, PI / 2.0);
1160
1161 let se2c = se2a.left_minus(&se2b, None, None);
1162
1163 assert!((se2c.angle() - PI / 2.0).abs() < TOLERANCE);
1164 }
1165
1166 #[test]
1167 fn test_se2_lift() {
1168 let se2 = SE2::from_xy_angle(1.0, 1.0, PI);
1169 let se2_log = se2.log(None);
1170
1171 assert!((se2_log.angle() - PI).abs() < TOLERANCE);
1172 }
1173
1174 #[test]
1175 fn test_se2_compose_detailed() {
1176 let se2a = SE2::from_xy_angle(1.0, 1.0, PI / 2.0);
1177 let se2b = SE2::from_xy_angle(2.0, 2.0, PI / 2.0);
1178
1179 let se2c = se2a.compose(&se2b, None, None);
1180
1181 assert!((se2c.x() - (-1.0)).abs() < TOLERANCE);
1182 assert!((se2c.y() - 3.0).abs() < TOLERANCE);
1183 assert!((se2c.angle() - PI).abs() < TOLERANCE);
1184 }
1185
1186 #[test]
1187 fn test_se2_between_identity() {
1188 let se2a = SE2::from_xy_angle(1.0, 1.0, PI);
1189 let se2b = SE2::from_xy_angle(1.0, 1.0, PI);
1190
1191 let se2c = se2a.between(&se2b, None, None);
1192
1193 assert!((se2c.x() - 0.0).abs() < TOLERANCE);
1194 assert!((se2c.y() - 0.0).abs() < TOLERANCE);
1195 assert!((se2c.angle() - 0.0).abs() < TOLERANCE);
1196 }
1197
1198 #[test]
1199 fn test_se2_between_detailed() {
1200 let se2a = SE2::from_xy_angle(1.0, 1.0, PI);
1201 let se2b = SE2::from_xy_angle(2.0, 2.0, PI / 2.0);
1202
1203 let se2c = se2a.between(&se2b, None, None);
1204
1205 assert!((se2c.x() - (-1.0)).abs() < TOLERANCE);
1206 assert!((se2c.y() - (-1.0)).abs() < TOLERANCE);
1207 assert!((se2c.angle() - (-PI / 2.0)).abs() < TOLERANCE);
1208 }
1209
1210 #[test]
1211 fn test_se2_act_detailed() {
1212 let se2 = SE2::from_xy_angle(1.0, 1.0, PI / 2.0);
1213 let point = Vector3::new(1.0, 1.0, 0.0);
1214 let transformed_point = se2.act(&point, None, None);
1215
1216 assert!((transformed_point.x - 0.0).abs() < TOLERANCE);
1217 assert!((transformed_point.y - 2.0).abs() < TOLERANCE);
1218
1219 let se2 = SE2::from_xy_angle(1.0, 1.0, -PI / 2.0);
1220 let transformed_point = se2.act(&point, None, None);
1221
1222 assert!((transformed_point.x - 2.0).abs() < TOLERANCE);
1223 assert!((transformed_point.y - 0.0).abs() < TOLERANCE);
1224
1225 let se2 = SE2::identity();
1226 let transformed_point = se2.act(&point, None, None);
1227
1228 assert!((transformed_point.x - 1.0).abs() < TOLERANCE);
1229 assert!((transformed_point.y - 1.0).abs() < TOLERANCE);
1230 }
1231
1232 #[test]
1233 fn test_se2_rotation_matrix() {
1234 let se2 = SE2::identity();
1235 let r = se2.rotation_matrix();
1236
1237 assert_eq!(r.nrows(), 2);
1238 assert_eq!(r.ncols(), 2);
1239
1240 assert!((r[(0, 0)] - 1.0).abs() < TOLERANCE);
1242 assert!((r[(0, 1)] - 0.0).abs() < TOLERANCE);
1243 assert!((r[(1, 0)] - 0.0).abs() < TOLERANCE);
1244 assert!((r[(1, 1)] - 1.0).abs() < TOLERANCE);
1245 }
1246
1247 #[test]
1250 fn test_se2_tangent_data() {
1251 let se2_tan = SE2Tangent::new(4.0, 2.0, PI);
1252
1253 assert_eq!(se2_tan.x(), 4.0);
1255 assert_eq!(se2_tan.y(), 2.0);
1256 assert_eq!(se2_tan.angle(), PI);
1257 }
1258
1259 #[test]
1260 fn test_se2_tangent_retract() {
1261 let se2_tan = SE2Tangent::new(4.0, 2.0, PI);
1262
1263 assert_eq!(se2_tan.x(), 4.0);
1264 assert_eq!(se2_tan.y(), 2.0);
1265 assert_eq!(se2_tan.angle(), PI);
1266
1267 let se2_exp = se2_tan.exp(None);
1268
1269 assert!((se2_exp.real() - PI.cos()).abs() < TOLERANCE);
1270 assert!((se2_exp.imag() - PI.sin()).abs() < TOLERANCE);
1271 assert!((se2_exp.angle() - PI).abs() < TOLERANCE);
1272 }
1273
1274 #[test]
1275 fn test_se2_tangent_retract_jac() {
1276 let se2_tan = SE2Tangent::new(4.0, 2.0, PI);
1277
1278 let mut j_ret = Matrix3::zeros();
1279 let se2_exp = se2_tan.exp(Some(&mut j_ret));
1280
1281 assert!((se2_exp.real() - PI.cos()).abs() < TOLERANCE);
1282 assert!((se2_exp.imag() - PI.sin()).abs() < TOLERANCE);
1283 assert!((se2_exp.angle() - PI).abs() < TOLERANCE);
1284
1285 assert_eq!(j_ret.nrows(), 3);
1287 assert_eq!(j_ret.ncols(), 3);
1288 }
1289
1290 #[test]
1291 fn test_se2_small_angle_approximations() {
1292 let small_tangent = SE2Tangent::new(1e-8, 2e-8, 1e-9);
1294
1295 let se2 = small_tangent.exp(None);
1296 let recovered = se2.log(None);
1297
1298 let diff = (Vector3::new(small_tangent.x(), small_tangent.y(), small_tangent.angle())
1299 - Vector3::new(recovered.x(), recovered.y(), recovered.angle()))
1300 .norm();
1301 assert!(diff < TOLERANCE);
1302 }
1303
1304 #[test]
1305 fn test_se2_tangent_norm() {
1306 let tangent = SE2Tangent::new(3.0, 4.0, 0.0);
1307 let norm = Vector3::new(tangent.x(), tangent.y(), tangent.angle()).norm();
1308 assert!((norm - 5.0).abs() < TOLERANCE); }
1310
1311 #[test]
1314 fn test_se2_vee() {
1315 let se2 = SE2::random();
1316 let tangent_log = se2.log(None);
1317 let tangent_vee = se2.vee();
1318
1319 assert!((tangent_log.data - tangent_vee.data).norm() < 1e-10);
1320 }
1321
1322 #[test]
1323 fn test_se2_is_approx() {
1324 let se2_1 = SE2::random();
1325 let se2_2 = se2_1.clone();
1326
1327 assert!(se2_1.is_approx(&se2_1, 1e-10));
1328 assert!(se2_1.is_approx(&se2_2, 1e-10));
1329
1330 let small_tangent = SE2Tangent::new(1e-12, 1e-12, 1e-12);
1332 let se2_perturbed = se2_1.right_plus(&small_tangent, None, None);
1333 assert!(se2_1.is_approx(&se2_perturbed, 1e-10));
1334 }
1335
1336 #[test]
1337 fn test_se2_tangent_small_adj() {
1338 let tangent = SE2Tangent::new(0.1, 0.2, 0.3);
1339 let small_adj = tangent.small_adj();
1340
1341 assert_eq!(small_adj[(0, 0)], 0.0);
1347 assert_eq!(small_adj[(1, 1)], 0.0);
1348 assert_eq!(small_adj[(2, 2)], 0.0);
1349 assert_eq!(small_adj[(0, 1)], -tangent.angle());
1350 assert_eq!(small_adj[(1, 0)], tangent.angle());
1351 assert_eq!(small_adj[(0, 2)], tangent.y());
1352 assert_eq!(small_adj[(1, 2)], -tangent.x());
1353 }
1354
1355 #[test]
1356 fn test_se2_tangent_lie_bracket() {
1357 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);
1361 let bracket_ba = tangent_b.lie_bracket(&tangent_a);
1362
1363 assert!((bracket_ab.data + bracket_ba.data).norm() < 1e-10);
1365
1366 let bracket_aa = tangent_a.lie_bracket(&tangent_a);
1368 assert!(bracket_aa.is_zero(1e-10));
1369
1370 let bracket_hat = bracket_ab.hat();
1372 let expected = tangent_a.hat() * tangent_b.hat() - tangent_b.hat() * tangent_a.hat();
1373 assert!((bracket_hat - expected).norm() < 1e-10);
1374 }
1375
1376 #[test]
1377 fn test_se2_tangent_is_approx() {
1378 let tangent_1 = SE2Tangent::new(0.1, 0.2, 0.3);
1379 let tangent_2 = SE2Tangent::new(0.1 + 1e-12, 0.2, 0.3);
1380 let tangent_3 = SE2Tangent::new(0.5, 0.6, 0.7);
1381
1382 assert!(tangent_1.is_approx(&tangent_1, 1e-10));
1383 assert!(tangent_1.is_approx(&tangent_2, 1e-10));
1384 assert!(!tangent_1.is_approx(&tangent_3, 1e-10));
1385 }
1386
1387 #[test]
1388 fn test_se2_generators() {
1389 let tangent = SE2Tangent::new(1.0, 1.0, 1.0);
1390
1391 for i in 0..3 {
1393 let generator = tangent.generator(i);
1394
1395 assert_eq!(generator.nrows(), 3);
1397 assert_eq!(generator.ncols(), 3);
1398 }
1399
1400 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);
1407 let expected_e2 = Matrix3::new(0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0);
1408 let expected_e3 = Matrix3::new(0.0, -1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0);
1409
1410 assert!((e1 - expected_e1).norm() < 1e-10);
1411 assert!((e2 - expected_e2).norm() < 1e-10);
1412 assert!((e3 - expected_e3).norm() < 1e-10);
1413 }
1414
1415 #[test]
1416 #[should_panic]
1417 fn test_se2_generator_invalid_index() {
1418 let tangent = SE2Tangent::new(1.0, 1.0, 1.0);
1419 let _generator = tangent.generator(3); }
1421
1422 #[test]
1423 fn test_se2_jacobi_identity() {
1424 let x = SE2Tangent::new(0.1, 0.0, 0.0);
1426 let y = SE2Tangent::new(0.0, 0.2, 0.0);
1427 let z = SE2Tangent::new(0.0, 0.0, 0.3);
1428
1429 let term1 = x.lie_bracket(&y.lie_bracket(&z));
1430 let term2 = y.lie_bracket(&z.lie_bracket(&x));
1431 let term3 = z.lie_bracket(&x.lie_bracket(&y));
1432
1433 let jacobi_sum = SE2Tangent {
1434 data: term1.data + term2.data + term3.data,
1435 };
1436 assert!(jacobi_sum.is_zero(1e-10));
1437 }
1438
1439 #[test]
1440 fn test_se2_hat_vee_consistency() {
1441 let tangent = SE2Tangent::new(0.1, 0.2, 0.3);
1442 let hat_matrix = tangent.hat();
1443
1444 assert_eq!(hat_matrix[(0, 2)], tangent.x());
1447 assert_eq!(hat_matrix[(1, 2)], tangent.y());
1448 assert_eq!(hat_matrix[(0, 1)], -tangent.angle());
1449 assert_eq!(hat_matrix[(1, 0)], tangent.angle());
1450 assert_eq!(hat_matrix[(2, 0)], 0.0);
1451 assert_eq!(hat_matrix[(2, 1)], 0.0);
1452 assert_eq!(hat_matrix[(2, 2)], 0.0);
1453 }
1454
1455 #[test]
1458 fn test_se2_circular_path_accumulation() {
1459 let step = SE2::from_xy_angle(0.01, 0.0, (std::f64::consts::PI * 2.0) / 360.0);
1461
1462 let mut pose = SE2::identity();
1463 for _ in 0..360 {
1464 pose = pose.compose(&step, None, None);
1465 }
1466
1467 assert!(pose.is_approx(&SE2::identity(), 1e-2));
1469 }
1470
1471 #[test]
1474 fn test_se2_angle_wrap_around() {
1475 let se2_wrapped = SE2::from_xy_angle(1.0, 2.0, 3.0 * std::f64::consts::PI);
1477 let se2_canonical = SE2::from_xy_angle(1.0, 2.0, std::f64::consts::PI);
1478
1479 let tangent_wrapped = se2_wrapped.log(None);
1481 let tangent_canonical = se2_canonical.log(None);
1482
1483 let angle_diff = (tangent_wrapped.angle() - tangent_canonical.angle()).abs();
1485 assert!(angle_diff < 1e-10 || (angle_diff - 2.0 * std::f64::consts::PI).abs() < 1e-10);
1486 }
1487
1488 #[test]
1489 fn test_se2_negative_angles() {
1490 let se2_neg = SE2::from_xy_angle(0.5, -0.3, -std::f64::consts::PI / 4.0);
1491 let tangent = se2_neg.log(None);
1492 let recovered = tangent.exp(None);
1493 assert!(se2_neg.is_approx(&recovered, 1e-10));
1494 }
1495
1496 #[test]
1499 fn test_se2_right_jacobian_inverse_identity() {
1500 let test_tangents = vec![
1501 SE2Tangent::new(0.1, 0.2, 0.3),
1502 SE2Tangent::new(0.5, -0.3, 1.5),
1503 ];
1504
1505 for tangent in test_tangents {
1506 let jr = tangent.right_jacobian();
1507 let jr_inv = tangent.right_jacobian_inv();
1508 let product = jr * jr_inv;
1509 let identity = Matrix3::identity();
1510
1511 assert!((product - identity).norm() < 1e-10);
1512 }
1513 }
1514
1515 #[test]
1516 fn test_se2_left_jacobian_inverse_identity() {
1517 let tangent = SE2Tangent::new(0.2, 0.3, 0.8);
1519 let jl = tangent.left_jacobian();
1520 let jl_inv = tangent.left_jacobian_inv();
1521 let product = jl * jl_inv;
1522
1523 assert!((product - Matrix3::identity()).norm() < 1e-10);
1524 }
1525
1526 #[test]
1527 fn test_se2_tangent_from_components() {
1528 let t1 = SE2Tangent::new(1.0, 2.0, 0.5);
1529 let t2 = SE2Tangent::from_components(1.0, 2.0, 0.5);
1530 assert!(t1.is_approx(&t2, 1e-15));
1531 assert_eq!(t2.x(), 1.0);
1532 assert_eq!(t2.y(), 2.0);
1533 assert_eq!(t2.angle(), 0.5);
1534 }
1535
1536 #[test]
1537 fn test_se2_small_angle_threshold_exp_log() {
1538 let near_threshold_angles = [1e-6, 1e-5, 1e-4, 1e-3];
1541
1542 for &angle in &near_threshold_angles {
1543 let tangent = SE2Tangent::new(1.0, 2.0, angle);
1544 let se2 = tangent.exp(None);
1545 let recovered = se2.log(None);
1546 assert!(
1547 (tangent.x() - recovered.x()).abs() < 1e-10
1548 && (tangent.y() - recovered.y()).abs() < 1e-10
1549 && (tangent.angle() - recovered.angle()).abs() < 1e-10,
1550 "SE2 exp-log round-trip failed for angle = {angle}"
1551 );
1552 }
1553 }
1554
1555 #[test]
1556 fn se2_param_slice_round_trip() {
1557 let g = SE2::random();
1558 let recovered = SE2::from_param_slice(g.as_param_slice());
1559 assert!(g.is_approx(&recovered, 1e-14));
1560 }
1561
1562 #[test]
1563 fn se2_tangent_slice_round_trip() {
1564 let t = SE2Tangent::random();
1565 let recovered = SE2Tangent::from_slice(t.as_slice());
1566 assert!(t.is_approx(&recovered, 1e-14));
1567 }
1568}