1use crate::{
31 LieGroup, Tangent,
32 so3::{SO3, SO3Tangent},
33};
34use nalgebra::{
35 Isometry3, Matrix3, Matrix4, Matrix6, Quaternion, SVector, Translation3, UnitQuaternion,
36 Vector3, Vector6,
37};
38use std::{
39 fmt,
40 fmt::{Display, Formatter},
41};
42
43#[derive(Clone, PartialEq)]
48pub struct SE3 {
49 params: SVector<f64, 7>,
51}
52
53impl Display for SE3 {
54 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
55 let t = self.translation();
56 let q = self.rotation_quaternion();
57 write!(
58 f,
59 "SE3(translation: [{:.4}, {:.4}, {:.4}], rotation: [w: {:.4}, x: {:.4}, y: {:.4}, z: {:.4}])",
60 t.x, t.y, t.z, q.w, q.i, q.j, q.k
61 )
62 }
63}
64
65impl SE3 {
66 pub const DIM: usize = 3;
68
69 pub const DOF: usize = 6;
71
72 pub const REP_SIZE: usize = 7;
74
75 #[inline]
76 fn translation_impl(&self) -> Vector3<f64> {
77 Vector3::new(self.params[0], self.params[1], self.params[2])
78 }
79
80 #[inline]
81 fn rotation_impl(&self) -> SO3 {
82 SO3::from_quaternion_wxyz(
83 self.params[3],
84 self.params[4],
85 self.params[5],
86 self.params[6],
87 )
88 }
89
90 #[inline]
91 fn from_parts(t: Vector3<f64>, r: &SO3) -> Self {
92 let q = r.params();
93 SE3 {
94 params: SVector::<f64, 7>::from([t.x, t.y, t.z, q[0], q[1], q[2], q[3]]),
95 }
96 }
97
98 pub fn identity() -> Self {
102 SE3 {
103 params: SVector::<f64, 7>::from([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]),
104 }
105 }
106
107 pub fn jacobian_identity() -> Matrix6<f64> {
111 Matrix6::<f64>::identity()
112 }
113
114 #[inline]
120 pub fn new(translation: Vector3<f64>, rotation: UnitQuaternion<f64>) -> Self {
121 SE3::from_parts(translation, &SO3::new(rotation))
122 }
123
124 pub fn from_translation_quaternion(
126 translation: Vector3<f64>,
127 quaternion: Quaternion<f64>,
128 ) -> Self {
129 let q = UnitQuaternion::from_quaternion(quaternion.normalize());
130 SE3::from_parts(translation, &SO3::new(q))
131 }
132
133 pub fn from_translation_euler(x: f64, y: f64, z: f64, roll: f64, pitch: f64, yaw: f64) -> Self {
135 let translation = Vector3::new(x, y, z);
136 let rotation = UnitQuaternion::from_euler_angles(roll, pitch, yaw);
137 SE3::from_parts(translation, &SO3::new(rotation))
138 }
139
140 pub fn from_isometry(isometry: Isometry3<f64>) -> Self {
142 SE3::from_parts(isometry.translation.vector, &SO3::new(isometry.rotation))
143 }
144
145 pub fn from_translation_so3(translation: Vector3<f64>, rotation: SO3) -> Self {
147 SE3::from_parts(translation, &rotation)
148 }
149
150 pub fn translation(&self) -> Vector3<f64> {
152 self.translation_impl()
153 }
154
155 pub fn rotation_so3(&self) -> SO3 {
157 self.rotation_impl()
158 }
159
160 pub fn rotation_quaternion(&self) -> UnitQuaternion<f64> {
162 self.rotation_impl().quaternion()
163 }
164
165 pub fn isometry(&self) -> Isometry3<f64> {
167 Isometry3::from_parts(
168 Translation3::from(self.translation()),
169 self.rotation_quaternion(),
170 )
171 }
172
173 pub fn matrix(&self) -> Matrix4<f64> {
175 self.isometry().to_homogeneous()
176 }
177
178 #[inline]
180 pub fn x(&self) -> f64 {
181 self.params[0]
182 }
183
184 #[inline]
186 pub fn y(&self) -> f64 {
187 self.params[1]
188 }
189
190 #[inline]
192 pub fn z(&self) -> f64 {
193 self.params[2]
194 }
195
196 pub fn coeffs(&self) -> [f64; 7] {
198 [
199 self.params[0],
200 self.params[1],
201 self.params[2],
202 self.params[3],
203 self.params[4],
204 self.params[5],
205 self.params[6],
206 ]
207 }
208}
209
210impl LieGroup for SE3 {
211 const NAME: &'static str = "SE3";
212
213 type TangentVector = SE3Tangent;
214 type JacobianMatrix = Matrix6<f64>;
215 type LieAlgebra = Matrix4<f64>;
216
217 fn inverse(&self, jacobian: Option<&mut Self::JacobianMatrix>) -> Self {
231 let rot = self.rotation_impl();
232 let rot_inv = rot.inverse(None);
233 let trans_inv = -rot_inv.act(&self.translation_impl(), None, None);
234
235 if let Some(jac) = jacobian {
236 *jac = -self.adjoint();
237 }
238
239 SE3::from_parts(trans_inv, &rot_inv)
240 }
241
242 fn compose(
262 &self,
263 other: &Self,
264 jacobian_self: Option<&mut Self::JacobianMatrix>,
265 jacobian_other: Option<&mut Self::JacobianMatrix>,
266 ) -> Self {
267 let rot = self.rotation_impl();
268 let composed_rotation = rot.compose(&other.rotation_impl(), None, None);
269 let composed_translation =
270 rot.act(&other.translation_impl(), None, None) + self.translation_impl();
271
272 let result = SE3::from_parts(composed_translation, &composed_rotation);
273
274 if let Some(jac_self) = jacobian_self {
275 *jac_self = other.inverse(None).adjoint();
276 }
277
278 if let Some(jac_other) = jacobian_other {
279 *jac_other = Matrix6::identity();
280 }
281
282 result
283 }
284
285 fn log(&self, jacobian: Option<&mut Self::JacobianMatrix>) -> Self::TangentVector {
299 let theta = self.rotation_impl().log(None);
300 let mut data = Vector6::zeros();
301 let translation_vector = theta.left_jacobian_inv() * self.translation_impl();
302 data.fixed_rows_mut::<3>(0).copy_from(&translation_vector);
303 data.fixed_rows_mut::<3>(3).copy_from(&theta.coeffs());
304 let result = SE3Tangent { data };
305 if let Some(jac) = jacobian {
306 *jac = result.right_jacobian_inv();
307 }
308
309 result
310 }
311
312 fn act(
313 &self,
314 vector: &Vector3<f64>,
315 jacobian_self: Option<&mut Self::JacobianMatrix>,
316 jacobian_vector: Option<&mut Matrix3<f64>>,
317 ) -> Vector3<f64> {
318 let rot = self.rotation_impl();
319 let result = rot.act(vector, None, None) + self.translation_impl();
320
321 if let Some(jac_self) = jacobian_self {
322 let rot_mat = rot.rotation_matrix();
323 jac_self.fixed_view_mut::<3, 3>(0, 0).copy_from(&rot_mat);
324 jac_self
325 .fixed_view_mut::<3, 3>(0, 3)
326 .copy_from(&(-rot_mat * SO3Tangent::new(*vector).hat()));
327 }
328
329 if let Some(jac_vector) = jacobian_vector {
330 jac_vector.copy_from(&rot.rotation_matrix());
331 }
332
333 result
334 }
335
336 fn adjoint(&self) -> Self::JacobianMatrix {
337 let rotation_matrix = self.rotation_impl().rotation_matrix();
338 let translation = self.translation_impl();
339 let mut adjoint_matrix = Matrix6::zeros();
340
341 adjoint_matrix
342 .fixed_view_mut::<3, 3>(0, 0)
343 .copy_from(&rotation_matrix);
344 adjoint_matrix
345 .fixed_view_mut::<3, 3>(3, 3)
346 .copy_from(&rotation_matrix);
347
348 let top_right = SO3Tangent::new(translation).hat() * rotation_matrix;
349 adjoint_matrix
350 .fixed_view_mut::<3, 3>(0, 3)
351 .copy_from(&top_right);
352
353 adjoint_matrix
354 }
355
356 fn random() -> Self {
357 use rand::Rng;
358 let mut rng = rand::rng();
359
360 let translation = Vector3::new(
361 rng.random_range(-1.0..1.0),
362 rng.random_range(-1.0..1.0),
363 rng.random_range(-1.0..1.0),
364 );
365 let rotation = SO3::random();
366
367 SE3::from_parts(translation, &rotation)
368 }
369
370 fn jacobian_identity() -> Self::JacobianMatrix {
371 Matrix6::<f64>::identity()
372 }
373
374 fn zero_jacobian() -> Self::JacobianMatrix {
375 Matrix6::<f64>::zeros()
376 }
377
378 fn normalize(&mut self) {
379 let mut rot = self.rotation_impl();
380 rot.normalize();
381 let q = rot.params();
382 self.params[3] = q[0];
383 self.params[4] = q[1];
384 self.params[5] = q[2];
385 self.params[6] = q[3];
386 }
387
388 fn is_valid(&self, tolerance: f64) -> bool {
389 self.rotation_impl().is_valid(tolerance)
390 }
391
392 fn as_param_slice(&self) -> &[f64] {
393 self.params.as_slice()
394 }
395
396 fn as_param_slice_mut(&mut self) -> &mut [f64] {
397 self.params.as_mut_slice()
398 }
399
400 fn from_param_slice(s: &[f64]) -> Self {
401 debug_assert_eq!(s.len(), 7);
402 SE3 {
403 params: SVector::from_column_slice(s),
404 }
405 }
406
407 fn vee(&self) -> Self::TangentVector {
412 self.log(None)
413 }
414
415 fn is_approx(&self, other: &Self, tolerance: f64) -> bool {
421 let difference = self.right_minus(other, None, None);
422 difference.is_zero(tolerance)
423 }
424}
425
426#[derive(Clone, PartialEq)]
432pub struct SE3Tangent {
433 data: Vector6<f64>,
435}
436
437impl fmt::Display for SE3Tangent {
438 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
439 let rho = self.rho();
440 let theta = self.theta();
441 write!(
442 f,
443 "se3(rho: [{:.4}, {:.4}, {:.4}], theta: [{:.4}, {:.4}, {:.4}])",
444 rho.x, rho.y, rho.z, theta.x, theta.y, theta.z
445 )
446 }
447}
448
449impl SE3Tangent {
450 #[inline]
456 pub fn new(rho: Vector3<f64>, theta: Vector3<f64>) -> Self {
457 let mut data = Vector6::zeros();
458 data.fixed_rows_mut::<3>(0).copy_from(&rho);
459 data.fixed_rows_mut::<3>(3).copy_from(&theta);
460 SE3Tangent { data }
461 }
462
463 pub fn from_components(
465 rho_x: f64,
466 rho_y: f64,
467 rho_z: f64,
468 theta_x: f64,
469 theta_y: f64,
470 theta_z: f64,
471 ) -> Self {
472 SE3Tangent {
473 data: Vector6::new(rho_x, rho_y, rho_z, theta_x, theta_y, theta_z),
474 }
475 }
476
477 #[inline]
479 pub fn rho(&self) -> Vector3<f64> {
480 self.data.fixed_rows::<3>(0).into_owned()
481 }
482
483 #[inline]
485 pub fn theta(&self) -> Vector3<f64> {
486 self.data.fixed_rows::<3>(3).into_owned()
487 }
488
489 pub fn q_block_jacobian_matrix(rho: Vector3<f64>, theta: Vector3<f64>) -> Matrix3<f64> {
494 let rho_skew = SO3Tangent::new(rho).hat();
495 let theta_skew = SO3Tangent::new(theta).hat();
496 let theta_squared = theta.norm_squared();
497
498 let a = 0.5;
499 let mut b = 1.0 / 6.0 + 1.0 / 120.0 * theta_squared;
500 let mut c = -1.0 / 24.0 + 1.0 / 720.0 * theta_squared;
501 let mut d = -1.0 / 60.0;
502
503 if theta_squared > crate::SMALL_ANGLE_THRESHOLD {
504 let theta_norm = theta_squared.sqrt();
505 let theta_norm_3 = theta_norm * theta_squared;
506 let theta_norm_4 = theta_squared * theta_squared;
507 let theta_norm_5 = theta_norm_3 * theta_squared;
508 let sin_theta = theta_norm.sin();
509 let cos_theta = theta_norm.cos();
510
511 b = (theta_norm - sin_theta) / theta_norm_3;
512 c = (1.0 - theta_squared / 2.0 - cos_theta) / theta_norm_4;
513 d = (c - 3.0) * (theta_norm - sin_theta - theta_norm_3 / 6.0) / theta_norm_5;
514 }
515
516 let tr = theta_skew * rho_skew;
517 let rt = rho_skew * theta_skew;
518 let trt = tr * theta_skew;
519 let rt_t2 = rt * theta_skew;
520
521 rho_skew * a + (tr + rt + trt) * b
522 - (rt_t2 - rt_t2.transpose() - trt * 3.0) * c
523 - (trt * theta_skew) * d
524 }
525}
526
527impl Tangent<SE3> for SE3Tangent {
529 const DIM: usize = 6;
531
532 fn exp(&self, jacobian: Option<&mut <SE3 as LieGroup>::JacobianMatrix>) -> SE3 {
543 let rho = self.rho();
544 let theta = self.theta();
545
546 let theta_tangent = SO3Tangent::new(theta);
547 let rotation = theta_tangent.exp(None);
549 let translation = theta_tangent.left_jacobian() * rho;
550
551 if let Some(jac) = jacobian {
552 *jac = self.right_jacobian();
553 }
554
555 SE3::from_translation_so3(translation, rotation)
556 }
557
558 fn right_jacobian(&self) -> <SE3 as LieGroup>::JacobianMatrix {
568 let mut jac = Matrix6::zeros();
569 let rho = self.rho();
570 let theta = self.theta();
571 let theta_right_jac = SO3Tangent::new(-theta).right_jacobian();
572 jac.fixed_view_mut::<3, 3>(0, 0).copy_from(&theta_right_jac);
573 jac.fixed_view_mut::<3, 3>(3, 3).copy_from(&theta_right_jac);
574 jac.fixed_view_mut::<3, 3>(0, 3)
575 .copy_from(&SE3Tangent::q_block_jacobian_matrix(-rho, -theta));
576 jac
577 }
578
579 fn left_jacobian(&self) -> <SE3 as LieGroup>::JacobianMatrix {
589 let mut jac = Matrix6::zeros();
590 let theta_left_jac = SO3Tangent::new(self.theta()).left_jacobian();
591 jac.fixed_view_mut::<3, 3>(0, 0).copy_from(&theta_left_jac);
592 jac.fixed_view_mut::<3, 3>(3, 3).copy_from(&theta_left_jac);
593 jac.fixed_view_mut::<3, 3>(0, 3)
594 .copy_from(&SE3Tangent::q_block_jacobian_matrix(
595 self.rho(),
596 self.theta(),
597 ));
598 jac
599 }
600
601 fn right_jacobian_inv(&self) -> <SE3 as LieGroup>::JacobianMatrix {
626 let mut jac = Matrix6::zeros();
627 let rho = self.rho();
628 let theta = self.theta();
629 let theta_left_inv_jac = SO3Tangent::new(theta).left_jacobian_inv();
630 let q_block_jac = SE3Tangent::q_block_jacobian_matrix(-rho, -theta);
631 jac.fixed_view_mut::<3, 3>(0, 0)
632 .copy_from(&theta_left_inv_jac);
633 jac.fixed_view_mut::<3, 3>(3, 3)
634 .copy_from(&theta_left_inv_jac);
635 let top_right = -1.0 * theta_left_inv_jac * q_block_jac * theta_left_inv_jac;
636 jac.fixed_view_mut::<3, 3>(0, 3).copy_from(&top_right);
637 jac
638 }
639
640 fn left_jacobian_inv(&self) -> <SE3 as LieGroup>::JacobianMatrix {
661 let mut jac = Matrix6::zeros();
662 let rho = self.rho();
663 let theta = self.theta();
664 let theta_left_inv_jac = SO3Tangent::new(theta).left_jacobian_inv();
665 let q_block_jac = SE3Tangent::q_block_jacobian_matrix(rho, theta);
666 let top_right_block = -1.0 * theta_left_inv_jac * q_block_jac * theta_left_inv_jac;
667 jac.fixed_view_mut::<3, 3>(0, 0)
668 .copy_from(&theta_left_inv_jac);
669 jac.fixed_view_mut::<3, 3>(3, 3)
670 .copy_from(&theta_left_inv_jac);
671 jac.fixed_view_mut::<3, 3>(0, 3).copy_from(&top_right_block);
672 jac
673 }
674
675 fn hat(&self) -> <SE3 as LieGroup>::LieAlgebra {
688 let mut lie_alg = Matrix4::zeros();
689
690 let theta_hat = SO3Tangent::new(self.theta()).hat();
692 lie_alg.view_mut((0, 0), (3, 3)).copy_from(&theta_hat);
693
694 let rho = self.rho();
696 lie_alg[(0, 3)] = rho[0];
697 lie_alg[(1, 3)] = rho[1];
698 lie_alg[(2, 3)] = rho[2];
699
700 lie_alg
701 }
702
703 fn zero() -> <SE3 as LieGroup>::TangentVector {
712 SE3Tangent::new(Vector3::zeros(), Vector3::zeros())
713 }
714
715 fn random() -> <SE3 as LieGroup>::TangentVector {
723 use rand::Rng;
724 let mut rng = rand::rng();
725 SE3Tangent::from_components(
726 rng.random_range(-1.0..1.0), rng.random_range(-1.0..1.0), rng.random_range(-1.0..1.0), rng.random_range(-0.1..0.1), rng.random_range(-0.1..0.1), rng.random_range(-0.1..0.1), )
733 }
734
735 fn is_zero(&self, tolerance: f64) -> bool {
745 self.data.norm() < tolerance
746 }
747
748 fn normalize(&mut self) {
753 let theta_norm = self.theta().norm();
754 self.data[3] /= theta_norm;
755 self.data[4] /= theta_norm;
756 self.data[5] /= theta_norm;
757 }
758
759 fn normalized(&self) -> <SE3 as LieGroup>::TangentVector {
767 let norm = self.theta().norm();
768 if norm > f64::EPSILON {
769 SE3Tangent::new(self.rho(), self.theta() / norm)
770 } else {
771 SE3Tangent::new(self.rho(), Vector3::zeros())
772 }
773 }
774
775 fn as_slice(&self) -> &[f64] {
776 self.data.as_slice()
777 }
778
779 fn from_slice(s: &[f64]) -> Self {
780 debug_assert_eq!(s.len(), 6);
781 SE3Tangent {
782 data: Vector6::from_column_slice(s),
783 }
784 }
785
786 fn small_adj(&self) -> <SE3 as LieGroup>::JacobianMatrix {
794 let mut small_adj = Matrix6::zeros();
795 let rho_skew = SO3Tangent::new(self.rho()).hat();
796 let theta_skew = SO3Tangent::new(self.theta()).hat();
797
798 small_adj
800 .fixed_view_mut::<3, 3>(0, 0)
801 .copy_from(&theta_skew);
802 small_adj
803 .fixed_view_mut::<3, 3>(3, 3)
804 .copy_from(&theta_skew);
805
806 small_adj.fixed_view_mut::<3, 3>(0, 3).copy_from(&rho_skew);
808
809 small_adj
812 }
813
814 fn lie_bracket(&self, other: &Self) -> <SE3 as LieGroup>::TangentVector {
818 let bracket_result = self.small_adj() * other.data;
819 SE3Tangent {
820 data: bracket_result,
821 }
822 }
823
824 fn is_approx(&self, other: &Self, tolerance: f64) -> bool {
830 (self.data - other.data).norm() < tolerance
831 }
832
833 fn generator(&self, i: usize) -> <SE3 as LieGroup>::LieAlgebra {
841 assert!(i < 6, "SE(3) only has generators for indices 0-5");
842
843 let mut generator = Matrix4::zeros();
844
845 match i {
846 0 => {
847 generator[(0, 3)] = 1.0;
849 }
850 1 => {
851 generator[(1, 3)] = 1.0;
853 }
854 2 => {
855 generator[(2, 3)] = 1.0;
857 }
858 3 => {
859 generator[(1, 2)] = -1.0;
861 generator[(2, 1)] = 1.0;
862 }
863 4 => {
864 generator[(0, 2)] = 1.0;
866 generator[(2, 0)] = -1.0;
867 }
868 5 => {
869 generator[(0, 1)] = -1.0;
871 generator[(1, 0)] = 1.0;
872 }
873 _ => unreachable!(),
874 }
875
876 generator
877 }
878}
879
880#[cfg(test)]
881mod tests {
882 use super::*;
883 use Quaternion;
884 use std::f64::consts::PI;
885
886 const TOLERANCE: f64 = 1e-9;
887
888 #[test]
889 fn test_se3_tangent_basic() {
890 let linear = Vector3::new(1.0, 2.0, 3.0);
891 let angular = Vector3::new(0.1, 0.2, 0.3);
892 let tangent = SE3Tangent::new(linear, angular);
893
894 assert_eq!(tangent.rho(), linear);
895 assert_eq!(tangent.theta(), angular);
896 }
897
898 #[test]
899 fn test_se3_tangent_zero() {
900 let zero = SE3Tangent::zero();
901 assert_eq!(zero.data, Vector6::zeros());
902
903 let tangent = SE3Tangent::from_components(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
904 assert!(tangent.is_zero(1e-10));
905 }
906
907 #[test]
909 fn test_se3_identity() {
910 let identity = SE3::identity();
911 assert!(identity.is_valid(TOLERANCE));
912
913 let translation = identity.translation();
914 let rotation = identity.rotation_quaternion();
915
916 assert!(translation.norm() < TOLERANCE);
917 assert!((rotation.angle()) < TOLERANCE);
918 }
919
920 #[test]
921 fn test_se3_new() {
922 let translation = Vector3::new(1.0, 2.0, 3.0);
923 let rotation = UnitQuaternion::from_euler_angles(0.1, 0.2, 0.3);
924
925 let se3 = SE3::new(translation, rotation);
926
927 assert!(se3.is_valid(TOLERANCE));
928 assert!((se3.translation() - translation).norm() < TOLERANCE);
929 assert!((se3.rotation_quaternion().angle() - rotation.angle()).abs() < TOLERANCE);
930 }
931
932 #[test]
933 fn test_se3_random() {
934 let se3 = SE3::random();
935 assert!(se3.is_valid(TOLERANCE));
936 }
937
938 #[test]
939 fn test_se3_inverse() {
940 let se3 = SE3::random();
941 let se3_inv = se3.inverse(None);
942
943 let composed = se3.compose(&se3_inv, None, None);
945 let identity = SE3::identity();
946
947 let translation_diff = (composed.translation() - identity.translation()).norm();
948 let rotation_diff = composed.rotation_quaternion().angle();
949
950 assert!(translation_diff < TOLERANCE);
951 assert!(rotation_diff < TOLERANCE);
952 }
953
954 #[test]
955 fn test_se3_compose() {
956 let se3_1 = SE3::random();
957 let se3_2 = SE3::random();
958
959 let composed = se3_1.compose(&se3_2, None, None);
960 assert!(composed.is_valid(TOLERANCE));
961
962 let identity = SE3::identity();
964 let composed_with_identity = se3_1.compose(&identity, None, None);
965
966 let translation_diff = (composed_with_identity.translation() - se3_1.translation()).norm();
967 let rotation_diff = (composed_with_identity.rotation_quaternion().angle()
968 - se3_1.rotation_quaternion().angle())
969 .abs();
970
971 assert!(translation_diff < TOLERANCE);
972 assert!(rotation_diff < TOLERANCE);
973 }
974
975 #[test]
976 fn test_se3_adjoint() {
977 let se3 = SE3::random();
978 let adj = se3.adjoint();
979
980 assert_eq!(adj.nrows(), 6);
982 assert_eq!(adj.ncols(), 6);
983
984 let det = adj.determinant();
987 assert!((det - 1.0).abs() < TOLERANCE);
988 }
989
990 #[test]
991 fn test_se3_act() {
992 let se3 = SE3::random();
993 let point = Vector3::new(1.0, 2.0, 3.0);
994
995 let _transformed_point = se3.act(&point, None, None);
996
997 let identity = SE3::identity();
999 let identity_transformed = identity.act(&point, None, None);
1000
1001 let diff = (identity_transformed - point).norm();
1002 assert!(diff < TOLERANCE);
1003 }
1004
1005 #[test]
1006 fn test_se3_between() {
1007 let se3a = SE3::from_translation_euler(1.0, 2.0, 3.0, 0.1, 0.2, 0.3);
1008 let se3b = se3a.clone();
1009 let se3_between_identity = se3a.between(&se3b, None, None);
1010 assert!(se3_between_identity.is_approx(&SE3::identity(), TOLERANCE));
1011
1012 let se3c = SE3::from_translation_euler(4.0, 5.0, 6.0, 0.4, 0.5, 0.6);
1013 let se3_between = se3a.between(&se3c, None, None);
1014 let expected = se3a.inverse(None).compose(&se3c, None, None);
1015 assert!(se3_between.is_approx(&expected, TOLERANCE));
1016 }
1017
1018 #[test]
1019 fn test_se3_exp_log() {
1020 let tangent_vec = Vector6::new(0.1, 0.2, 0.3, 0.01, 0.02, 0.03);
1021 let tangent = SE3Tangent { data: tangent_vec };
1022
1023 let se3 = tangent.exp(None);
1025 let recovered_tangent = se3.log(None);
1026
1027 let diff = (tangent.data - recovered_tangent.data).norm();
1028 assert!(diff < TOLERANCE);
1029 }
1030
1031 #[test]
1032 fn test_se3_exp_zero() {
1033 let zero_tangent = SE3Tangent::zero();
1034 let se3 = zero_tangent.exp(None);
1035 let identity = SE3::identity();
1036
1037 let translation_diff = (se3.translation() - identity.translation()).norm();
1038 let rotation_diff = se3.rotation_quaternion().angle();
1039
1040 assert!(translation_diff < TOLERANCE);
1041 assert!(rotation_diff < TOLERANCE);
1042 }
1043
1044 #[test]
1045 fn test_se3_log_identity() {
1046 let identity = SE3::identity();
1047 let tangent = identity.log(None);
1048
1049 assert!(tangent.data.norm() < TOLERANCE);
1050 }
1051
1052 #[test]
1053 fn test_se3_normalize() {
1054 let translation = Vector3::new(1.0, 2.0, 3.0);
1055 let rotation =
1056 UnitQuaternion::from_quaternion(Quaternion::new(0.5, 0.5, 0.5, 0.5).normalize()); let mut se3 = SE3::new(translation, rotation);
1059 se3.normalize();
1060
1061 assert!(se3.is_valid(TOLERANCE));
1062 }
1063
1064 #[test]
1065 fn test_se3_manifold_properties() {
1066 assert_eq!(SE3::DIM, 3);
1068 assert_eq!(SE3::DOF, 6);
1069 assert_eq!(SE3::REP_SIZE, 7);
1070 }
1071
1072 #[test]
1073 fn test_se3_consistency() {
1074 let se3_1 = SE3::random();
1076 let se3_2 = SE3::random();
1077
1078 let se3_3 = SE3::random();
1080 let left_assoc = se3_1
1081 .compose(&se3_2, None, None)
1082 .compose(&se3_3, None, None);
1083 let right_assoc = se3_1.compose(&se3_2.compose(&se3_3, None, None), None, None);
1084
1085 let translation_diff = (left_assoc.translation() - right_assoc.translation()).norm();
1086 let rotation_diff = (left_assoc.rotation_quaternion().angle()
1087 - right_assoc.rotation_quaternion().angle())
1088 .abs();
1089
1090 assert!(translation_diff < 1e-10);
1091 assert!(rotation_diff < 1e-10);
1092 }
1093
1094 #[test]
1095 fn test_se3_specific_values() {
1096 let translation_only = SE3::new(Vector3::new(1.0, 2.0, 3.0), UnitQuaternion::identity());
1100
1101 let point = Vector3::new(0.0, 0.0, 0.0);
1102 let transformed = translation_only.act(&point, None, None);
1103 let expected = Vector3::new(1.0, 2.0, 3.0);
1104
1105 assert!((transformed - expected).norm() < TOLERANCE);
1106
1107 let rotation_only = SE3::new(
1109 Vector3::zeros(),
1110 UnitQuaternion::from_euler_angles(PI / 2.0, 0.0, 0.0),
1111 );
1112
1113 let point_y = Vector3::new(0.0, 1.0, 0.0);
1114 let rotated = rotation_only.act(&point_y, None, None);
1115 let expected_rotated = Vector3::new(0.0, 0.0, 1.0);
1116
1117 assert!((rotated - expected_rotated).norm() < TOLERANCE);
1118 }
1119
1120 #[test]
1121 fn test_se3_small_angle_approximations() {
1122 let small_tangent = Vector6::new(1e-8, 2e-8, 3e-8, 1e-9, 2e-9, 3e-9);
1124
1125 let se3 = SE3::new(
1126 Vector3::new(1e-8, 2e-8, 3e-8),
1127 UnitQuaternion::from_euler_angles(1e-9, 2e-9, 3e-9),
1128 );
1129 let recovered = se3.log(None);
1130
1131 let diff = (small_tangent - recovered.data).norm();
1132 assert!(diff < TOLERANCE);
1133 }
1134
1135 #[test]
1136 fn test_se3_tangent_norm() {
1137 let tangent_vec = Vector6::new(3.0, 4.0, 0.0, 0.0, 0.0, 0.0);
1138 let tangent = SE3Tangent { data: tangent_vec };
1139
1140 let norm = tangent.data.norm();
1141 assert!((norm - 5.0).abs() < TOLERANCE); }
1143
1144 #[test]
1145 fn test_se3_from_components() {
1146 let translation = Vector3::new(1.0, 2.0, 3.0);
1147 let quaternion = Quaternion::new(1.0, 0.0, 0.0, 0.0);
1148 let se3 = SE3::from_translation_quaternion(translation, quaternion);
1149 assert!(se3.is_valid(TOLERANCE));
1150 assert_eq!(se3.x(), 1.0);
1151 assert_eq!(se3.y(), 2.0);
1152 assert_eq!(se3.z(), 3.0);
1153
1154 let quat = se3.rotation_quaternion();
1155 assert!((quat.w - 1.0).abs() < TOLERANCE);
1156 assert!(quat.i.abs() < TOLERANCE);
1157 assert!(quat.j.abs() < TOLERANCE);
1158 assert!(quat.k.abs() < TOLERANCE);
1159 }
1160
1161 #[test]
1162 fn test_se3_from_isometry() {
1163 let translation = Translation3::new(1.0, 2.0, 3.0);
1164 let rotation = UnitQuaternion::from_euler_angles(0.1, 0.2, 0.3);
1165 let isometry = Isometry3::from_parts(translation, rotation);
1166
1167 let se3 = SE3::from_isometry(isometry);
1168 let recovered_isometry = se3.isometry();
1169
1170 let translation_diff =
1171 (isometry.translation.vector - recovered_isometry.translation.vector).norm();
1172 let rotation_diff = (isometry.rotation.angle() - recovered_isometry.rotation.angle()).abs();
1173
1174 assert!(translation_diff < TOLERANCE);
1175 assert!(rotation_diff < TOLERANCE);
1176 }
1177
1178 #[test]
1179 fn test_se3_matrix() {
1180 let se3 = SE3::random();
1181 let matrix = se3.matrix();
1182
1183 assert_eq!(matrix.nrows(), 4);
1185 assert_eq!(matrix.ncols(), 4);
1186
1187 assert!((matrix[(3, 0)]).abs() < TOLERANCE);
1189 assert!((matrix[(3, 1)]).abs() < TOLERANCE);
1190 assert!((matrix[(3, 2)]).abs() < TOLERANCE);
1191 assert!((matrix[(3, 3)] - 1.0).abs() < TOLERANCE);
1192 }
1193
1194 #[test]
1196 fn test_se3_manif_like_operations() {
1197 let g1 = SE3::new(
1201 Vector3::new(1.0, 0.0, 0.0),
1202 UnitQuaternion::from_euler_angles(0.0, 0.0, PI / 4.0),
1203 );
1204
1205 let g2 = SE3::new(
1206 Vector3::new(0.0, 1.0, 0.0),
1207 UnitQuaternion::from_euler_angles(0.0, PI / 4.0, 0.0),
1208 );
1209
1210 let g3 = g1.compose(&g2, None, None);
1212 assert!(g3.is_valid(TOLERANCE));
1213
1214 let g2_inv = g2.inverse(None);
1216 let g1_inv = g1.inverse(None);
1217 let result = g1
1218 .compose(&g2, None, None)
1219 .compose(&g2_inv, None, None)
1220 .compose(&g1_inv, None, None);
1221
1222 let identity = SE3::identity();
1223 let translation_diff = (result.translation() - identity.translation()).norm();
1224 let rotation_diff = result.rotation_quaternion().angle();
1225
1226 assert!(translation_diff < TOLERANCE);
1227 assert!(rotation_diff < TOLERANCE);
1228 }
1229
1230 #[test]
1231 fn test_se3_tangent_exp_jacobians() {
1232 let tangent = SE3Tangent::new(Vector3::new(0.1, 0.0, 0.0), Vector3::new(0.0, 0.1, 0.0));
1233
1234 let se3_element = tangent.exp(None);
1236 assert!(se3_element.is_valid(TOLERANCE));
1237
1238 let another_tangent = SE3Tangent::new(
1240 Vector3::new(0.01, 0.02, 0.03),
1241 Vector3::new(0.001, 0.002, 0.003),
1242 );
1243 let another_se3 = another_tangent.exp(None);
1244 assert!(another_se3.is_valid(TOLERANCE));
1245
1246 let _right_jac = tangent.right_jacobian();
1248 let _left_jac = tangent.left_jacobian();
1249 let _right_jac_inv = tangent.right_jacobian_inv();
1250 let _left_jac_inv = tangent.left_jacobian_inv();
1251
1252 assert_eq!(_right_jac.nrows(), 6);
1254 assert_eq!(_right_jac.ncols(), 6);
1255 assert_eq!(_left_jac.nrows(), 6);
1256 assert_eq!(_left_jac.ncols(), 6);
1257 assert_eq!(_right_jac_inv.nrows(), 6);
1258 assert_eq!(_right_jac_inv.ncols(), 6);
1259 assert_eq!(_left_jac_inv.nrows(), 6);
1260 assert_eq!(_left_jac_inv.ncols(), 6);
1261 }
1262
1263 #[test]
1264 fn test_se3_tangent_utility_functions() {
1265 let zero_vec = SE3Tangent::zero();
1267 assert!(zero_vec.data.norm() < TOLERANCE);
1268
1269 let random_vec = SE3Tangent::random();
1271 assert!(random_vec.data.norm() > 0.0);
1272
1273 let tangent = SE3Tangent::new(Vector3::zeros(), Vector3::zeros());
1275 assert!(tangent.is_zero(1e-10));
1276
1277 let non_zero_tangent = SE3Tangent::new(Vector3::new(1e-5, 0.0, 0.0), Vector3::zeros());
1278 assert!(!non_zero_tangent.is_zero(1e-10));
1279 }
1280
1281 #[test]
1284 fn test_se3_vee() {
1285 let se3 = SE3::random();
1286 let tangent_log = se3.log(None);
1287 let tangent_vee = se3.vee();
1288
1289 assert!((tangent_log.data - tangent_vee.data).norm() < 1e-10);
1290 }
1291
1292 #[test]
1293 fn test_se3_is_approx() {
1294 let se3_1 = SE3::random();
1295 let se3_2 = se3_1.clone();
1296
1297 assert!(se3_1.is_approx(&se3_1, 1e-10));
1298 assert!(se3_1.is_approx(&se3_2, 1e-10));
1299
1300 let small_tangent = SE3Tangent::new(
1302 Vector3::new(1e-12, 1e-12, 1e-12),
1303 Vector3::new(1e-12, 1e-12, 1e-12),
1304 );
1305 let se3_perturbed = se3_1.right_plus(&small_tangent, None, None);
1306 assert!(se3_1.is_approx(&se3_perturbed, 1e-10));
1307 }
1308
1309 #[test]
1310 fn test_se3_tangent_small_adj() {
1311 let tangent = SE3Tangent::new(Vector3::new(0.1, 0.2, 0.3), Vector3::new(0.4, 0.5, 0.6));
1312 let small_adj = tangent.small_adj();
1313
1314 let rho_skew = SO3Tangent::new(tangent.rho()).hat();
1319 let theta_skew = SO3Tangent::new(tangent.theta()).hat();
1320
1321 let top_left = small_adj.fixed_view::<3, 3>(0, 0);
1323 let bottom_right = small_adj.fixed_view::<3, 3>(3, 3);
1324 assert!((top_left - theta_skew).norm() < 1e-10);
1325 assert!((bottom_right - theta_skew).norm() < 1e-10);
1326
1327 let top_right = small_adj.fixed_view::<3, 3>(0, 3);
1329 assert!((top_right - rho_skew).norm() < 1e-10);
1330
1331 let bottom_left = small_adj.fixed_view::<3, 3>(3, 0);
1333 assert!(bottom_left.norm() < 1e-10);
1334 }
1335
1336 #[test]
1337 fn test_se3_tangent_lie_bracket() {
1338 let tangent_a = SE3Tangent::new(Vector3::new(0.1, 0.0, 0.0), Vector3::new(0.0, 0.2, 0.0));
1339 let tangent_b = SE3Tangent::new(Vector3::new(0.0, 0.3, 0.0), Vector3::new(0.0, 0.0, 0.4));
1340
1341 let bracket_ab = tangent_a.lie_bracket(&tangent_b);
1342 let bracket_ba = tangent_b.lie_bracket(&tangent_a);
1343
1344 assert!((bracket_ab.data + bracket_ba.data).norm() < 1e-10);
1346
1347 let bracket_aa = tangent_a.lie_bracket(&tangent_a);
1349 assert!(bracket_aa.is_zero(1e-10));
1350
1351 let bracket_hat = bracket_ab.hat();
1353 let expected = tangent_a.hat() * tangent_b.hat() - tangent_b.hat() * tangent_a.hat();
1354 assert!((bracket_hat - expected).norm() < 1e-10);
1355 }
1356
1357 #[test]
1358 fn test_se3_tangent_is_approx() {
1359 let tangent_1 = SE3Tangent::new(Vector3::new(0.1, 0.2, 0.3), Vector3::new(0.4, 0.5, 0.6));
1360 let tangent_2 = SE3Tangent::new(
1361 Vector3::new(0.1 + 1e-12, 0.2, 0.3),
1362 Vector3::new(0.4, 0.5, 0.6),
1363 );
1364 let tangent_3 = SE3Tangent::new(Vector3::new(0.7, 0.8, 0.9), Vector3::new(1.0, 1.1, 1.2));
1365
1366 assert!(tangent_1.is_approx(&tangent_1, 1e-10));
1367 assert!(tangent_1.is_approx(&tangent_2, 1e-10));
1368 assert!(!tangent_1.is_approx(&tangent_3, 1e-10));
1369 }
1370
1371 #[test]
1372 fn test_se3_generators() {
1373 let tangent = SE3Tangent::new(Vector3::new(1.0, 1.0, 1.0), Vector3::new(1.0, 1.0, 1.0));
1374
1375 for i in 0..6 {
1377 let generator = tangent.generator(i);
1378
1379 assert_eq!(generator.nrows(), 4);
1381 assert_eq!(generator.ncols(), 4);
1382
1383 assert_eq!(generator[(3, 0)], 0.0);
1385 assert_eq!(generator[(3, 1)], 0.0);
1386 assert_eq!(generator[(3, 2)], 0.0);
1387 assert_eq!(generator[(3, 3)], 0.0);
1388 }
1389
1390 let e1 = tangent.generator(0); let e2 = tangent.generator(1); let e3 = tangent.generator(2); assert_eq!(e1[(0, 3)], 1.0);
1396 assert_eq!(e2[(1, 3)], 1.0);
1397 assert_eq!(e3[(2, 3)], 1.0);
1398
1399 let e4 = tangent.generator(3); let e5 = tangent.generator(4); let e6 = tangent.generator(5); assert_eq!(e4[(1, 2)], -1.0);
1406 assert_eq!(e4[(2, 1)], 1.0);
1407 assert_eq!(e5[(0, 2)], 1.0);
1408 assert_eq!(e5[(2, 0)], -1.0);
1409 assert_eq!(e6[(0, 1)], -1.0);
1410 assert_eq!(e6[(1, 0)], 1.0);
1411 }
1412
1413 #[test]
1414 #[should_panic]
1415 fn test_se3_generator_invalid_index() {
1416 let tangent = SE3Tangent::new(Vector3::new(1.0, 1.0, 1.0), Vector3::new(1.0, 1.0, 1.0));
1417 let _generator = tangent.generator(6); }
1419
1420 #[test]
1421 fn test_se3_jacobi_identity() {
1422 let x = SE3Tangent::new(Vector3::new(0.1, 0.0, 0.0), Vector3::new(0.0, 0.1, 0.0));
1424 let y = SE3Tangent::new(Vector3::new(0.0, 0.2, 0.0), Vector3::new(0.0, 0.0, 0.2));
1425 let z = SE3Tangent::new(Vector3::new(0.0, 0.0, 0.3), Vector3::new(0.3, 0.0, 0.0));
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 = SE3Tangent {
1432 data: term1.data + term2.data + term3.data,
1433 };
1434 assert!(jacobi_sum.is_zero(1e-10));
1435 }
1436
1437 #[test]
1438 fn test_se3_hat_matrix_structure() {
1439 let tangent = SE3Tangent::new(Vector3::new(0.1, 0.2, 0.3), Vector3::new(0.4, 0.5, 0.6));
1440 let hat_matrix = tangent.hat();
1441
1442 assert_eq!(hat_matrix[(0, 3)], tangent.rho()[0]);
1445 assert_eq!(hat_matrix[(1, 3)], tangent.rho()[1]);
1446 assert_eq!(hat_matrix[(2, 3)], tangent.rho()[2]);
1447
1448 let theta_hat = SO3Tangent::new(tangent.theta()).hat();
1450 let top_left = hat_matrix.fixed_view::<3, 3>(0, 0);
1451 assert!((top_left - theta_hat).norm() < 1e-10);
1452
1453 assert_eq!(hat_matrix[(3, 0)], 0.0);
1455 assert_eq!(hat_matrix[(3, 1)], 0.0);
1456 assert_eq!(hat_matrix[(3, 2)], 0.0);
1457 assert_eq!(hat_matrix[(3, 3)], 0.0);
1458 }
1459
1460 #[test]
1476 fn test_se3_accumulated_error_odometry() {
1477 let step = SE3::new(
1479 Vector3::new(1.0, 0.0, 0.0), UnitQuaternion::from_euler_angles(0.0, 0.0, 0.1), );
1482
1483 let mut pose = SE3::identity();
1484 for _ in 0..10 {
1485 pose = pose.compose(&step, None, None);
1486 }
1487
1488 let expected = SE3::new(
1490 Vector3::new(10.0, 0.0, 0.0),
1491 UnitQuaternion::from_euler_angles(0.0, 0.0, 1.0),
1492 );
1493
1494 assert!(pose.is_approx(&expected, 5.0));
1497 }
1498
1499 #[test]
1513 fn test_se3_large_translation_small_rotation() {
1514 let large_t = Vector3::new(1000.0, 2000.0, 500.0);
1516 let small_r = SO3::from_scaled_axis(Vector3::new(1e-6, 2e-6, 3e-6));
1517 let se3 = SE3::from_translation_so3(large_t, small_r);
1518
1519 let tangent = se3.log(None);
1520 let recovered = tangent.exp(None);
1521
1522 assert!(se3.is_approx(&recovered, 1e-3));
1524 }
1525
1526 #[test]
1527 fn test_se3_small_translation_large_rotation() {
1528 let small_t = Vector3::new(0.001, 0.002, -0.001);
1530 let large_r = SO3::from_euler_angles(1.5, 0.5, -1.2);
1532 let se3 = SE3::from_translation_so3(small_t, large_r);
1533
1534 let tangent = se3.log(None);
1535 let recovered = tangent.exp(None);
1536
1537 assert!(se3.is_approx(&recovered, 1e-3));
1539 }
1540
1541 #[test]
1542 fn test_se3_right_jacobian_inverse_identity() {
1543 let tangent = SE3Tangent::new(
1544 Vector3::new(0.1, 0.15, 0.2),
1545 Vector3::new(0.001, 0.002, 0.003),
1546 );
1547 let jr = tangent.right_jacobian();
1548 let jr_inv = tangent.right_jacobian_inv();
1549 let product = jr * jr_inv;
1550 let identity = Matrix6::identity();
1551
1552 assert!(
1553 (product - identity).norm() < 1e-10,
1554 "Jr * Jr_inv should be identity, got error: {}",
1555 (product - identity).norm()
1556 );
1557 }
1558
1559 #[test]
1560 fn test_se3_left_jacobian_inverse_identity() {
1561 let tangent = SE3Tangent::new(
1562 Vector3::new(0.1, 0.15, 0.2),
1563 Vector3::new(0.001, 0.002, 0.003),
1564 );
1565 let jl = tangent.left_jacobian();
1566 let jl_inv = tangent.left_jacobian_inv();
1567 let product = jl * jl_inv;
1568
1569 assert!(
1570 (product - Matrix6::identity()).norm() < 1e-10,
1571 "Jl * Jl_inv should be identity, got error: {}",
1572 (product - Matrix6::identity()).norm()
1573 );
1574 }
1575
1576 #[test]
1577 fn se3_param_slice_round_trip() {
1578 let g = SE3::random();
1579 let recovered = SE3::from_param_slice(g.as_param_slice());
1580 assert!(g.is_approx(&recovered, 1e-14));
1581 }
1582
1583 #[test]
1584 fn se3_param_slice_mut_modifies_in_place() {
1585 let mut g = SE3::identity();
1586 g.as_param_slice_mut()[0] = 1.0;
1587 assert_eq!(g.translation().x, 1.0);
1588 }
1589
1590 #[test]
1591 fn se3_tangent_slice_round_trip() {
1592 let t = SE3Tangent::random();
1593 let recovered = SE3Tangent::from_slice(t.as_slice());
1594 assert!(t.is_approx(&recovered, 1e-14));
1595 }
1596}