1use crate::{
31 LieGroup, Tangent,
32 so3::{SO3, SO3Tangent},
33};
34use nalgebra::{
35 DVector, Isometry3, Matrix3, Matrix4, Matrix6, Quaternion, Translation3, UnitQuaternion,
36 Vector3, Vector6,
37};
38use std::{
39 fmt,
40 fmt::{Display, Formatter},
41};
42
43#[derive(Clone, PartialEq)]
47pub struct SE3 {
48 rotation: SO3,
50 translation: Vector3<f64>,
52}
53
54impl Display for SE3 {
55 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
56 let t = self.translation();
57 let q = self.rotation_quaternion();
58 write!(
59 f,
60 "SE3(translation: [{:.4}, {:.4}, {:.4}], rotation: [w: {:.4}, x: {:.4}, y: {:.4}, z: {:.4}])",
61 t.x, t.y, t.z, q.w, q.i, q.j, q.k
62 )
63 }
64}
65
66impl SE3 {
67 pub const DIM: usize = 3;
69
70 pub const DOF: usize = 6;
72
73 pub const REP_SIZE: usize = 7;
75
76 pub fn identity() -> Self {
80 SE3 {
81 rotation: SO3::identity(),
82 translation: Vector3::zeros(),
83 }
84 }
85
86 pub fn jacobian_identity() -> Matrix6<f64> {
90 Matrix6::<f64>::identity()
91 }
92
93 #[inline]
99 pub fn new(translation: Vector3<f64>, rotation: UnitQuaternion<f64>) -> Self {
100 SE3 {
101 rotation: SO3::new(rotation),
102 translation,
103 }
104 }
105
106 pub fn from_translation_quaternion(
108 translation: Vector3<f64>,
109 quaternion: Quaternion<f64>,
110 ) -> Self {
111 let quaternion = UnitQuaternion::from_quaternion(quaternion.normalize());
112 Self::new(translation, quaternion)
113 }
114
115 pub fn from_translation_euler(x: f64, y: f64, z: f64, roll: f64, pitch: f64, yaw: f64) -> Self {
117 let translation = Vector3::new(x, y, z);
118 let rotation = UnitQuaternion::from_euler_angles(roll, pitch, yaw);
119 Self::new(translation, rotation)
120 }
121
122 pub fn from_isometry(isometry: Isometry3<f64>) -> Self {
124 SE3 {
125 rotation: SO3::new(isometry.rotation),
126 translation: isometry.translation.vector,
127 }
128 }
129
130 pub fn from_translation_so3(translation: Vector3<f64>, rotation: SO3) -> Self {
132 SE3 {
133 rotation,
134 translation,
135 }
136 }
137
138 pub fn translation(&self) -> Vector3<f64> {
140 self.translation
141 }
142
143 pub fn rotation_so3(&self) -> SO3 {
145 self.rotation.clone()
146 }
147
148 pub fn rotation_quaternion(&self) -> UnitQuaternion<f64> {
150 self.rotation.quaternion()
151 }
152
153 pub fn isometry(&self) -> Isometry3<f64> {
155 Isometry3::from_parts(
156 Translation3::from(self.translation),
157 self.rotation_quaternion(),
158 )
159 }
160
161 pub fn matrix(&self) -> Matrix4<f64> {
163 self.isometry().to_homogeneous()
164 }
165
166 #[inline]
168 pub fn x(&self) -> f64 {
169 self.translation.x
170 }
171
172 #[inline]
174 pub fn y(&self) -> f64 {
175 self.translation.y
176 }
177
178 #[inline]
180 pub fn z(&self) -> f64 {
181 self.translation.z
182 }
183
184 pub fn coeffs(&self) -> [f64; 7] {
186 let q = self.rotation.quaternion();
187 [
188 self.translation.x,
189 self.translation.y,
190 self.translation.z,
191 q.w,
192 q.i,
193 q.j,
194 q.k,
195 ]
196 }
197}
198
199impl From<DVector<f64>> for SE3 {
201 fn from(data: DVector<f64>) -> Self {
202 let translation = Vector3::new(data[0], data[1], data[2]);
203 let quaternion = Quaternion::new(data[3], data[4], data[5], data[6]);
204 SE3::from_translation_quaternion(translation, quaternion)
205 }
206}
207
208impl From<SE3> for DVector<f64> {
209 fn from(se3: SE3) -> Self {
210 let q = se3.rotation.quaternion();
211 DVector::from_vec(vec![
212 se3.translation.x,
213 se3.translation.y,
214 se3.translation.z,
215 q.w,
216 q.i,
217 q.j,
218 q.k,
219 ])
220 }
221}
222
223impl LieGroup for SE3 {
225 type TangentVector = SE3Tangent;
226 type JacobianMatrix = Matrix6<f64>;
227 type LieAlgebra = Matrix4<f64>;
228
229 fn inverse(&self, jacobian: Option<&mut Self::JacobianMatrix>) -> Self {
243 let rot_inv = self.rotation.inverse(None);
244 let trans_inv = -rot_inv.act(&self.translation, None, None);
245
246 if let Some(jac) = jacobian {
247 *jac = -self.adjoint();
248 }
249
250 SE3::from_translation_so3(trans_inv, rot_inv)
251 }
252
253 fn compose(
273 &self,
274 other: &Self,
275 jacobian_self: Option<&mut Self::JacobianMatrix>,
276 jacobian_other: Option<&mut Self::JacobianMatrix>,
277 ) -> Self {
278 let composed_rotation = self.rotation.compose(&other.rotation, None, None);
279 let composed_translation =
280 self.rotation.act(&other.translation, None, None) + self.translation;
281
282 let result = SE3::from_translation_so3(composed_translation, composed_rotation);
283
284 if let Some(jac_self) = jacobian_self {
285 *jac_self = other.inverse(None).adjoint();
286 }
287
288 if let Some(jac_other) = jacobian_other {
289 *jac_other = Matrix6::identity();
290 }
291
292 result
293 }
294
295 fn log(&self, jacobian: Option<&mut Self::JacobianMatrix>) -> Self::TangentVector {
309 let theta = self.rotation.log(None);
310 let mut data = Vector6::zeros();
311 let translation_vector = theta.left_jacobian_inv() * self.translation;
312 data.fixed_rows_mut::<3>(0).copy_from(&translation_vector);
313 data.fixed_rows_mut::<3>(3).copy_from(&theta.coeffs());
314 let result = SE3Tangent { data };
315 if let Some(jac) = jacobian {
316 *jac = result.right_jacobian_inv();
317 }
318
319 result
320 }
321
322 fn act(
323 &self,
324 vector: &Vector3<f64>,
325 jacobian_self: Option<&mut Self::JacobianMatrix>,
326 jacobian_vector: Option<&mut Matrix3<f64>>,
327 ) -> Vector3<f64> {
328 let result = self.rotation.act(vector, None, None) + self.translation;
329
330 if let Some(jac_self) = jacobian_self {
331 jac_self
332 .fixed_view_mut::<3, 3>(0, 0)
333 .copy_from(&self.rotation.rotation_matrix());
334 jac_self
335 .fixed_view_mut::<3, 3>(0, 3)
336 .copy_from(&(-self.rotation.rotation_matrix() * SO3Tangent::new(*vector).hat()));
337 }
338
339 if let Some(jac_vector) = jacobian_vector {
340 let rotation_matrix = self.rotation.rotation_matrix();
341 jac_vector.copy_from(&rotation_matrix);
342 }
343
344 result
345 }
346
347 fn adjoint(&self) -> Self::JacobianMatrix {
348 let rotation_matrix = self.rotation.rotation_matrix();
349 let translation = self.translation;
350 let mut adjoint_matrix = Matrix6::zeros();
351
352 adjoint_matrix
354 .fixed_view_mut::<3, 3>(0, 0)
355 .copy_from(&rotation_matrix);
356
357 adjoint_matrix
359 .fixed_view_mut::<3, 3>(3, 3)
360 .copy_from(&rotation_matrix);
361
362 let top_right = SO3Tangent::new(translation).hat() * rotation_matrix;
364 adjoint_matrix
365 .fixed_view_mut::<3, 3>(0, 3)
366 .copy_from(&top_right);
367
368 adjoint_matrix
369 }
370
371 fn random() -> Self {
372 use rand::Rng;
373 let mut rng = rand::rng();
374
375 let translation = Vector3::new(
377 rng.random_range(-1.0..1.0),
378 rng.random_range(-1.0..1.0),
379 rng.random_range(-1.0..1.0),
380 );
381
382 let rotation = SO3::random();
384
385 SE3::from_translation_so3(translation, rotation)
386 }
387
388 fn jacobian_identity() -> Self::JacobianMatrix {
389 Matrix6::<f64>::identity()
390 }
391
392 fn zero_jacobian() -> Self::JacobianMatrix {
393 Matrix6::<f64>::zeros()
394 }
395
396 fn normalize(&mut self) {
397 self.rotation.normalize();
399 }
400
401 fn is_valid(&self, tolerance: f64) -> bool {
402 self.rotation.is_valid(tolerance)
404 }
405
406 fn vee(&self) -> Self::TangentVector {
411 self.log(None)
412 }
413
414 fn is_approx(&self, other: &Self, tolerance: f64) -> bool {
420 let difference = self.right_minus(other, None, None);
421 difference.is_zero(tolerance)
422 }
423}
424
425#[derive(Clone, PartialEq)]
431pub struct SE3Tangent {
432 data: Vector6<f64>,
434}
435
436impl fmt::Display for SE3Tangent {
437 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
438 let rho = self.rho();
439 let theta = self.theta();
440 write!(
441 f,
442 "se3(rho: [{:.4}, {:.4}, {:.4}], theta: [{:.4}, {:.4}, {:.4}])",
443 rho.x, rho.y, rho.z, theta.x, theta.y, theta.z
444 )
445 }
446}
447
448impl From<DVector<f64>> for SE3Tangent {
449 fn from(data_vector: DVector<f64>) -> Self {
450 SE3Tangent {
451 data: Vector6::new(
452 data_vector[0],
453 data_vector[1],
454 data_vector[2],
455 data_vector[3],
456 data_vector[4],
457 data_vector[5],
458 ),
459 }
460 }
461}
462
463impl From<SE3Tangent> for DVector<f64> {
464 fn from(se3_tangent: SE3Tangent) -> Self {
465 DVector::from_vec(vec![
466 se3_tangent.data[0],
467 se3_tangent.data[1],
468 se3_tangent.data[2],
469 se3_tangent.data[3],
470 se3_tangent.data[4],
471 se3_tangent.data[5],
472 ])
473 }
474}
475
476impl SE3Tangent {
477 #[inline]
483 pub fn new(rho: Vector3<f64>, theta: Vector3<f64>) -> Self {
484 let mut data = Vector6::zeros();
485 data.fixed_rows_mut::<3>(0).copy_from(&rho);
486 data.fixed_rows_mut::<3>(3).copy_from(&theta);
487 SE3Tangent { data }
488 }
489
490 pub fn from_components(
492 rho_x: f64,
493 rho_y: f64,
494 rho_z: f64,
495 theta_x: f64,
496 theta_y: f64,
497 theta_z: f64,
498 ) -> Self {
499 SE3Tangent {
500 data: Vector6::new(rho_x, rho_y, rho_z, theta_x, theta_y, theta_z),
501 }
502 }
503
504 #[inline]
506 pub fn rho(&self) -> Vector3<f64> {
507 self.data.fixed_rows::<3>(0).into_owned()
508 }
509
510 #[inline]
512 pub fn theta(&self) -> Vector3<f64> {
513 self.data.fixed_rows::<3>(3).into_owned()
514 }
515
516 pub fn q_block_jacobian_matrix(rho: Vector3<f64>, theta: Vector3<f64>) -> Matrix3<f64> {
521 let rho_skew = SO3Tangent::new(rho).hat();
522 let theta_skew = SO3Tangent::new(theta).hat();
523 let theta_squared = theta.norm_squared();
524
525 let a = 0.5;
526 let mut b = 1.0 / 6.0 + 1.0 / 120.0 * theta_squared;
527 let mut c = -1.0 / 24.0 + 1.0 / 720.0 * theta_squared;
528 let mut d = -1.0 / 60.0;
529
530 if theta_squared > crate::SMALL_ANGLE_THRESHOLD {
531 let theta_norm = theta_squared.sqrt();
532 let theta_norm_3 = theta_norm * theta_squared;
533 let theta_norm_4 = theta_squared * theta_squared;
534 let theta_norm_5 = theta_norm_3 * theta_squared;
535 let sin_theta = theta_norm.sin();
536 let cos_theta = theta_norm.cos();
537
538 b = (theta_norm - sin_theta) / theta_norm_3;
539 c = (1.0 - theta_squared / 2.0 - cos_theta) / theta_norm_4;
540 d = (c - 3.0) * (theta_norm - sin_theta - theta_norm_3 / 6.0) / theta_norm_5;
541 }
542
543 let tr = theta_skew * rho_skew;
544 let rt = rho_skew * theta_skew;
545 let trt = tr * theta_skew;
546 let rt_t2 = rt * theta_skew;
547
548 rho_skew * a + (tr + rt + trt) * b
549 - (rt_t2 - rt_t2.transpose() - trt * 3.0) * c
550 - (trt * theta_skew) * d
551 }
552}
553
554impl Tangent<SE3> for SE3Tangent {
556 const DIM: usize = 6;
558
559 fn exp(&self, jacobian: Option<&mut <SE3 as LieGroup>::JacobianMatrix>) -> SE3 {
570 let rho = self.rho();
571 let theta = self.theta();
572
573 let theta_tangent = SO3Tangent::new(theta);
574 let rotation = theta_tangent.exp(None);
576 let translation = theta_tangent.left_jacobian() * rho;
577
578 if let Some(jac) = jacobian {
579 *jac = self.right_jacobian();
580 }
581
582 SE3::from_translation_so3(translation, rotation)
583 }
584
585 fn right_jacobian(&self) -> <SE3 as LieGroup>::JacobianMatrix {
595 let mut jac = Matrix6::zeros();
596 let rho = self.rho();
597 let theta = self.theta();
598 let theta_right_jac = SO3Tangent::new(-theta).right_jacobian();
599 jac.fixed_view_mut::<3, 3>(0, 0).copy_from(&theta_right_jac);
600 jac.fixed_view_mut::<3, 3>(3, 3).copy_from(&theta_right_jac);
601 jac.fixed_view_mut::<3, 3>(0, 3)
602 .copy_from(&SE3Tangent::q_block_jacobian_matrix(-rho, -theta));
603 jac
604 }
605
606 fn left_jacobian(&self) -> <SE3 as LieGroup>::JacobianMatrix {
616 let mut jac = Matrix6::zeros();
617 let theta_left_jac = SO3Tangent::new(self.theta()).left_jacobian();
618 jac.fixed_view_mut::<3, 3>(0, 0).copy_from(&theta_left_jac);
619 jac.fixed_view_mut::<3, 3>(3, 3).copy_from(&theta_left_jac);
620 jac.fixed_view_mut::<3, 3>(0, 3)
621 .copy_from(&SE3Tangent::q_block_jacobian_matrix(
622 self.rho(),
623 self.theta(),
624 ));
625 jac
626 }
627
628 fn right_jacobian_inv(&self) -> <SE3 as LieGroup>::JacobianMatrix {
653 let mut jac = Matrix6::zeros();
654 let rho = self.rho();
655 let theta = self.theta();
656 let theta_left_inv_jac = SO3Tangent::new(-theta).left_jacobian_inv();
657 let q_block_jac = SE3Tangent::q_block_jacobian_matrix(-rho, -theta);
658 jac.fixed_view_mut::<3, 3>(0, 0)
659 .copy_from(&theta_left_inv_jac);
660 jac.fixed_view_mut::<3, 3>(3, 3)
661 .copy_from(&theta_left_inv_jac);
662 let top_right = -1.0 * theta_left_inv_jac * q_block_jac * theta_left_inv_jac;
663 jac.fixed_view_mut::<3, 3>(0, 3).copy_from(&top_right);
664 jac
665 }
666
667 fn left_jacobian_inv(&self) -> <SE3 as LieGroup>::JacobianMatrix {
688 let mut jac = Matrix6::zeros();
689 let rho = self.rho();
690 let theta = self.theta();
691 let theta_left_inv_jac = SO3Tangent::new(theta).left_jacobian_inv();
692 let q_block_jac = SE3Tangent::q_block_jacobian_matrix(rho, theta);
693 let top_right_block = -1.0 * theta_left_inv_jac * q_block_jac * theta_left_inv_jac;
694 jac.fixed_view_mut::<3, 3>(0, 0)
695 .copy_from(&theta_left_inv_jac);
696 jac.fixed_view_mut::<3, 3>(3, 3)
697 .copy_from(&theta_left_inv_jac);
698 jac.fixed_view_mut::<3, 3>(0, 3).copy_from(&top_right_block);
699 jac
700 }
701
702 fn hat(&self) -> <SE3 as LieGroup>::LieAlgebra {
715 let mut lie_alg = Matrix4::zeros();
716
717 let theta_hat = SO3Tangent::new(self.theta()).hat();
719 lie_alg.view_mut((0, 0), (3, 3)).copy_from(&theta_hat);
720
721 let rho = self.rho();
723 lie_alg[(0, 3)] = rho[0];
724 lie_alg[(1, 3)] = rho[1];
725 lie_alg[(2, 3)] = rho[2];
726
727 lie_alg
728 }
729
730 fn zero() -> <SE3 as LieGroup>::TangentVector {
739 SE3Tangent::new(Vector3::zeros(), Vector3::zeros())
740 }
741
742 fn random() -> <SE3 as LieGroup>::TangentVector {
750 use rand::Rng;
751 let mut rng = rand::rng();
752 SE3Tangent::from_components(
753 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), )
760 }
761
762 fn is_zero(&self, tolerance: f64) -> bool {
772 self.data.norm() < tolerance
773 }
774
775 fn normalize(&mut self) {
780 let theta_norm = self.theta().norm();
781 self.data[3] /= theta_norm;
782 self.data[4] /= theta_norm;
783 self.data[5] /= theta_norm;
784 }
785
786 fn normalized(&self) -> <SE3 as LieGroup>::TangentVector {
794 let norm = self.theta().norm();
795 if norm > f64::EPSILON {
796 SE3Tangent::new(self.rho(), self.theta() / norm)
797 } else {
798 SE3Tangent::new(self.rho(), Vector3::zeros())
799 }
800 }
801
802 fn small_adj(&self) -> <SE3 as LieGroup>::JacobianMatrix {
810 let mut small_adj = Matrix6::zeros();
811 let rho_skew = SO3Tangent::new(self.rho()).hat();
812 let theta_skew = SO3Tangent::new(self.theta()).hat();
813
814 small_adj
816 .fixed_view_mut::<3, 3>(0, 0)
817 .copy_from(&theta_skew);
818 small_adj
819 .fixed_view_mut::<3, 3>(3, 3)
820 .copy_from(&theta_skew);
821
822 small_adj.fixed_view_mut::<3, 3>(0, 3).copy_from(&rho_skew);
824
825 small_adj
828 }
829
830 fn lie_bracket(&self, other: &Self) -> <SE3 as LieGroup>::TangentVector {
834 let bracket_result = self.small_adj() * other.data;
835 SE3Tangent {
836 data: bracket_result,
837 }
838 }
839
840 fn is_approx(&self, other: &Self, tolerance: f64) -> bool {
846 (self.data - other.data).norm() < tolerance
847 }
848
849 fn generator(&self, i: usize) -> <SE3 as LieGroup>::LieAlgebra {
857 assert!(i < 6, "SE(3) only has generators for indices 0-5");
858
859 let mut generator = Matrix4::zeros();
860
861 match i {
862 0 => {
863 generator[(0, 3)] = 1.0;
865 }
866 1 => {
867 generator[(1, 3)] = 1.0;
869 }
870 2 => {
871 generator[(2, 3)] = 1.0;
873 }
874 3 => {
875 generator[(1, 2)] = -1.0;
877 generator[(2, 1)] = 1.0;
878 }
879 4 => {
880 generator[(0, 2)] = 1.0;
882 generator[(2, 0)] = -1.0;
883 }
884 5 => {
885 generator[(0, 1)] = -1.0;
887 generator[(1, 0)] = 1.0;
888 }
889 _ => unreachable!(),
890 }
891
892 generator
893 }
894}
895
896#[cfg(test)]
897mod tests {
898 use super::*;
899 use Quaternion;
900 use std::f64::consts::PI;
901
902 const TOLERANCE: f64 = 1e-9;
903
904 #[test]
905 fn test_se3_tangent_basic() {
906 let linear = Vector3::new(1.0, 2.0, 3.0);
907 let angular = Vector3::new(0.1, 0.2, 0.3);
908 let tangent = SE3Tangent::new(linear, angular);
909
910 assert_eq!(tangent.rho(), linear);
911 assert_eq!(tangent.theta(), angular);
912 }
913
914 #[test]
915 fn test_se3_tangent_zero() {
916 let zero = SE3Tangent::zero();
917 assert_eq!(zero.data, Vector6::zeros());
918
919 let tangent = SE3Tangent::from_components(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
920 assert!(tangent.is_zero(1e-10));
921 }
922
923 #[test]
925 fn test_se3_identity() {
926 let identity = SE3::identity();
927 assert!(identity.is_valid(TOLERANCE));
928
929 let translation = identity.translation();
930 let rotation = identity.rotation_quaternion();
931
932 assert!(translation.norm() < TOLERANCE);
933 assert!((rotation.angle()) < TOLERANCE);
934 }
935
936 #[test]
937 fn test_se3_new() {
938 let translation = Vector3::new(1.0, 2.0, 3.0);
939 let rotation = UnitQuaternion::from_euler_angles(0.1, 0.2, 0.3);
940
941 let se3 = SE3::new(translation, rotation);
942
943 assert!(se3.is_valid(TOLERANCE));
944 assert!((se3.translation() - translation).norm() < TOLERANCE);
945 assert!((se3.rotation_quaternion().angle() - rotation.angle()).abs() < TOLERANCE);
946 }
947
948 #[test]
949 fn test_se3_random() {
950 let se3 = SE3::random();
951 assert!(se3.is_valid(TOLERANCE));
952 }
953
954 #[test]
955 fn test_se3_inverse() {
956 let se3 = SE3::random();
957 let se3_inv = se3.inverse(None);
958
959 let composed = se3.compose(&se3_inv, None, None);
961 let identity = SE3::identity();
962
963 let translation_diff = (composed.translation() - identity.translation()).norm();
964 let rotation_diff = composed.rotation_quaternion().angle();
965
966 assert!(translation_diff < TOLERANCE);
967 assert!(rotation_diff < TOLERANCE);
968 }
969
970 #[test]
971 fn test_se3_compose() {
972 let se3_1 = SE3::random();
973 let se3_2 = SE3::random();
974
975 let composed = se3_1.compose(&se3_2, None, None);
976 assert!(composed.is_valid(TOLERANCE));
977
978 let identity = SE3::identity();
980 let composed_with_identity = se3_1.compose(&identity, None, None);
981
982 let translation_diff = (composed_with_identity.translation() - se3_1.translation()).norm();
983 let rotation_diff = (composed_with_identity.rotation_quaternion().angle()
984 - se3_1.rotation_quaternion().angle())
985 .abs();
986
987 assert!(translation_diff < TOLERANCE);
988 assert!(rotation_diff < TOLERANCE);
989 }
990
991 #[test]
992 fn test_se3_adjoint() {
993 let se3 = SE3::random();
994 let adj = se3.adjoint();
995
996 assert_eq!(adj.nrows(), 6);
998 assert_eq!(adj.ncols(), 6);
999
1000 let det = adj.determinant();
1003 assert!((det - 1.0).abs() < TOLERANCE);
1004 }
1005
1006 #[test]
1007 fn test_se3_act() {
1008 let se3 = SE3::random();
1009 let point = Vector3::new(1.0, 2.0, 3.0);
1010
1011 let _transformed_point = se3.act(&point, None, None);
1012
1013 let identity = SE3::identity();
1015 let identity_transformed = identity.act(&point, None, None);
1016
1017 let diff = (identity_transformed - point).norm();
1018 assert!(diff < TOLERANCE);
1019 }
1020
1021 #[test]
1022 fn test_se3_between() {
1023 let se3a = SE3::from_translation_euler(1.0, 2.0, 3.0, 0.1, 0.2, 0.3);
1024 let se3b = se3a.clone();
1025 let se3_between_identity = se3a.between(&se3b, None, None);
1026 assert!(se3_between_identity.is_approx(&SE3::identity(), TOLERANCE));
1027
1028 let se3c = SE3::from_translation_euler(4.0, 5.0, 6.0, 0.4, 0.5, 0.6);
1029 let se3_between = se3a.between(&se3c, None, None);
1030 let expected = se3a.inverse(None).compose(&se3c, None, None);
1031 assert!(se3_between.is_approx(&expected, TOLERANCE));
1032 }
1033
1034 #[test]
1035 fn test_se3_exp_log() {
1036 let tangent_vec = Vector6::new(0.1, 0.2, 0.3, 0.01, 0.02, 0.03);
1037 let tangent = SE3Tangent { data: tangent_vec };
1038
1039 let se3 = tangent.exp(None);
1041 let recovered_tangent = se3.log(None);
1042
1043 let diff = (tangent.data - recovered_tangent.data).norm();
1044 assert!(diff < TOLERANCE);
1045 }
1046
1047 #[test]
1048 fn test_se3_exp_zero() {
1049 let zero_tangent = SE3Tangent::zero();
1050 let se3 = zero_tangent.exp(None);
1051 let identity = SE3::identity();
1052
1053 let translation_diff = (se3.translation() - identity.translation()).norm();
1054 let rotation_diff = se3.rotation_quaternion().angle();
1055
1056 assert!(translation_diff < TOLERANCE);
1057 assert!(rotation_diff < TOLERANCE);
1058 }
1059
1060 #[test]
1061 fn test_se3_log_identity() {
1062 let identity = SE3::identity();
1063 let tangent = identity.log(None);
1064
1065 assert!(tangent.data.norm() < TOLERANCE);
1066 }
1067
1068 #[test]
1069 fn test_se3_normalize() {
1070 let translation = Vector3::new(1.0, 2.0, 3.0);
1071 let rotation =
1072 UnitQuaternion::from_quaternion(Quaternion::new(0.5, 0.5, 0.5, 0.5).normalize()); let mut se3 = SE3::new(translation, rotation);
1075 se3.normalize();
1076
1077 assert!(se3.is_valid(TOLERANCE));
1078 }
1079
1080 #[test]
1081 fn test_se3_manifold_properties() {
1082 assert_eq!(SE3::DIM, 3);
1084 assert_eq!(SE3::DOF, 6);
1085 assert_eq!(SE3::REP_SIZE, 7);
1086 }
1087
1088 #[test]
1089 fn test_se3_consistency() {
1090 let se3_1 = SE3::random();
1092 let se3_2 = SE3::random();
1093
1094 let se3_3 = SE3::random();
1096 let left_assoc = se3_1
1097 .compose(&se3_2, None, None)
1098 .compose(&se3_3, None, None);
1099 let right_assoc = se3_1.compose(&se3_2.compose(&se3_3, None, None), None, None);
1100
1101 let translation_diff = (left_assoc.translation() - right_assoc.translation()).norm();
1102 let rotation_diff = (left_assoc.rotation_quaternion().angle()
1103 - right_assoc.rotation_quaternion().angle())
1104 .abs();
1105
1106 assert!(translation_diff < 1e-10);
1107 assert!(rotation_diff < 1e-10);
1108 }
1109
1110 #[test]
1111 fn test_se3_specific_values() {
1112 let translation_only = SE3::new(Vector3::new(1.0, 2.0, 3.0), UnitQuaternion::identity());
1116
1117 let point = Vector3::new(0.0, 0.0, 0.0);
1118 let transformed = translation_only.act(&point, None, None);
1119 let expected = Vector3::new(1.0, 2.0, 3.0);
1120
1121 assert!((transformed - expected).norm() < TOLERANCE);
1122
1123 let rotation_only = SE3::new(
1125 Vector3::zeros(),
1126 UnitQuaternion::from_euler_angles(PI / 2.0, 0.0, 0.0),
1127 );
1128
1129 let point_y = Vector3::new(0.0, 1.0, 0.0);
1130 let rotated = rotation_only.act(&point_y, None, None);
1131 let expected_rotated = Vector3::new(0.0, 0.0, 1.0);
1132
1133 assert!((rotated - expected_rotated).norm() < TOLERANCE);
1134 }
1135
1136 #[test]
1137 fn test_se3_small_angle_approximations() {
1138 let small_tangent = Vector6::new(1e-8, 2e-8, 3e-8, 1e-9, 2e-9, 3e-9);
1140
1141 let se3 = SE3::new(
1142 Vector3::new(1e-8, 2e-8, 3e-8),
1143 UnitQuaternion::from_euler_angles(1e-9, 2e-9, 3e-9),
1144 );
1145 let recovered = se3.log(None);
1146
1147 let diff = (small_tangent - recovered.data).norm();
1148 assert!(diff < TOLERANCE);
1149 }
1150
1151 #[test]
1152 fn test_se3_tangent_norm() {
1153 let tangent_vec = Vector6::new(3.0, 4.0, 0.0, 0.0, 0.0, 0.0);
1154 let tangent = SE3Tangent { data: tangent_vec };
1155
1156 let norm = tangent.data.norm();
1157 assert!((norm - 5.0).abs() < TOLERANCE); }
1159
1160 #[test]
1161 fn test_se3_from_components() {
1162 let translation = Vector3::new(1.0, 2.0, 3.0);
1163 let quaternion = Quaternion::new(1.0, 0.0, 0.0, 0.0);
1164 let se3 = SE3::from_translation_quaternion(translation, quaternion);
1165 assert!(se3.is_valid(TOLERANCE));
1166 assert_eq!(se3.x(), 1.0);
1167 assert_eq!(se3.y(), 2.0);
1168 assert_eq!(se3.z(), 3.0);
1169
1170 let quat = se3.rotation_quaternion();
1171 assert!((quat.w - 1.0).abs() < TOLERANCE);
1172 assert!(quat.i.abs() < TOLERANCE);
1173 assert!(quat.j.abs() < TOLERANCE);
1174 assert!(quat.k.abs() < TOLERANCE);
1175 }
1176
1177 #[test]
1178 fn test_se3_from_isometry() {
1179 let translation = Translation3::new(1.0, 2.0, 3.0);
1180 let rotation = UnitQuaternion::from_euler_angles(0.1, 0.2, 0.3);
1181 let isometry = Isometry3::from_parts(translation, rotation);
1182
1183 let se3 = SE3::from_isometry(isometry);
1184 let recovered_isometry = se3.isometry();
1185
1186 let translation_diff =
1187 (isometry.translation.vector - recovered_isometry.translation.vector).norm();
1188 let rotation_diff = (isometry.rotation.angle() - recovered_isometry.rotation.angle()).abs();
1189
1190 assert!(translation_diff < TOLERANCE);
1191 assert!(rotation_diff < TOLERANCE);
1192 }
1193
1194 #[test]
1195 fn test_se3_matrix() {
1196 let se3 = SE3::random();
1197 let matrix = se3.matrix();
1198
1199 assert_eq!(matrix.nrows(), 4);
1201 assert_eq!(matrix.ncols(), 4);
1202
1203 assert!((matrix[(3, 0)]).abs() < TOLERANCE);
1205 assert!((matrix[(3, 1)]).abs() < TOLERANCE);
1206 assert!((matrix[(3, 2)]).abs() < TOLERANCE);
1207 assert!((matrix[(3, 3)] - 1.0).abs() < TOLERANCE);
1208 }
1209
1210 #[test]
1212 fn test_se3_manif_like_operations() {
1213 let g1 = SE3::new(
1217 Vector3::new(1.0, 0.0, 0.0),
1218 UnitQuaternion::from_euler_angles(0.0, 0.0, PI / 4.0),
1219 );
1220
1221 let g2 = SE3::new(
1222 Vector3::new(0.0, 1.0, 0.0),
1223 UnitQuaternion::from_euler_angles(0.0, PI / 4.0, 0.0),
1224 );
1225
1226 let g3 = g1.compose(&g2, None, None);
1228 assert!(g3.is_valid(TOLERANCE));
1229
1230 let g2_inv = g2.inverse(None);
1232 let g1_inv = g1.inverse(None);
1233 let result = g1
1234 .compose(&g2, None, None)
1235 .compose(&g2_inv, None, None)
1236 .compose(&g1_inv, None, None);
1237
1238 let identity = SE3::identity();
1239 let translation_diff = (result.translation() - identity.translation()).norm();
1240 let rotation_diff = result.rotation_quaternion().angle();
1241
1242 assert!(translation_diff < TOLERANCE);
1243 assert!(rotation_diff < TOLERANCE);
1244 }
1245
1246 #[test]
1247 fn test_se3_tangent_exp_jacobians() {
1248 let tangent = SE3Tangent::new(Vector3::new(0.1, 0.0, 0.0), Vector3::new(0.0, 0.1, 0.0));
1249
1250 let se3_element = tangent.exp(None);
1252 assert!(se3_element.is_valid(TOLERANCE));
1253
1254 let another_tangent = SE3Tangent::new(
1256 Vector3::new(0.01, 0.02, 0.03),
1257 Vector3::new(0.001, 0.002, 0.003),
1258 );
1259 let another_se3 = another_tangent.exp(None);
1260 assert!(another_se3.is_valid(TOLERANCE));
1261
1262 let _right_jac = tangent.right_jacobian();
1264 let _left_jac = tangent.left_jacobian();
1265 let _right_jac_inv = tangent.right_jacobian_inv();
1266 let _left_jac_inv = tangent.left_jacobian_inv();
1267
1268 assert_eq!(_right_jac.nrows(), 6);
1270 assert_eq!(_right_jac.ncols(), 6);
1271 assert_eq!(_left_jac.nrows(), 6);
1272 assert_eq!(_left_jac.ncols(), 6);
1273 assert_eq!(_right_jac_inv.nrows(), 6);
1274 assert_eq!(_right_jac_inv.ncols(), 6);
1275 assert_eq!(_left_jac_inv.nrows(), 6);
1276 assert_eq!(_left_jac_inv.ncols(), 6);
1277 }
1278
1279 #[test]
1280 fn test_se3_tangent_utility_functions() {
1281 let zero_vec = SE3Tangent::zero();
1283 assert!(zero_vec.data.norm() < TOLERANCE);
1284
1285 let random_vec = SE3Tangent::random();
1287 assert!(random_vec.data.norm() > 0.0);
1288
1289 let tangent = SE3Tangent::new(Vector3::zeros(), Vector3::zeros());
1291 assert!(tangent.is_zero(1e-10));
1292
1293 let non_zero_tangent = SE3Tangent::new(Vector3::new(1e-5, 0.0, 0.0), Vector3::zeros());
1294 assert!(!non_zero_tangent.is_zero(1e-10));
1295 }
1296
1297 #[test]
1300 fn test_se3_vee() {
1301 let se3 = SE3::random();
1302 let tangent_log = se3.log(None);
1303 let tangent_vee = se3.vee();
1304
1305 assert!((tangent_log.data - tangent_vee.data).norm() < 1e-10);
1306 }
1307
1308 #[test]
1309 fn test_se3_is_approx() {
1310 let se3_1 = SE3::random();
1311 let se3_2 = se3_1.clone();
1312
1313 assert!(se3_1.is_approx(&se3_1, 1e-10));
1314 assert!(se3_1.is_approx(&se3_2, 1e-10));
1315
1316 let small_tangent = SE3Tangent::new(
1318 Vector3::new(1e-12, 1e-12, 1e-12),
1319 Vector3::new(1e-12, 1e-12, 1e-12),
1320 );
1321 let se3_perturbed = se3_1.right_plus(&small_tangent, None, None);
1322 assert!(se3_1.is_approx(&se3_perturbed, 1e-10));
1323 }
1324
1325 #[test]
1326 fn test_se3_tangent_small_adj() {
1327 let tangent = SE3Tangent::new(Vector3::new(0.1, 0.2, 0.3), Vector3::new(0.4, 0.5, 0.6));
1328 let small_adj = tangent.small_adj();
1329
1330 let rho_skew = SO3Tangent::new(tangent.rho()).hat();
1335 let theta_skew = SO3Tangent::new(tangent.theta()).hat();
1336
1337 let top_left = small_adj.fixed_view::<3, 3>(0, 0);
1339 let bottom_right = small_adj.fixed_view::<3, 3>(3, 3);
1340 assert!((top_left - theta_skew).norm() < 1e-10);
1341 assert!((bottom_right - theta_skew).norm() < 1e-10);
1342
1343 let top_right = small_adj.fixed_view::<3, 3>(0, 3);
1345 assert!((top_right - rho_skew).norm() < 1e-10);
1346
1347 let bottom_left = small_adj.fixed_view::<3, 3>(3, 0);
1349 assert!(bottom_left.norm() < 1e-10);
1350 }
1351
1352 #[test]
1353 fn test_se3_tangent_lie_bracket() {
1354 let tangent_a = SE3Tangent::new(Vector3::new(0.1, 0.0, 0.0), Vector3::new(0.0, 0.2, 0.0));
1355 let tangent_b = SE3Tangent::new(Vector3::new(0.0, 0.3, 0.0), Vector3::new(0.0, 0.0, 0.4));
1356
1357 let bracket_ab = tangent_a.lie_bracket(&tangent_b);
1358 let bracket_ba = tangent_b.lie_bracket(&tangent_a);
1359
1360 assert!((bracket_ab.data + bracket_ba.data).norm() < 1e-10);
1362
1363 let bracket_aa = tangent_a.lie_bracket(&tangent_a);
1365 assert!(bracket_aa.is_zero(1e-10));
1366
1367 let bracket_hat = bracket_ab.hat();
1369 let expected = tangent_a.hat() * tangent_b.hat() - tangent_b.hat() * tangent_a.hat();
1370 assert!((bracket_hat - expected).norm() < 1e-10);
1371 }
1372
1373 #[test]
1374 fn test_se3_tangent_is_approx() {
1375 let tangent_1 = SE3Tangent::new(Vector3::new(0.1, 0.2, 0.3), Vector3::new(0.4, 0.5, 0.6));
1376 let tangent_2 = SE3Tangent::new(
1377 Vector3::new(0.1 + 1e-12, 0.2, 0.3),
1378 Vector3::new(0.4, 0.5, 0.6),
1379 );
1380 let tangent_3 = SE3Tangent::new(Vector3::new(0.7, 0.8, 0.9), Vector3::new(1.0, 1.1, 1.2));
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_se3_generators() {
1389 let tangent = SE3Tangent::new(Vector3::new(1.0, 1.0, 1.0), Vector3::new(1.0, 1.0, 1.0));
1390
1391 for i in 0..6 {
1393 let generator = tangent.generator(i);
1394
1395 assert_eq!(generator.nrows(), 4);
1397 assert_eq!(generator.ncols(), 4);
1398
1399 assert_eq!(generator[(3, 0)], 0.0);
1401 assert_eq!(generator[(3, 1)], 0.0);
1402 assert_eq!(generator[(3, 2)], 0.0);
1403 assert_eq!(generator[(3, 3)], 0.0);
1404 }
1405
1406 let e1 = tangent.generator(0); let e2 = tangent.generator(1); let e3 = tangent.generator(2); assert_eq!(e1[(0, 3)], 1.0);
1412 assert_eq!(e2[(1, 3)], 1.0);
1413 assert_eq!(e3[(2, 3)], 1.0);
1414
1415 let e4 = tangent.generator(3); let e5 = tangent.generator(4); let e6 = tangent.generator(5); assert_eq!(e4[(1, 2)], -1.0);
1422 assert_eq!(e4[(2, 1)], 1.0);
1423 assert_eq!(e5[(0, 2)], 1.0);
1424 assert_eq!(e5[(2, 0)], -1.0);
1425 assert_eq!(e6[(0, 1)], -1.0);
1426 assert_eq!(e6[(1, 0)], 1.0);
1427 }
1428
1429 #[test]
1430 #[should_panic]
1431 fn test_se3_generator_invalid_index() {
1432 let tangent = SE3Tangent::new(Vector3::new(1.0, 1.0, 1.0), Vector3::new(1.0, 1.0, 1.0));
1433 let _generator = tangent.generator(6); }
1435
1436 #[test]
1437 fn test_se3_jacobi_identity() {
1438 let x = SE3Tangent::new(Vector3::new(0.1, 0.0, 0.0), Vector3::new(0.0, 0.1, 0.0));
1440 let y = SE3Tangent::new(Vector3::new(0.0, 0.2, 0.0), Vector3::new(0.0, 0.0, 0.2));
1441 let z = SE3Tangent::new(Vector3::new(0.0, 0.0, 0.3), Vector3::new(0.3, 0.0, 0.0));
1442
1443 let term1 = x.lie_bracket(&y.lie_bracket(&z));
1444 let term2 = y.lie_bracket(&z.lie_bracket(&x));
1445 let term3 = z.lie_bracket(&x.lie_bracket(&y));
1446
1447 let jacobi_sum = SE3Tangent {
1448 data: term1.data + term2.data + term3.data,
1449 };
1450 assert!(jacobi_sum.is_zero(1e-10));
1451 }
1452
1453 #[test]
1454 fn test_se3_hat_matrix_structure() {
1455 let tangent = SE3Tangent::new(Vector3::new(0.1, 0.2, 0.3), Vector3::new(0.4, 0.5, 0.6));
1456 let hat_matrix = tangent.hat();
1457
1458 assert_eq!(hat_matrix[(0, 3)], tangent.rho()[0]);
1461 assert_eq!(hat_matrix[(1, 3)], tangent.rho()[1]);
1462 assert_eq!(hat_matrix[(2, 3)], tangent.rho()[2]);
1463
1464 let theta_hat = SO3Tangent::new(tangent.theta()).hat();
1466 let top_left = hat_matrix.fixed_view::<3, 3>(0, 0);
1467 assert!((top_left - theta_hat).norm() < 1e-10);
1468
1469 assert_eq!(hat_matrix[(3, 0)], 0.0);
1471 assert_eq!(hat_matrix[(3, 1)], 0.0);
1472 assert_eq!(hat_matrix[(3, 2)], 0.0);
1473 assert_eq!(hat_matrix[(3, 3)], 0.0);
1474 }
1475
1476 #[test]
1492 fn test_se3_accumulated_error_odometry() {
1493 let step = SE3::new(
1495 Vector3::new(1.0, 0.0, 0.0), UnitQuaternion::from_euler_angles(0.0, 0.0, 0.1), );
1498
1499 let mut pose = SE3::identity();
1500 for _ in 0..10 {
1501 pose = pose.compose(&step, None, None);
1502 }
1503
1504 let expected = SE3::new(
1506 Vector3::new(10.0, 0.0, 0.0),
1507 UnitQuaternion::from_euler_angles(0.0, 0.0, 1.0),
1508 );
1509
1510 assert!(pose.is_approx(&expected, 5.0));
1513 }
1514
1515 #[test]
1529 fn test_se3_large_translation_small_rotation() {
1530 let large_t = Vector3::new(1000.0, 2000.0, 500.0);
1532 let small_r = SO3::from_scaled_axis(Vector3::new(1e-6, 2e-6, 3e-6));
1533 let se3 = SE3::from_translation_so3(large_t, small_r);
1534
1535 let tangent = se3.log(None);
1536 let recovered = tangent.exp(None);
1537
1538 assert!(se3.is_approx(&recovered, 1e-3));
1540 }
1541
1542 #[test]
1543 fn test_se3_small_translation_large_rotation() {
1544 let small_t = Vector3::new(0.001, 0.002, -0.001);
1546 let large_r = SO3::from_euler_angles(1.5, 0.5, -1.2);
1548 let se3 = SE3::from_translation_so3(small_t, large_r);
1549
1550 let tangent = se3.log(None);
1551 let recovered = tangent.exp(None);
1552
1553 assert!(se3.is_approx(&recovered, 1e-3));
1555 }
1556
1557 #[test]
1575 fn test_se3_right_jacobian_inverse_identity() {
1576 let tangent = SE3Tangent::new(
1578 Vector3::new(0.1, 0.15, 0.2),
1579 Vector3::new(0.001, 0.002, 0.003),
1580 );
1581 let jr = tangent.right_jacobian();
1582 let jr_inv = tangent.right_jacobian_inv();
1583 let product = jr * jr_inv;
1584 let identity = Matrix6::identity();
1585
1586 assert!((product - identity).norm() < 0.01);
1588 }
1589
1590 #[test]
1591 fn test_se3_left_jacobian_inverse_identity() {
1592 let tangent = SE3Tangent::new(
1594 Vector3::new(0.1, 0.15, 0.2),
1595 Vector3::new(0.001, 0.002, 0.003),
1596 );
1597 let jl = tangent.left_jacobian();
1598 let jl_inv = tangent.left_jacobian_inv();
1599 let product = jl * jl_inv;
1600
1601 assert!((product - Matrix6::identity()).norm() < 0.01);
1603 }
1604}