1use crate::{LieGroup, Tangent};
36use nalgebra::{DVector, Matrix3, Matrix4, Quaternion, Unit, UnitQuaternion, Vector3};
37use std::{
38 fmt,
39 fmt::{Display, Formatter},
40};
41
42#[derive(Clone, PartialEq)]
46pub struct SO3 {
47 quaternion: UnitQuaternion<f64>,
49}
50
51impl Display for SO3 {
52 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
53 let q = self.quaternion.quaternion();
54 write!(
55 f,
56 "SO3(quaternion: [w: {:.4}, x: {:.4}, y: {:.4}, z: {:.4}])",
57 q.w, q.i, q.j, q.k
58 )
59 }
60}
61
62impl SO3 {
63 pub const DIM: usize = 3;
65
66 pub const DOF: usize = 3;
68
69 pub const REP_SIZE: usize = 4;
71
72 pub fn identity() -> Self {
76 SO3 {
77 quaternion: UnitQuaternion::identity(),
78 }
79 }
80
81 pub fn jacobian_identity() -> Matrix3<f64> {
85 Matrix3::<f64>::identity()
86 }
87
88 #[inline]
93 pub fn new(quaternion: UnitQuaternion<f64>) -> Self {
94 SO3 { quaternion }
95 }
96
97 pub fn from_quaternion_coeffs(x: f64, y: f64, z: f64, w: f64) -> Self {
109 let q = Quaternion::new(w, x, y, z);
110 SO3::new(UnitQuaternion::from_quaternion(q))
111 }
112
113 pub fn from_quaternion_wxyz(w: f64, x: f64, y: f64, z: f64) -> Self {
125 let q = Quaternion::new(w, x, y, z);
126 SO3::new(UnitQuaternion::from_quaternion(q))
127 }
128
129 pub fn from_euler_angles(roll: f64, pitch: f64, yaw: f64) -> Self {
131 let quaternion = UnitQuaternion::from_euler_angles(roll, pitch, yaw);
132 SO3::new(quaternion)
133 }
134
135 pub fn from_axis_angle(axis: &Vector3<f64>, angle: f64) -> Self {
137 let unit_axis = Unit::new_normalize(*axis);
138 let quaternion = UnitQuaternion::from_axis_angle(&unit_axis, angle);
139 SO3::new(quaternion)
140 }
141
142 pub fn from_scaled_axis(axis_angle: Vector3<f64>) -> Self {
144 let quaternion = UnitQuaternion::from_scaled_axis(axis_angle);
145 SO3::new(quaternion)
146 }
147
148 pub fn quaternion(&self) -> UnitQuaternion<f64> {
150 self.quaternion
151 }
152
153 pub fn from_quaternion(quaternion: UnitQuaternion<f64>) -> Self {
155 Self::new(quaternion)
156 }
157
158 pub fn to_quaternion(&self) -> UnitQuaternion<f64> {
160 self.quaternion
161 }
162
163 pub fn quat(&self) -> Quaternion<f64> {
165 *self.quaternion.quaternion()
166 }
167
168 #[inline]
170 pub fn x(&self) -> f64 {
171 self.quaternion.i
172 }
173
174 #[inline]
176 pub fn y(&self) -> f64 {
177 self.quaternion.j
178 }
179
180 #[inline]
182 pub fn z(&self) -> f64 {
183 self.quaternion.k
184 }
185
186 #[inline]
188 pub fn w(&self) -> f64 {
189 self.quaternion.w
190 }
191
192 pub fn rotation_matrix(&self) -> Matrix3<f64> {
194 self.quaternion.to_rotation_matrix().into_inner()
195 }
196
197 pub fn transform(&self) -> Matrix4<f64> {
199 self.quaternion.to_homogeneous()
200 }
201
202 pub fn set_quaternion(&mut self, coeffs: &[f64; 4]) {
204 let q = Quaternion::new(coeffs[0], coeffs[1], coeffs[2], coeffs[3]);
205 self.quaternion = UnitQuaternion::from_quaternion(q);
206 }
207
208 #[inline]
210 pub fn coeffs(&self) -> [f64; 4] {
211 let q = self.quaternion.quaternion();
212 [q.w, q.i, q.j, q.k]
213 }
214
215 pub fn distance(&self, other: &Self) -> f64 {
220 self.between(other, None, None).log(None).angle()
221 }
222}
223
224impl From<DVector<f64>> for SO3 {
226 fn from(data: DVector<f64>) -> Self {
227 SO3::from_quaternion_coeffs(data[0], data[1], data[2], data[3])
228 }
229}
230
231impl From<SO3> for DVector<f64> {
232 fn from(so3: SO3) -> Self {
233 DVector::from_vec(so3.coeffs().to_vec())
234 }
235}
236
237impl LieGroup for SO3 {
239 type TangentVector = SO3Tangent;
240 type JacobianMatrix = Matrix3<f64>;
241 type LieAlgebra = Matrix3<f64>;
242
243 fn inverse(&self, jacobian: Option<&mut Self::JacobianMatrix>) -> Self {
255 let inverse_quat = self.quaternion.inverse();
256
257 if let Some(jac) = jacobian {
258 *jac = -self.adjoint();
259 }
260
261 SO3 {
262 quaternion: inverse_quat,
263 }
264 }
265
266 fn compose(
281 &self,
282 other: &Self,
283 jacobian_self: Option<&mut Self::JacobianMatrix>,
284 jacobian_other: Option<&mut Self::JacobianMatrix>,
285 ) -> Self {
286 let result = SO3 {
287 quaternion: self.quaternion * other.quaternion,
288 };
289
290 if let Some(jac_self) = jacobian_self {
291 *jac_self = other.inverse(None).adjoint();
292 }
293
294 if let Some(jac_other) = jacobian_other {
295 *jac_other = Matrix3::identity();
296 }
297
298 result
299 }
300
301 fn log(&self, jacobian: Option<&mut Self::JacobianMatrix>) -> Self::TangentVector {
314 debug_assert!(
315 (self.quaternion.norm() - 1.0).abs() < 1e-6,
316 "SO3::log() requires normalized quaternion, norm = {}",
317 self.quaternion.norm()
318 );
319
320 let q = self.quaternion.quaternion();
321 let sin_angle_squared = q.i * q.i + q.j * q.j + q.k * q.k;
322
323 let log_coeff = if sin_angle_squared > crate::SMALL_ANGLE_THRESHOLD {
324 let sin_angle = sin_angle_squared.sqrt();
325 let cos_angle = q.w;
326
327 let two_angle = 2.0
335 * if cos_angle < 0.0 {
336 f64::atan2(-sin_angle, -cos_angle)
337 } else {
338 f64::atan2(sin_angle, cos_angle)
339 };
340
341 two_angle / sin_angle
342 } else {
343 2.0
344 };
345
346 let axis_angle = SO3Tangent::new(Vector3::new(
347 q.i * log_coeff,
348 q.j * log_coeff,
349 q.k * log_coeff,
350 ));
351
352 if let Some(jac) = jacobian {
353 *jac = axis_angle.right_jacobian_inv();
354 }
355
356 axis_angle
357 }
358
359 fn act(
360 &self,
361 vector: &Vector3<f64>,
362 jacobian_self: Option<&mut Self::JacobianMatrix>,
363 jacobian_vector: Option<&mut Matrix3<f64>>,
364 ) -> Vector3<f64> {
365 let result = self.quaternion * vector;
366
367 if let Some(jac_self) = jacobian_self {
368 let vector_hat = SO3Tangent::new(*vector).hat();
370 *jac_self = -self.rotation_matrix() * vector_hat;
371 }
372
373 if let Some(jac_vector) = jacobian_vector {
374 *jac_vector = self.rotation_matrix();
375 }
376
377 result
378 }
379
380 fn adjoint(&self) -> Self::JacobianMatrix {
381 self.rotation_matrix()
382 }
383
384 fn random() -> Self {
385 SO3 {
386 quaternion: UnitQuaternion::from_scaled_axis(Vector3::new(
387 rand::random::<f64>() * 2.0 - 1.0,
388 rand::random::<f64>() * 2.0 - 1.0,
389 rand::random::<f64>() * 2.0 - 1.0,
390 )),
391 }
392 }
393
394 fn jacobian_identity() -> Self::JacobianMatrix {
395 Matrix3::<f64>::identity()
396 }
397
398 fn zero_jacobian() -> Self::JacobianMatrix {
399 Matrix3::<f64>::zeros()
400 }
401
402 fn normalize(&mut self) {
403 let q = self.quaternion.into_inner().normalize();
404 self.quaternion = UnitQuaternion::from_quaternion(q);
405 }
406
407 fn is_valid(&self, tolerance: f64) -> bool {
408 let norm_diff = (self.quaternion.norm() - 1.0).abs();
410 norm_diff < tolerance
411 }
412
413 fn vee(&self) -> Self::TangentVector {
418 self.log(None)
419 }
420
421 fn is_approx(&self, other: &Self, tolerance: f64) -> bool {
427 let difference = self.right_minus(other, None, None);
428 difference.is_zero(tolerance)
429 }
430}
431
432#[derive(Clone, PartialEq)]
438pub struct SO3Tangent {
439 data: Vector3<f64>,
441}
442
443impl fmt::Display for SO3Tangent {
444 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
445 write!(
446 f,
447 "so3(axis-angle: [{:.4}, {:.4}, {:.4}])",
448 self.data.x, self.data.y, self.data.z
449 )
450 }
451}
452
453impl From<DVector<f64>> for SO3Tangent {
455 fn from(data_vector: DVector<f64>) -> Self {
456 SO3Tangent {
457 data: Vector3::new(data_vector[0], data_vector[1], data_vector[2]),
458 }
459 }
460}
461
462impl From<SO3Tangent> for DVector<f64> {
463 fn from(so3_tangent: SO3Tangent) -> Self {
464 DVector::from_vec(vec![
465 so3_tangent.data.x,
466 so3_tangent.data.y,
467 so3_tangent.data.z,
468 ])
469 }
470}
471
472impl SO3Tangent {
473 #[inline]
478 pub fn new(axis_angle: Vector3<f64>) -> Self {
479 SO3Tangent { data: axis_angle }
480 }
481
482 pub fn from_components(x: f64, y: f64, z: f64) -> Self {
484 SO3Tangent::new(Vector3::new(x, y, z))
485 }
486
487 #[inline]
489 pub fn axis_angle(&self) -> Vector3<f64> {
490 self.data
491 }
492
493 #[inline]
495 pub fn angle(&self) -> f64 {
496 self.data.norm()
497 }
498
499 #[inline]
501 pub fn axis(&self) -> Vector3<f64> {
502 let norm = self.data.norm();
503 if norm < f64::EPSILON {
504 Vector3::identity()
505 } else {
506 self.data / norm
507 }
508 }
509
510 #[inline]
512 pub fn x(&self) -> f64 {
513 self.data.x
514 }
515
516 #[inline]
518 pub fn y(&self) -> f64 {
519 self.data.y
520 }
521
522 #[inline]
524 pub fn z(&self) -> f64 {
525 self.data.z
526 }
527
528 #[inline]
530 pub fn coeffs(&self) -> Vector3<f64> {
531 self.data
532 }
533
534 pub fn ang(&self) -> Vector3<f64> {
536 self.data
537 }
538}
539
540impl Tangent<SO3> for SO3Tangent {
542 const DIM: usize = 3;
544
545 fn exp(&self, jacobian: Option<&mut <SO3 as LieGroup>::JacobianMatrix>) -> SO3 {
559 let theta_squared = self.data.norm_squared();
560
561 let quaternion = if theta_squared > crate::SMALL_ANGLE_THRESHOLD {
562 UnitQuaternion::from_scaled_axis(self.data)
563 } else {
564 UnitQuaternion::from_quaternion(Quaternion::new(
565 1.0,
566 self.data.x / 2.0,
567 self.data.y / 2.0,
568 self.data.z / 2.0,
569 ))
570 };
571
572 if let Some(jac) = jacobian {
573 *jac = self.right_jacobian();
574 }
575
576 SO3 { quaternion }
577 }
578
579 fn right_jacobian(&self) -> <SO3 as LieGroup>::JacobianMatrix {
586 self.left_jacobian().transpose()
587 }
588
589 fn left_jacobian(&self) -> <SO3 as LieGroup>::JacobianMatrix {
596 let angle = self.data.norm_squared();
597 let tangent_skew = self.hat();
598
599 if angle <= crate::SMALL_ANGLE_THRESHOLD {
600 Matrix3::identity() + 0.5 * tangent_skew
601 } else {
602 let theta = angle.sqrt();
603 let sin_theta = theta.sin();
604 let cos_theta = theta.cos();
605
606 Matrix3::identity()
607 + (1.0 - cos_theta) / angle * tangent_skew
608 + (theta - sin_theta) / (angle * angle) * tangent_skew * tangent_skew
609 }
610 }
611
612 fn right_jacobian_inv(&self) -> <SO3 as LieGroup>::JacobianMatrix {
636 self.left_jacobian_inv().transpose()
637 }
638
639 fn left_jacobian_inv(&self) -> <SO3 as LieGroup>::JacobianMatrix {
664 let angle = self.data.norm_squared();
665 let tangent_skew = self.hat();
666
667 if angle <= crate::SMALL_ANGLE_THRESHOLD {
668 Matrix3::identity() - 0.5 * tangent_skew
669 } else {
670 let theta = angle.sqrt();
671 let sin_theta = theta.sin();
672 let cos_theta = theta.cos();
673
674 Matrix3::identity() - (0.5 * tangent_skew)
675 + (1.0 / angle - (1.0 + cos_theta) / (2.0 * theta * sin_theta))
676 * tangent_skew
677 * tangent_skew
678 }
679 }
680
681 fn hat(&self) -> <SO3 as LieGroup>::LieAlgebra {
687 Matrix3::new(
688 0.0,
689 -self.data.z,
690 self.data.y,
691 self.data.z,
692 0.0,
693 -self.data.x,
694 -self.data.y,
695 self.data.x,
696 0.0,
697 )
698 }
699
700 fn zero() -> <SO3 as LieGroup>::TangentVector {
701 Self::new(Vector3::zeros())
702 }
703
704 fn random() -> <SO3 as LieGroup>::TangentVector {
705 Self::new(Vector3::new(
706 rand::random::<f64>() * 0.2 - 0.1,
707 rand::random::<f64>() * 0.2 - 0.1,
708 rand::random::<f64>() * 0.2 - 0.1,
709 ))
710 }
711
712 fn is_zero(&self, tolerance: f64) -> bool {
713 self.data.norm() < tolerance
714 }
715
716 fn normalize(&mut self) {
717 let norm = self.data.norm();
718 if norm > f64::EPSILON {
719 self.data /= norm;
720 }
721 }
722
723 fn normalized(&self) -> <SO3 as LieGroup>::TangentVector {
724 let norm = self.data.norm();
725 if norm > f64::EPSILON {
726 SO3Tangent::new(self.data / norm)
727 } else {
728 Self::zero()
729 }
730 }
731
732 fn small_adj(&self) -> <SO3 as LieGroup>::JacobianMatrix {
736 self.hat()
737 }
738
739 fn lie_bracket(&self, other: &Self) -> <SO3 as LieGroup>::TangentVector {
743 let bracket_result = self.small_adj() * other.data;
744 SO3Tangent::new(bracket_result)
745 }
746
747 fn is_approx(&self, other: &Self, tolerance: f64) -> bool {
753 (self.data - other.data).norm() < tolerance
754 }
755
756 fn generator(&self, i: usize) -> <SO3 as LieGroup>::LieAlgebra {
764 assert!(i < 3, "SO(3) only has generators for indices 0, 1, 2");
765
766 match i {
767 0 => {
768 Matrix3::new(0.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 1.0, 0.0)
770 }
771 1 => {
772 Matrix3::new(0.0, 0.0, 1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0)
774 }
775 2 => {
776 Matrix3::new(0.0, -1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0)
778 }
779 _ => unreachable!(),
780 }
781 }
782}
783
784#[cfg(test)]
785mod tests {
786 use super::*;
787 use core::f64;
788 use std::f64::consts::PI;
789
790 const TOLERANCE: f64 = 1e-12;
791
792 #[test]
793 fn test_so3_constructor_datatype() {
794 let so3 = SO3::from_quaternion_coeffs(0.0, 0.0, 0.0, 1.0);
795 assert_eq!(0.0, so3.x());
796 assert_eq!(0.0, so3.y());
797 assert_eq!(0.0, so3.z());
798 assert_eq!(1.0, so3.w());
799 }
800
801 #[test]
802 fn test_so3_constructor_quat() {
803 let quat = UnitQuaternion::identity();
804 let so3 = SO3::new(quat);
805 assert_eq!(0.0, so3.x());
806 assert_eq!(0.0, so3.y());
807 assert_eq!(0.0, so3.z());
808 assert_eq!(1.0, so3.w());
809 }
810
811 #[test]
812 fn test_so3_constructor_euler() {
813 let so3 = SO3::from_euler_angles(0.0, 0.0, 0.0);
814 assert_eq!(0.0, so3.x());
815 assert_eq!(0.0, so3.y());
816 assert_eq!(0.0, so3.z());
817 assert_eq!(1.0, so3.w());
818 }
819
820 #[test]
821 fn test_so3_identity() {
822 let so3 = SO3::identity();
823 assert_eq!(0.0, so3.x());
824 assert_eq!(0.0, so3.y());
825 assert_eq!(0.0, so3.z());
826 assert_eq!(1.0, so3.w());
827 }
828
829 #[test]
830 fn test_so3_coeffs() {
831 let so3 = SO3::from_quaternion_coeffs(0.0, 0.0, 0.0, 1.0);
833 let coeffs = so3.coeffs();
834 assert!((coeffs[0] - 1.0).abs() < TOLERANCE); assert!((coeffs[1] - 0.0).abs() < TOLERANCE); assert!((coeffs[2] - 0.0).abs() < TOLERANCE); assert!((coeffs[3] - 0.0).abs() < TOLERANCE); let so3 = SO3::from_quaternion_coeffs(0.1, 0.2, 0.3, 0.4);
841 let coeffs = so3.coeffs();
842 let original_quat = Quaternion::new(0.4, 0.1, 0.2, 0.3);
843 let normalized_quat = original_quat.normalize();
844 assert!((coeffs[0] - normalized_quat.w).abs() < TOLERANCE);
845 assert!((coeffs[1] - normalized_quat.i).abs() < TOLERANCE);
846 assert!((coeffs[2] - normalized_quat.j).abs() < TOLERANCE);
847 assert!((coeffs[3] - normalized_quat.k).abs() < TOLERANCE);
848 }
849
850 #[test]
851 fn test_so3_random() {
852 let so3 = SO3::random();
853 assert!((so3.quaternion().norm() - 1.0).abs() < TOLERANCE);
854 }
855
856 #[test]
857 fn test_so3_transform() {
858 let so3 = SO3::identity();
859 let transform = so3.transform();
860
861 assert_eq!(4, transform.nrows());
862 assert_eq!(4, transform.ncols());
863
864 for i in 0..4 {
866 for j in 0..4 {
867 if i == j {
868 assert!((transform[(i, j)] - 1.0).abs() < TOLERANCE);
869 } else {
870 assert!(transform[(i, j)].abs() < TOLERANCE);
871 }
872 }
873 }
874 }
875
876 #[test]
877 fn test_so3_rotation() {
878 let so3 = SO3::identity();
879 let rotation = so3.rotation_matrix();
880
881 assert_eq!(3, rotation.nrows());
882 assert_eq!(3, rotation.ncols());
883
884 for i in 0..3 {
886 for j in 0..3 {
887 if i == j {
888 assert!((rotation[(i, j)] - 1.0).abs() < TOLERANCE);
889 } else {
890 assert!(rotation[(i, j)].abs() < TOLERANCE);
891 }
892 }
893 }
894 }
895
896 #[test]
897 fn test_so3_inverse() {
898 let so3 = SO3::identity();
900 let so3_inv = so3.inverse(None);
901 assert_eq!(0.0, so3_inv.x());
902 assert_eq!(0.0, so3_inv.y());
903 assert_eq!(0.0, so3_inv.z());
904 assert_eq!(1.0, so3_inv.w());
905
906 let so3 = SO3::random();
908 let so3_inv = so3.inverse(None);
909 assert!((so3.x() + so3_inv.x()).abs() < TOLERANCE);
910 assert!((so3.y() + so3_inv.y()).abs() < TOLERANCE);
911 assert!((so3.z() + so3_inv.z()).abs() < TOLERANCE);
912 assert!((so3.w() - so3_inv.w()).abs() < TOLERANCE);
913 }
914
915 #[test]
916 fn test_so3_inverse_jacobian() {
917 let so3 = SO3::identity();
918 let mut jacobian = Matrix3::zeros();
919 let so3_inv = so3.inverse(Some(&mut jacobian));
920
921 assert_eq!(0.0, so3_inv.x());
923 assert_eq!(0.0, so3_inv.y());
924 assert_eq!(0.0, so3_inv.z());
925 assert_eq!(1.0, so3_inv.w());
926
927 let expected_jac = -Matrix3::identity();
929 assert!((jacobian - expected_jac).norm() < TOLERANCE);
930 }
931
932 #[test]
933 fn test_so3_rplus() {
934 let so3a = SO3::identity();
936 let so3b = SO3Tangent::new(Vector3::zeros());
937 let so3c = so3a.right_plus(&so3b, None, None);
938 assert_eq!(0.0, so3c.x());
939 assert_eq!(0.0, so3c.y());
940 assert_eq!(0.0, so3c.z());
941 assert_eq!(1.0, so3c.w());
942
943 let so3a = SO3::random();
945 let so3c = so3a.right_plus(&so3b, None, None);
946 assert!((so3a.x() - so3c.x()).abs() < TOLERANCE);
947 assert!((so3a.y() - so3c.y()).abs() < TOLERANCE);
948 assert!((so3a.z() - so3c.z()).abs() < TOLERANCE);
949 assert!((so3a.w() - so3c.w()).abs() < TOLERANCE);
950 }
951
952 #[test]
953 fn test_so3_lplus() {
954 let so3a = SO3::identity();
956 let so3t = SO3Tangent::new(Vector3::zeros());
957 let so3c = so3a.left_plus(&so3t, None, None);
958 assert_eq!(0.0, so3c.x());
959 assert_eq!(0.0, so3c.y());
960 assert_eq!(0.0, so3c.z());
961 assert_eq!(1.0, so3c.w());
962
963 let so3a = SO3::random();
965 let so3c = so3a.left_plus(&so3t, None, None);
966 assert!((so3a.x() - so3c.x()).abs() < TOLERANCE);
967 assert!((so3a.y() - so3c.y()).abs() < TOLERANCE);
968 assert!((so3a.z() - so3c.z()).abs() < TOLERANCE);
969 assert!((so3a.w() - so3c.w()).abs() < TOLERANCE);
970 }
971
972 #[test]
973 fn test_so3_rminus() {
974 let so3a = SO3::identity();
976 let so3b = SO3::identity();
977 let so3c = so3a.right_minus(&so3b, None, None);
978 assert!(so3c.x().abs() < TOLERANCE);
979 assert!(so3c.y().abs() < TOLERANCE);
980 assert!(so3c.z().abs() < TOLERANCE);
981
982 let so3a = SO3::random();
984 let so3b = so3a.clone();
985 let so3c = so3a.right_minus(&so3b, None, None);
986 assert!(so3c.data.norm() < TOLERANCE);
987 }
988
989 #[test]
990 fn test_so3_minus() {
991 let so3a = SO3::random();
993 let so3b = SO3::random();
994 let so3c = so3a.minus(&so3b, None, None);
995 let so3d = so3a.right_minus(&so3b, None, None);
996 assert!((so3c.data - so3d.data).norm() < TOLERANCE);
997 }
998
999 #[test]
1000 fn test_so3_exp_log() {
1001 let so3t = SO3Tangent::new(Vector3::zeros());
1003 let so3 = so3t.exp(None);
1004 assert_eq!(0.0, so3.x());
1005 assert_eq!(0.0, so3.y());
1006 assert_eq!(0.0, so3.z());
1007 assert_eq!(1.0, so3.w());
1008
1009 let so3t = SO3Tangent::new(Vector3::new(0.1, 0.2, 0.3));
1011 let so3 = so3t.exp(None);
1012 let so3n = SO3Tangent::new(Vector3::new(-0.1, -0.2, -0.3));
1013 let so3_inv = so3n.exp(None);
1014 assert!((so3_inv.x() + so3.x()).abs() < TOLERANCE);
1015 assert!((so3_inv.y() + so3.y()).abs() < TOLERANCE);
1016 assert!((so3_inv.z() + so3.z()).abs() < TOLERANCE);
1017 assert!((so3_inv.w() - so3.w()).abs() < TOLERANCE);
1018 }
1019
1020 #[test]
1021 fn test_so3_log() {
1022 let so3 = SO3::identity();
1024 let so3_log = so3.log(None);
1025 assert!(so3_log.x().abs() < TOLERANCE);
1026 assert!(so3_log.y().abs() < TOLERANCE);
1027 assert!(so3_log.z().abs() < TOLERANCE);
1028
1029 let so3 = SO3::random();
1031 let so3_log = so3.log(None);
1032 let so3_inv_log = so3.inverse(None).log(None);
1033 assert!((so3_inv_log.x() + so3_log.x()).abs() < TOLERANCE);
1034 assert!((so3_inv_log.y() + so3_log.y()).abs() < TOLERANCE);
1035 assert!((so3_inv_log.z() + so3_log.z()).abs() < TOLERANCE);
1036 }
1037
1038 #[test]
1039 fn test_so3_tangent_hat() {
1040 let so3_tan = SO3Tangent::new(Vector3::new(1.0, 2.0, 3.0));
1041 let so3_lie = so3_tan.hat();
1042
1043 assert!((so3_lie[(0, 0)] - 0.0).abs() < TOLERANCE);
1044 assert!((so3_lie[(0, 1)] + 3.0).abs() < TOLERANCE);
1045 assert!((so3_lie[(0, 2)] - 2.0).abs() < TOLERANCE);
1046 assert!((so3_lie[(1, 0)] - 3.0).abs() < TOLERANCE);
1047 assert!((so3_lie[(1, 1)] - 0.0).abs() < TOLERANCE);
1048 assert!((so3_lie[(1, 2)] + 1.0).abs() < TOLERANCE);
1049 assert!((so3_lie[(2, 0)] + 2.0).abs() < TOLERANCE);
1050 assert!((so3_lie[(2, 1)] - 1.0).abs() < TOLERANCE);
1051 assert!((so3_lie[(2, 2)] - 0.0).abs() < TOLERANCE);
1052 }
1053
1054 #[test]
1055 fn test_so3_act() {
1056 let so3 = SO3::identity();
1057 let transformed_point = so3.act(&Vector3::new(1.0, 1.0, 1.0), None, None);
1058 assert!((transformed_point.x - 1.0).abs() < TOLERANCE);
1059 assert!((transformed_point.y - 1.0).abs() < TOLERANCE);
1060 assert!((transformed_point.z - 1.0).abs() < TOLERANCE);
1061
1062 let so3 = SO3::from_euler_angles(PI, PI / 2.0, PI / 4.0);
1063 let transformed_point = so3.act(&Vector3::new(1.0, 1.0, 1.0), None, None);
1064 assert!((transformed_point.x - 0.0).abs() < TOLERANCE);
1065 assert!((transformed_point.y + f64::consts::SQRT_2).abs() < 1e-10);
1066 assert!((transformed_point.z + 1.0).abs() < TOLERANCE);
1067 }
1068
1069 #[test]
1070 fn test_so3_tangent_angular_velocity() {
1071 let so3tan = SO3Tangent::new(Vector3::new(1.0, 2.0, 3.0));
1072 let ang_vel = so3tan.ang();
1073 assert!((ang_vel - Vector3::new(1.0, 2.0, 3.0)).norm() < TOLERANCE);
1074 }
1075
1076 #[test]
1077 fn test_so3_compose() {
1078 let so3_1 = SO3::random();
1079 let so3_2 = SO3::random();
1080 let composed = so3_1.compose(&so3_2, None, None);
1081 assert!(composed.is_valid(TOLERANCE));
1082
1083 let identity = SO3::identity();
1085 let composed_with_identity = so3_1.compose(&identity, None, None);
1086 assert!((composed_with_identity.distance(&so3_1)).abs() < TOLERANCE);
1087 }
1088
1089 #[test]
1090 fn test_so3_exp_log_consistency() {
1091 let tangent = SO3Tangent::new(Vector3::new(0.1, 0.2, 0.3));
1092 let so3 = tangent.exp(None);
1093 let recovered_tangent = so3.log(None);
1094 assert!((tangent.data - recovered_tangent.data).norm() < TOLERANCE);
1095 }
1096
1097 #[test]
1098 fn test_so3_right_left_jacobian_relationship() {
1099 let tangent = SO3Tangent::new(Vector3::zeros());
1101 let ljac = tangent.left_jacobian();
1102 let rjac = tangent.right_jacobian();
1103 assert!((ljac - rjac).norm() < TOLERANCE);
1104 assert!((ljac - Matrix3::identity()).norm() < TOLERANCE);
1105
1106 let tangent = SO3Tangent::new(Vector3::new(0.1, 0.2, 0.3));
1108 let ljac = tangent.left_jacobian();
1109 let rjac = tangent.right_jacobian();
1110
1111 assert!((ljac - rjac.transpose()).norm() < TOLERANCE);
1114 assert!((rjac - ljac.transpose()).norm() < TOLERANCE);
1115 }
1116
1117 #[test]
1118 fn test_so3_manifold_properties() {
1119 assert_eq!(SO3::DIM, 3);
1120 assert_eq!(SO3::DOF, 3);
1121 assert_eq!(SO3::REP_SIZE, 4);
1122 }
1123
1124 #[test]
1125 fn test_so3_normalize() {
1126 let mut so3 = SO3::from_quaternion_coeffs(0.5, 0.5, 0.5, 0.5);
1127 so3.normalize();
1128 assert!(so3.is_valid(TOLERANCE));
1129 }
1130
1131 #[test]
1132 fn test_so3_tangent_norms() {
1133 let tangent = SO3Tangent::new(Vector3::new(3.0, 4.0, 0.0));
1134 let norm = tangent.data.norm();
1135 assert!((norm - 5.0).abs() < TOLERANCE);
1136
1137 let squared_norm = tangent.data.norm_squared();
1138 assert!((squared_norm - 25.0).abs() < TOLERANCE);
1139 }
1140
1141 #[test]
1142 fn test_so3_tangent_zero() {
1143 let zero = SO3Tangent::zero();
1144 assert!(zero.data.norm() < TOLERANCE);
1145
1146 let tangent = SO3Tangent::new(Vector3::zeros());
1147 assert!(tangent.is_zero(TOLERANCE));
1148 }
1149
1150 #[test]
1151 fn test_so3_tangent_normalize() {
1152 let mut tangent = SO3Tangent::new(Vector3::new(3.0, 4.0, 0.0));
1153 tangent.normalize();
1154 assert!((tangent.data.norm() - 1.0).abs() < TOLERANCE);
1155 }
1156
1157 #[test]
1158 fn test_so3_adjoint() {
1159 let so3 = SO3::random();
1160 let adj = so3.adjoint();
1161 assert_eq!(adj.nrows(), 3);
1162 assert_eq!(adj.ncols(), 3);
1163
1164 let det = adj.determinant();
1166 assert!((det - 1.0).abs() < TOLERANCE);
1167 }
1168
1169 #[test]
1170 fn test_so3_small_angle_approximations() {
1171 let small_tangent = SO3Tangent::new(Vector3::new(1e-8, 2e-8, 3e-8));
1172 let so3 = small_tangent.exp(None);
1173 let recovered = so3.log(None);
1174 assert!((small_tangent.data - recovered.data).norm() < TOLERANCE);
1175 }
1176
1177 #[test]
1178 fn test_so3_specific_rotations() {
1179 let so3_x = SO3::from_axis_angle(&Vector3::x(), PI / 2.0);
1181 let point_y = Vector3::y();
1182 let rotated = so3_x.act(&point_y, None, None);
1183 assert!((rotated - Vector3::z()).norm() < TOLERANCE);
1184
1185 let so3_z = SO3::from_axis_angle(&Vector3::z(), PI / 2.0);
1187 let point_x = Vector3::x();
1188 let rotated = so3_z.act(&point_x, None, None);
1189 assert!((rotated - Vector3::y()).norm() < TOLERANCE);
1190 }
1191
1192 #[test]
1193 fn test_so3_from_components() {
1194 let so3 = SO3::from_quaternion_coeffs(0.0, 0.0, 0.0, 1.0);
1195 assert_eq!(so3.x(), 0.0);
1196 assert_eq!(so3.y(), 0.0);
1197 assert_eq!(so3.z(), 0.0);
1198 assert_eq!(so3.w(), 1.0);
1199 }
1200
1201 #[test]
1202 fn test_so3_tangent_from_components() {
1203 let tangent = SO3Tangent::from_components(1.0, 2.0, 3.0);
1204 assert_eq!(tangent.x(), 1.0);
1205 assert_eq!(tangent.y(), 2.0);
1206 assert_eq!(tangent.z(), 3.0);
1207 }
1208
1209 #[test]
1210 fn test_so3_consistency_with_manif() {
1211 let so3_1 = SO3::random();
1213 let so3_2 = SO3::random();
1214
1215 let so3_3 = SO3::random();
1217 let left_assoc = so3_1
1218 .compose(&so3_2, None, None)
1219 .compose(&so3_3, None, None);
1220 let right_assoc = so3_1.compose(&so3_2.compose(&so3_3, None, None), None, None);
1221
1222 assert!(left_assoc.distance(&right_assoc) < 1e-10);
1223 }
1224
1225 #[test]
1226 fn test_so3_tangent_accessors() {
1227 let tangent = SO3Tangent::new(Vector3::new(1.0, 2.0, 3.0));
1228 assert_eq!(tangent.x(), 1.0);
1229 assert_eq!(tangent.y(), 2.0);
1230 assert_eq!(tangent.z(), 3.0);
1231
1232 let coeffs = tangent.coeffs();
1233 assert_eq!(coeffs, Vector3::new(1.0, 2.0, 3.0));
1234 }
1235
1236 #[test]
1237 fn test_so3_between() {
1238 let so3_1 = SO3::random();
1239 let so3_2 = SO3::random();
1240 let between = so3_1.between(&so3_2, None, None);
1241
1242 let result = so3_1.compose(&between, None, None);
1244 assert!(result.distance(&so3_2) < TOLERANCE);
1245 }
1246
1247 #[test]
1248 fn test_so3_distance() {
1249 let so3_1 = SO3::random();
1250 let so3_2 = SO3::random();
1251 let distance = so3_1.distance(&so3_2);
1252 assert!(distance >= 0.0);
1253 assert!(so3_1.distance(&so3_1) < TOLERANCE);
1254 }
1255
1256 #[test]
1259 fn test_so3_vee() {
1260 let so3 = SO3::random();
1261 let tangent_log = so3.log(None);
1262 let tangent_vee = so3.vee();
1263
1264 assert!((tangent_log.data - tangent_vee.data).norm() < 1e-10);
1265 }
1266
1267 #[test]
1268 fn test_so3_is_approx() {
1269 let so3_1 = SO3::random();
1270 let so3_2 = so3_1.clone();
1271
1272 assert!(so3_1.is_approx(&so3_1, 1e-10));
1273 assert!(so3_1.is_approx(&so3_2, 1e-10));
1274
1275 let small_tangent = SO3Tangent::new(Vector3::new(1e-12, 1e-12, 1e-12));
1277 let so3_perturbed = so3_1.right_plus(&small_tangent, None, None);
1278 assert!(so3_1.is_approx(&so3_perturbed, 1e-10));
1279 }
1280
1281 #[test]
1282 fn test_so3_tangent_small_adj() {
1283 let axis_angle = Vector3::new(0.1, 0.2, 0.3);
1284 let tangent = SO3Tangent::new(axis_angle);
1285 let small_adj = tangent.small_adj();
1286 let hat_matrix = tangent.hat();
1287
1288 assert!((small_adj - hat_matrix).norm() < 1e-10);
1290 }
1291
1292 #[test]
1293 fn test_so3_tangent_lie_bracket() {
1294 let tangent_a = SO3Tangent::new(Vector3::new(0.1, 0.0, 0.0));
1295 let tangent_b = SO3Tangent::new(Vector3::new(0.0, 0.2, 0.0));
1296
1297 let bracket_ab = tangent_a.lie_bracket(&tangent_b);
1298 let bracket_ba = tangent_b.lie_bracket(&tangent_a);
1299
1300 assert!((bracket_ab.data + bracket_ba.data).norm() < 1e-10);
1302
1303 let bracket_aa = tangent_a.lie_bracket(&tangent_a);
1305 assert!(bracket_aa.is_zero(1e-10));
1306
1307 let bracket_hat = bracket_ab.hat();
1309 let expected = tangent_a.hat() * tangent_b.hat() - tangent_b.hat() * tangent_a.hat();
1310 assert!((bracket_hat - expected).norm() < 1e-10);
1311 }
1312
1313 #[test]
1314 fn test_so3_tangent_is_approx() {
1315 let tangent_1 = SO3Tangent::new(Vector3::new(0.1, 0.2, 0.3));
1316 let tangent_2 = SO3Tangent::new(Vector3::new(0.1 + 1e-12, 0.2, 0.3));
1317 let tangent_3 = SO3Tangent::new(Vector3::new(0.5, 0.6, 0.7));
1318
1319 assert!(tangent_1.is_approx(&tangent_1, 1e-10));
1320 assert!(tangent_1.is_approx(&tangent_2, 1e-10));
1321 assert!(!tangent_1.is_approx(&tangent_3, 1e-10));
1322 }
1323
1324 #[test]
1325 fn test_so3_generators() {
1326 let tangent = SO3Tangent::new(Vector3::new(1.0, 1.0, 1.0));
1327
1328 for i in 0..3 {
1330 let generator = tangent.generator(i);
1331
1332 assert!((generator + generator.transpose()).norm() < 1e-10);
1334
1335 assert!(generator.trace().abs() < 1e-10);
1337 }
1338
1339 let e1 = tangent.generator(0);
1341 let e2 = tangent.generator(1);
1342 let e3 = tangent.generator(2);
1343
1344 let expected_e1 = Matrix3::new(0.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 1.0, 0.0);
1346 let expected_e2 = Matrix3::new(0.0, 0.0, 1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0);
1347 let expected_e3 = Matrix3::new(0.0, -1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0);
1348
1349 assert!((e1 - expected_e1).norm() < 1e-10);
1350 assert!((e2 - expected_e2).norm() < 1e-10);
1351 assert!((e3 - expected_e3).norm() < 1e-10);
1352 }
1353
1354 #[test]
1355 #[should_panic]
1356 fn test_so3_generator_invalid_index() {
1357 let tangent = SO3Tangent::new(Vector3::new(1.0, 1.0, 1.0));
1358 let _generator = tangent.generator(3); }
1360
1361 #[test]
1362 fn test_so3_jacobi_identity() {
1363 let x = SO3Tangent::new(Vector3::new(0.1, 0.0, 0.0));
1365 let y = SO3Tangent::new(Vector3::new(0.0, 0.2, 0.0));
1366 let z = SO3Tangent::new(Vector3::new(0.0, 0.0, 0.3));
1367
1368 let term1 = x.lie_bracket(&y.lie_bracket(&z));
1369 let term2 = y.lie_bracket(&z.lie_bracket(&x));
1370 let term3 = z.lie_bracket(&x.lie_bracket(&y));
1371
1372 let jacobi_sum = SO3Tangent::new(term1.data + term2.data + term3.data);
1373 assert!(jacobi_sum.is_zero(1e-10));
1374 }
1375
1376 #[test]
1379 fn test_so3_right_jacobian_numerical() {
1380 let xi = Vector3::new(0.1, 0.2, 0.3);
1385 let tangent = SO3Tangent::new(xi);
1386 let jr_analytical = tangent.right_jacobian();
1387
1388 let delta_size = 1e-6;
1390 let test_deltas = vec![
1391 Vector3::new(delta_size, 0.0, 0.0),
1392 Vector3::new(0.0, delta_size, 0.0),
1393 Vector3::new(0.0, 0.0, delta_size),
1394 ];
1395
1396 for delta in test_deltas {
1397 let analytical_result = jr_analytical * delta;
1399
1400 let base = SO3Tangent::new(xi).exp(None);
1402 let perturbed = SO3Tangent::new(xi + delta).exp(None);
1403 let base_inv = base.inverse(None);
1404 let diff_element = base_inv.compose(&perturbed, None, None);
1405 let diff_log = diff_element.log(None);
1406 let numerical_result = Vector3::new(diff_log.x(), diff_log.y(), diff_log.z());
1407
1408 let error = (analytical_result - numerical_result).norm();
1409 assert!(
1410 error < 1e-7,
1411 "Right Jacobian verification failed: error = {}, delta = {:?}",
1412 error,
1413 delta
1414 );
1415 }
1416 }
1417
1418 #[test]
1419 fn test_so3_left_jacobian_numerical() {
1420 let xi = Vector3::new(0.1, 0.2, 0.3);
1425 let tangent = SO3Tangent::new(xi);
1426 let jl_analytical = tangent.left_jacobian();
1427
1428 let delta_size = 1e-6;
1430 let test_deltas = vec![
1431 Vector3::new(delta_size, 0.0, 0.0),
1432 Vector3::new(0.0, delta_size, 0.0),
1433 Vector3::new(0.0, 0.0, delta_size),
1434 ];
1435
1436 for delta in test_deltas {
1437 let analytical_result = jl_analytical * delta;
1439
1440 let base = SO3Tangent::new(xi).exp(None);
1442 let perturbed = SO3Tangent::new(xi + delta).exp(None);
1443 let base_inv = base.inverse(None);
1444 let diff_element = perturbed.compose(&base_inv, None, None);
1445 let diff_log = diff_element.log(None);
1446 let numerical_result = Vector3::new(diff_log.x(), diff_log.y(), diff_log.z());
1447
1448 let error = (analytical_result - numerical_result).norm();
1449 assert!(
1450 error < 1e-7,
1451 "Left Jacobian verification failed: error = {}, delta = {:?}",
1452 error,
1453 delta
1454 );
1455 }
1456 }
1457
1458 #[test]
1473 fn test_so3_accumulated_error_small_rotations() {
1474 let small_rotation = SO3::from_scaled_axis(Vector3::new(0.001, 0.002, -0.001));
1476 let mut accumulated = SO3::identity();
1477
1478 for _ in 0..1000 {
1479 accumulated = accumulated.compose(&small_rotation, None, None);
1480 }
1481
1482 let expected_tangent = SO3Tangent::new(Vector3::new(1.0, 2.0, -1.0));
1484 let expected = expected_tangent.exp(None);
1485
1486 assert!(accumulated.is_approx(&expected, 1e-6));
1488 }
1489
1490 #[test]
1491 fn test_so3_inverse_composition_chain() {
1492 let rotation = SO3::from_euler_angles(0.1, 0.2, 0.3);
1494 let inverse = rotation.inverse(None);
1495 let mut accumulated = SO3::identity();
1496
1497 for _ in 0..500 {
1498 accumulated = accumulated.compose(&rotation, None, None);
1499 accumulated = accumulated.compose(&inverse, None, None);
1500 }
1501
1502 assert!(accumulated.is_approx(&SO3::identity(), 1e-9));
1504 }
1505
1506 #[test]
1509 fn test_so3_antipodal_quaternions() {
1510 let q = UnitQuaternion::from_euler_angles(0.5, 0.3, 0.2);
1512 let so3_pos = SO3::new(q);
1513 let so3_neg = SO3::new(UnitQuaternion::from_quaternion(-q.quaternion()));
1514
1515 let log_pos = so3_pos.log(None);
1517 let log_neg = so3_neg.log(None);
1518
1519 let diff_norm = (log_pos.data - log_neg.data).norm();
1521 let sum_norm = (log_pos.data + log_neg.data).norm();
1522 assert!(diff_norm < 1e-10 || sum_norm < 1e-10);
1523 }
1524
1525 #[test]
1526 fn test_so3_near_pi_rotation() {
1527 let axis = Vector3::new(1.0, 0.0, 0.0).normalize();
1529 let angle = std::f64::consts::PI - 1e-8;
1530 let so3 = SO3::from_axis_angle(&axis, angle);
1531
1532 let tangent = so3.log(None);
1533 let recovered = tangent.exp(None);
1534
1535 assert!(so3.is_approx(&recovered, 1e-6));
1536 }
1537
1538 #[test]
1553 fn test_so3_right_jacobian_inverse_identity() {
1554 let tangent = SO3Tangent::new(Vector3::new(0.001, 0.002, 0.003));
1556 let jr = tangent.right_jacobian();
1557 let jr_inv = tangent.right_jacobian_inv();
1558 let product = jr * jr_inv;
1559 let identity = Matrix3::identity();
1560
1561 assert!(
1563 (product - identity).norm() < 0.01,
1564 "Jr * Jr_inv should be identity, got error: {}",
1565 (product - identity).norm()
1566 );
1567 }
1568
1569 #[test]
1570 fn test_so3_left_jacobian_inverse_identity() {
1571 let tangent = SO3Tangent::new(Vector3::new(0.001, 0.002, 0.003));
1573 let jl = tangent.left_jacobian();
1574 let jl_inv = tangent.left_jacobian_inv();
1575 let product = jl * jl_inv;
1576 let identity = Matrix3::identity();
1577
1578 assert!((product - identity).norm() < 0.01);
1580 }
1581
1582 #[test]
1583 fn test_so3_from_quaternion_wxyz() {
1584 let so3_wxyz = SO3::from_quaternion_wxyz(1.0, 0.0, 0.0, 0.0);
1586 let so3_xyzw = SO3::from_quaternion_coeffs(0.0, 0.0, 0.0, 1.0);
1587 assert!(so3_wxyz.is_approx(&so3_xyzw, 1e-12));
1588
1589 let so3_wxyz = SO3::from_quaternion_wxyz(0.4, 0.1, 0.2, 0.3);
1591 let so3_xyzw = SO3::from_quaternion_coeffs(0.1, 0.2, 0.3, 0.4);
1592 assert!(so3_wxyz.is_approx(&so3_xyzw, 1e-12));
1593 }
1594
1595 #[test]
1596 fn test_so3_tangent_is_not_dynamic() {
1597 assert!(!SO3Tangent::is_dynamic());
1598 assert_eq!(SO3Tangent::DIM, 3);
1599 }
1600
1601 #[test]
1602 fn test_so3_small_angle_threshold_exp_log() {
1603 let near_threshold_angles = [1e-6, 1e-5, 1e-4, 1e-3];
1606
1607 for &angle in &near_threshold_angles {
1608 let tangent = SO3Tangent::new(Vector3::new(angle, 0.0, 0.0));
1609 let so3 = tangent.exp(None);
1610 let recovered = so3.log(None);
1611 assert!(
1612 (tangent.data - recovered.data).norm() < 1e-10,
1613 "SO3 exp-log round-trip failed for angle = {angle}"
1614 );
1615 }
1616 }
1617}