1use crate::{LieGroup, Tangent};
36use nalgebra::{Matrix3, Matrix4, Quaternion, SVector, Unit, UnitQuaternion, Vector3};
37use std::{
38 fmt,
39 fmt::{Display, Formatter},
40};
41
42#[derive(Clone, PartialEq)]
48pub struct SO3 {
49 params: SVector<f64, 4>,
51}
52
53impl Display for SO3 {
54 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
55 write!(
56 f,
57 "SO3(quaternion: [w: {:.4}, x: {:.4}, y: {:.4}, z: {:.4}])",
58 self.params[0], self.params[1], self.params[2], self.params[3]
59 )
60 }
61}
62
63impl SO3 {
64 pub const DIM: usize = 3;
66
67 pub const DOF: usize = 3;
69
70 pub const REP_SIZE: usize = 4;
72
73 pub fn identity() -> Self {
77 SO3 {
78 params: SVector::<f64, 4>::new(1.0, 0.0, 0.0, 0.0),
79 }
80 }
81
82 pub fn jacobian_identity() -> Matrix3<f64> {
86 Matrix3::<f64>::identity()
87 }
88
89 #[inline]
94 pub fn new(quaternion: UnitQuaternion<f64>) -> Self {
95 Self::from_unit_quaternion(quaternion)
96 }
97
98 #[inline]
100 fn unit_quaternion(&self) -> UnitQuaternion<f64> {
101 UnitQuaternion::from_quaternion(Quaternion::new(
102 self.params[0],
103 self.params[1],
104 self.params[2],
105 self.params[3],
106 ))
107 }
108
109 #[inline]
111 fn from_unit_quaternion(q: UnitQuaternion<f64>) -> Self {
112 let r = q.quaternion();
113 SO3 {
114 params: SVector::<f64, 4>::new(r.w, r.i, r.j, r.k),
115 }
116 }
117
118 pub fn from_quaternion_coeffs(x: f64, y: f64, z: f64, w: f64) -> Self {
130 let q = UnitQuaternion::from_quaternion(Quaternion::new(w, x, y, z));
131 Self::from_unit_quaternion(q)
132 }
133
134 pub fn from_quaternion_wxyz(w: f64, x: f64, y: f64, z: f64) -> Self {
146 let q = UnitQuaternion::from_quaternion(Quaternion::new(w, x, y, z));
147 Self::from_unit_quaternion(q)
148 }
149
150 pub fn from_euler_angles(roll: f64, pitch: f64, yaw: f64) -> Self {
152 Self::from_unit_quaternion(UnitQuaternion::from_euler_angles(roll, pitch, yaw))
153 }
154
155 pub fn from_axis_angle(axis: &Vector3<f64>, angle: f64) -> Self {
157 let unit_axis = Unit::new_normalize(*axis);
158 Self::from_unit_quaternion(UnitQuaternion::from_axis_angle(&unit_axis, angle))
159 }
160
161 pub fn from_scaled_axis(axis_angle: Vector3<f64>) -> Self {
163 Self::from_unit_quaternion(UnitQuaternion::from_scaled_axis(axis_angle))
164 }
165
166 pub fn quaternion(&self) -> UnitQuaternion<f64> {
168 self.unit_quaternion()
169 }
170
171 pub fn from_quaternion(quaternion: UnitQuaternion<f64>) -> Self {
173 Self::from_unit_quaternion(quaternion)
174 }
175
176 pub fn to_quaternion(&self) -> UnitQuaternion<f64> {
178 self.unit_quaternion()
179 }
180
181 pub fn quat(&self) -> Quaternion<f64> {
183 *self.unit_quaternion().quaternion()
184 }
185
186 #[inline]
188 pub fn x(&self) -> f64 {
189 self.params[1]
190 }
191
192 #[inline]
194 pub fn y(&self) -> f64 {
195 self.params[2]
196 }
197
198 #[inline]
200 pub fn z(&self) -> f64 {
201 self.params[3]
202 }
203
204 #[inline]
206 pub fn w(&self) -> f64 {
207 self.params[0]
208 }
209
210 pub fn rotation_matrix(&self) -> Matrix3<f64> {
212 self.unit_quaternion().to_rotation_matrix().into_inner()
213 }
214
215 pub fn transform(&self) -> Matrix4<f64> {
217 self.unit_quaternion().to_homogeneous()
218 }
219
220 pub fn set_quaternion(&mut self, coeffs: &[f64; 4]) {
222 let q = UnitQuaternion::from_quaternion(Quaternion::new(
223 coeffs[0], coeffs[1], coeffs[2], coeffs[3],
224 ));
225 let r = q.quaternion();
226 self.params = SVector::<f64, 4>::new(r.w, r.i, r.j, r.k);
227 }
228
229 #[inline]
231 pub fn coeffs(&self) -> [f64; 4] {
232 [
233 self.params[0],
234 self.params[1],
235 self.params[2],
236 self.params[3],
237 ]
238 }
239
240 #[inline]
242 pub fn params(&self) -> &SVector<f64, 4> {
243 &self.params
244 }
245
246 pub fn distance(&self, other: &Self) -> f64 {
251 self.between(other, None, None).log(None).angle()
252 }
253}
254
255impl LieGroup for SO3 {
257 const NAME: &'static str = "SO3";
258
259 type TangentVector = SO3Tangent;
260 type JacobianMatrix = Matrix3<f64>;
261 type LieAlgebra = Matrix3<f64>;
262
263 fn inverse(&self, jacobian: Option<&mut Self::JacobianMatrix>) -> Self {
275 let inverse_quat = self.unit_quaternion().inverse();
276
277 if let Some(jac) = jacobian {
278 *jac = -self.adjoint();
279 }
280
281 Self::from_unit_quaternion(inverse_quat)
282 }
283
284 fn compose(
299 &self,
300 other: &Self,
301 jacobian_self: Option<&mut Self::JacobianMatrix>,
302 jacobian_other: Option<&mut Self::JacobianMatrix>,
303 ) -> Self {
304 let result = Self::from_unit_quaternion(self.unit_quaternion() * other.unit_quaternion());
305
306 if let Some(jac_self) = jacobian_self {
307 *jac_self = other.inverse(None).adjoint();
308 }
309
310 if let Some(jac_other) = jacobian_other {
311 *jac_other = Matrix3::identity();
312 }
313
314 result
315 }
316
317 fn log(&self, jacobian: Option<&mut Self::JacobianMatrix>) -> Self::TangentVector {
330 let uq = self.unit_quaternion();
331 debug_assert!(
332 (uq.norm() - 1.0).abs() < 1e-6,
333 "SO3::log() requires normalized quaternion, norm = {}",
334 uq.norm()
335 );
336
337 let q = uq.quaternion();
338 let sin_angle_squared = q.i * q.i + q.j * q.j + q.k * q.k;
339
340 let log_coeff = if sin_angle_squared > crate::SMALL_ANGLE_THRESHOLD {
341 let sin_angle = sin_angle_squared.sqrt();
342 let cos_angle = q.w;
343
344 let two_angle = 2.0
352 * if cos_angle < 0.0 {
353 f64::atan2(-sin_angle, -cos_angle)
354 } else {
355 f64::atan2(sin_angle, cos_angle)
356 };
357
358 two_angle / sin_angle
359 } else {
360 2.0
361 };
362
363 let axis_angle = SO3Tangent::new(Vector3::new(
364 q.i * log_coeff,
365 q.j * log_coeff,
366 q.k * log_coeff,
367 ));
368
369 if let Some(jac) = jacobian {
370 *jac = axis_angle.right_jacobian_inv();
371 }
372
373 axis_angle
374 }
375
376 fn act(
377 &self,
378 vector: &Vector3<f64>,
379 jacobian_self: Option<&mut Self::JacobianMatrix>,
380 jacobian_vector: Option<&mut Matrix3<f64>>,
381 ) -> Vector3<f64> {
382 let result = self.unit_quaternion() * vector;
383
384 if let Some(jac_self) = jacobian_self {
385 let vector_hat = SO3Tangent::new(*vector).hat();
387 *jac_self = -self.rotation_matrix() * vector_hat;
388 }
389
390 if let Some(jac_vector) = jacobian_vector {
391 *jac_vector = self.rotation_matrix();
392 }
393
394 result
395 }
396
397 fn adjoint(&self) -> Self::JacobianMatrix {
398 self.rotation_matrix()
399 }
400
401 fn random() -> Self {
402 Self::from_unit_quaternion(UnitQuaternion::from_scaled_axis(Vector3::new(
403 rand::random::<f64>() * 2.0 - 1.0,
404 rand::random::<f64>() * 2.0 - 1.0,
405 rand::random::<f64>() * 2.0 - 1.0,
406 )))
407 }
408
409 fn jacobian_identity() -> Self::JacobianMatrix {
410 Matrix3::<f64>::identity()
411 }
412
413 fn zero_jacobian() -> Self::JacobianMatrix {
414 Matrix3::<f64>::zeros()
415 }
416
417 fn normalize(&mut self) {
418 let q = UnitQuaternion::from_quaternion(
419 Quaternion::new(
420 self.params[0],
421 self.params[1],
422 self.params[2],
423 self.params[3],
424 )
425 .normalize(),
426 );
427 let r = q.quaternion();
428 self.params = SVector::<f64, 4>::new(r.w, r.i, r.j, r.k);
429 }
430
431 fn is_valid(&self, tolerance: f64) -> bool {
432 let norm_sq = self.params[0] * self.params[0]
433 + self.params[1] * self.params[1]
434 + self.params[2] * self.params[2]
435 + self.params[3] * self.params[3];
436 (norm_sq.sqrt() - 1.0).abs() < tolerance
437 }
438
439 fn as_param_slice(&self) -> &[f64] {
440 self.params.as_slice()
441 }
442
443 fn as_param_slice_mut(&mut self) -> &mut [f64] {
444 self.params.as_mut_slice()
445 }
446
447 fn from_param_slice(s: &[f64]) -> Self {
448 debug_assert_eq!(s.len(), 4);
449 SO3 {
450 params: SVector::from_column_slice(s),
451 }
452 }
453
454 fn vee(&self) -> Self::TangentVector {
459 self.log(None)
460 }
461
462 fn is_approx(&self, other: &Self, tolerance: f64) -> bool {
468 let difference = self.right_minus(other, None, None);
469 difference.is_zero(tolerance)
470 }
471}
472
473#[derive(Clone, PartialEq)]
479pub struct SO3Tangent {
480 data: Vector3<f64>,
482}
483
484impl fmt::Display for SO3Tangent {
485 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
486 write!(
487 f,
488 "so3(axis-angle: [{:.4}, {:.4}, {:.4}])",
489 self.data.x, self.data.y, self.data.z
490 )
491 }
492}
493
494impl SO3Tangent {
495 #[inline]
500 pub fn new(axis_angle: Vector3<f64>) -> Self {
501 SO3Tangent { data: axis_angle }
502 }
503
504 pub fn from_components(x: f64, y: f64, z: f64) -> Self {
506 SO3Tangent::new(Vector3::new(x, y, z))
507 }
508
509 #[inline]
511 pub fn axis_angle(&self) -> Vector3<f64> {
512 self.data
513 }
514
515 #[inline]
517 pub fn angle(&self) -> f64 {
518 self.data.norm()
519 }
520
521 #[inline]
523 pub fn axis(&self) -> Vector3<f64> {
524 let norm = self.data.norm();
525 if norm < f64::EPSILON {
526 Vector3::identity()
527 } else {
528 self.data / norm
529 }
530 }
531
532 #[inline]
534 pub fn x(&self) -> f64 {
535 self.data.x
536 }
537
538 #[inline]
540 pub fn y(&self) -> f64 {
541 self.data.y
542 }
543
544 #[inline]
546 pub fn z(&self) -> f64 {
547 self.data.z
548 }
549
550 #[inline]
552 pub fn coeffs(&self) -> Vector3<f64> {
553 self.data
554 }
555
556 pub fn ang(&self) -> Vector3<f64> {
558 self.data
559 }
560}
561
562impl Tangent<SO3> for SO3Tangent {
564 const DIM: usize = 3;
566
567 fn exp(&self, jacobian: Option<&mut <SO3 as LieGroup>::JacobianMatrix>) -> SO3 {
581 let theta_squared = self.data.norm_squared();
582
583 let quaternion = if theta_squared > crate::SMALL_ANGLE_THRESHOLD {
584 UnitQuaternion::from_scaled_axis(self.data)
585 } else {
586 UnitQuaternion::from_quaternion(Quaternion::new(
587 1.0,
588 self.data.x / 2.0,
589 self.data.y / 2.0,
590 self.data.z / 2.0,
591 ))
592 };
593
594 if let Some(jac) = jacobian {
595 *jac = self.right_jacobian();
596 }
597
598 SO3::from_unit_quaternion(quaternion)
599 }
600
601 fn right_jacobian(&self) -> <SO3 as LieGroup>::JacobianMatrix {
608 self.left_jacobian().transpose()
609 }
610
611 fn left_jacobian(&self) -> <SO3 as LieGroup>::JacobianMatrix {
618 let angle = self.data.norm_squared();
619 let tangent_skew = self.hat();
620
621 if angle <= crate::SMALL_ANGLE_THRESHOLD {
622 Matrix3::identity() + 0.5 * tangent_skew
623 } else {
624 let theta = angle.sqrt();
625 let sin_theta = theta.sin();
626 let cos_theta = theta.cos();
627
628 Matrix3::identity()
629 + (1.0 - cos_theta) / angle * tangent_skew
630 + (theta - sin_theta) / (angle * theta) * tangent_skew * tangent_skew
631 }
632 }
633
634 fn right_jacobian_inv(&self) -> <SO3 as LieGroup>::JacobianMatrix {
641 self.left_jacobian_inv().transpose()
642 }
643
644 fn left_jacobian_inv(&self) -> <SO3 as LieGroup>::JacobianMatrix {
651 let angle = self.data.norm_squared();
652 let tangent_skew = self.hat();
653
654 if angle <= crate::SMALL_ANGLE_THRESHOLD {
655 Matrix3::identity() - 0.5 * tangent_skew
656 } else {
657 let theta = angle.sqrt();
658 let sin_theta = theta.sin();
659 let cos_theta = theta.cos();
660
661 Matrix3::identity() - (0.5 * tangent_skew)
662 + (1.0 / angle - (1.0 + cos_theta) / (2.0 * theta * sin_theta))
663 * tangent_skew
664 * tangent_skew
665 }
666 }
667
668 fn hat(&self) -> <SO3 as LieGroup>::LieAlgebra {
674 Matrix3::new(
675 0.0,
676 -self.data.z,
677 self.data.y,
678 self.data.z,
679 0.0,
680 -self.data.x,
681 -self.data.y,
682 self.data.x,
683 0.0,
684 )
685 }
686
687 fn zero() -> <SO3 as LieGroup>::TangentVector {
688 Self::new(Vector3::zeros())
689 }
690
691 fn random() -> <SO3 as LieGroup>::TangentVector {
692 Self::new(Vector3::new(
693 rand::random::<f64>() * 0.2 - 0.1,
694 rand::random::<f64>() * 0.2 - 0.1,
695 rand::random::<f64>() * 0.2 - 0.1,
696 ))
697 }
698
699 fn is_zero(&self, tolerance: f64) -> bool {
700 self.data.norm() < tolerance
701 }
702
703 fn normalize(&mut self) {
704 let norm = self.data.norm();
705 if norm > f64::EPSILON {
706 self.data /= norm;
707 }
708 }
709
710 fn normalized(&self) -> <SO3 as LieGroup>::TangentVector {
711 let norm = self.data.norm();
712 if norm > f64::EPSILON {
713 SO3Tangent::new(self.data / norm)
714 } else {
715 Self::zero()
716 }
717 }
718
719 fn as_slice(&self) -> &[f64] {
720 self.data.as_slice()
721 }
722
723 fn from_slice(s: &[f64]) -> Self {
724 debug_assert_eq!(s.len(), 3);
725 SO3Tangent {
726 data: Vector3::from_column_slice(s),
727 }
728 }
729
730 fn small_adj(&self) -> <SO3 as LieGroup>::JacobianMatrix {
734 self.hat()
735 }
736
737 fn lie_bracket(&self, other: &Self) -> <SO3 as LieGroup>::TangentVector {
741 let bracket_result = self.small_adj() * other.data;
742 SO3Tangent::new(bracket_result)
743 }
744
745 fn is_approx(&self, other: &Self, tolerance: f64) -> bool {
751 (self.data - other.data).norm() < tolerance
752 }
753
754 fn generator(&self, i: usize) -> <SO3 as LieGroup>::LieAlgebra {
762 assert!(i < 3, "SO(3) only has generators for indices 0, 1, 2");
763
764 match i {
765 0 => {
766 Matrix3::new(0.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 1.0, 0.0)
768 }
769 1 => {
770 Matrix3::new(0.0, 0.0, 1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0)
772 }
773 2 => {
774 Matrix3::new(0.0, -1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0)
776 }
777 _ => unreachable!(),
778 }
779 }
780}
781
782#[cfg(test)]
783mod tests {
784 use super::*;
785 use core::f64;
786 use std::f64::consts::PI;
787
788 const TOLERANCE: f64 = 1e-12;
789
790 #[test]
791 fn test_so3_constructor_datatype() {
792 let so3 = SO3::from_quaternion_coeffs(0.0, 0.0, 0.0, 1.0);
793 assert_eq!(0.0, so3.x());
794 assert_eq!(0.0, so3.y());
795 assert_eq!(0.0, so3.z());
796 assert_eq!(1.0, so3.w());
797 }
798
799 #[test]
800 fn test_so3_constructor_quat() {
801 let quat = UnitQuaternion::identity();
802 let so3 = SO3::new(quat);
803 assert_eq!(0.0, so3.x());
804 assert_eq!(0.0, so3.y());
805 assert_eq!(0.0, so3.z());
806 assert_eq!(1.0, so3.w());
807 }
808
809 #[test]
810 fn test_so3_constructor_euler() {
811 let so3 = SO3::from_euler_angles(0.0, 0.0, 0.0);
812 assert_eq!(0.0, so3.x());
813 assert_eq!(0.0, so3.y());
814 assert_eq!(0.0, so3.z());
815 assert_eq!(1.0, so3.w());
816 }
817
818 #[test]
819 fn test_so3_identity() {
820 let so3 = SO3::identity();
821 assert_eq!(0.0, so3.x());
822 assert_eq!(0.0, so3.y());
823 assert_eq!(0.0, so3.z());
824 assert_eq!(1.0, so3.w());
825 }
826
827 #[test]
828 fn test_so3_coeffs() {
829 let so3 = SO3::from_quaternion_coeffs(0.0, 0.0, 0.0, 1.0);
831 let coeffs = so3.coeffs();
832 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);
839 let coeffs = so3.coeffs();
840 let original_quat = Quaternion::new(0.4, 0.1, 0.2, 0.3);
841 let normalized_quat = original_quat.normalize();
842 assert!((coeffs[0] - normalized_quat.w).abs() < TOLERANCE);
843 assert!((coeffs[1] - normalized_quat.i).abs() < TOLERANCE);
844 assert!((coeffs[2] - normalized_quat.j).abs() < TOLERANCE);
845 assert!((coeffs[3] - normalized_quat.k).abs() < TOLERANCE);
846 }
847
848 #[test]
849 fn test_so3_random() {
850 let so3 = SO3::random();
851 assert!((so3.quaternion().norm() - 1.0).abs() < TOLERANCE);
852 }
853
854 #[test]
855 fn test_so3_transform() {
856 let so3 = SO3::identity();
857 let transform = so3.transform();
858
859 assert_eq!(4, transform.nrows());
860 assert_eq!(4, transform.ncols());
861
862 for i in 0..4 {
864 for j in 0..4 {
865 if i == j {
866 assert!((transform[(i, j)] - 1.0).abs() < TOLERANCE);
867 } else {
868 assert!(transform[(i, j)].abs() < TOLERANCE);
869 }
870 }
871 }
872 }
873
874 #[test]
875 fn test_so3_rotation() {
876 let so3 = SO3::identity();
877 let rotation = so3.rotation_matrix();
878
879 assert_eq!(3, rotation.nrows());
880 assert_eq!(3, rotation.ncols());
881
882 for i in 0..3 {
884 for j in 0..3 {
885 if i == j {
886 assert!((rotation[(i, j)] - 1.0).abs() < TOLERANCE);
887 } else {
888 assert!(rotation[(i, j)].abs() < TOLERANCE);
889 }
890 }
891 }
892 }
893
894 #[test]
895 fn test_so3_inverse() {
896 let so3 = SO3::identity();
898 let so3_inv = so3.inverse(None);
899 assert_eq!(0.0, so3_inv.x());
900 assert_eq!(0.0, so3_inv.y());
901 assert_eq!(0.0, so3_inv.z());
902 assert_eq!(1.0, so3_inv.w());
903
904 let so3 = SO3::random();
906 let so3_inv = so3.inverse(None);
907 assert!((so3.x() + so3_inv.x()).abs() < TOLERANCE);
908 assert!((so3.y() + so3_inv.y()).abs() < TOLERANCE);
909 assert!((so3.z() + so3_inv.z()).abs() < TOLERANCE);
910 assert!((so3.w() - so3_inv.w()).abs() < TOLERANCE);
911 }
912
913 #[test]
914 fn test_so3_inverse_jacobian() {
915 let so3 = SO3::identity();
916 let mut jacobian = Matrix3::zeros();
917 let so3_inv = so3.inverse(Some(&mut jacobian));
918
919 assert_eq!(0.0, so3_inv.x());
921 assert_eq!(0.0, so3_inv.y());
922 assert_eq!(0.0, so3_inv.z());
923 assert_eq!(1.0, so3_inv.w());
924
925 let expected_jac = -Matrix3::identity();
927 assert!((jacobian - expected_jac).norm() < TOLERANCE);
928 }
929
930 #[test]
931 fn test_so3_rplus() {
932 let so3a = SO3::identity();
934 let so3b = SO3Tangent::new(Vector3::zeros());
935 let so3c = so3a.right_plus(&so3b, None, None);
936 assert_eq!(0.0, so3c.x());
937 assert_eq!(0.0, so3c.y());
938 assert_eq!(0.0, so3c.z());
939 assert_eq!(1.0, so3c.w());
940
941 let so3a = SO3::random();
943 let so3c = so3a.right_plus(&so3b, None, None);
944 assert!((so3a.x() - so3c.x()).abs() < TOLERANCE);
945 assert!((so3a.y() - so3c.y()).abs() < TOLERANCE);
946 assert!((so3a.z() - so3c.z()).abs() < TOLERANCE);
947 assert!((so3a.w() - so3c.w()).abs() < TOLERANCE);
948 }
949
950 #[test]
951 fn test_so3_lplus() {
952 let so3a = SO3::identity();
954 let so3t = SO3Tangent::new(Vector3::zeros());
955 let so3c = so3a.left_plus(&so3t, None, None);
956 assert_eq!(0.0, so3c.x());
957 assert_eq!(0.0, so3c.y());
958 assert_eq!(0.0, so3c.z());
959 assert_eq!(1.0, so3c.w());
960
961 let so3a = SO3::random();
963 let so3c = so3a.left_plus(&so3t, None, None);
964 assert!((so3a.x() - so3c.x()).abs() < TOLERANCE);
965 assert!((so3a.y() - so3c.y()).abs() < TOLERANCE);
966 assert!((so3a.z() - so3c.z()).abs() < TOLERANCE);
967 assert!((so3a.w() - so3c.w()).abs() < TOLERANCE);
968 }
969
970 #[test]
971 fn test_so3_rminus() {
972 let so3a = SO3::identity();
974 let so3b = SO3::identity();
975 let so3c = so3a.right_minus(&so3b, None, None);
976 assert!(so3c.x().abs() < TOLERANCE);
977 assert!(so3c.y().abs() < TOLERANCE);
978 assert!(so3c.z().abs() < TOLERANCE);
979
980 let so3a = SO3::random();
982 let so3b = so3a.clone();
983 let so3c = so3a.right_minus(&so3b, None, None);
984 assert!(so3c.data.norm() < TOLERANCE);
985 }
986
987 #[test]
988 fn test_so3_minus() {
989 let so3a = SO3::random();
991 let so3b = SO3::random();
992 let so3c = so3a.minus(&so3b, None, None);
993 let so3d = so3a.right_minus(&so3b, None, None);
994 assert!((so3c.data - so3d.data).norm() < TOLERANCE);
995 }
996
997 #[test]
998 fn test_so3_exp_log() {
999 let so3t = SO3Tangent::new(Vector3::zeros());
1001 let so3 = so3t.exp(None);
1002 assert_eq!(0.0, so3.x());
1003 assert_eq!(0.0, so3.y());
1004 assert_eq!(0.0, so3.z());
1005 assert_eq!(1.0, so3.w());
1006
1007 let so3t = SO3Tangent::new(Vector3::new(0.1, 0.2, 0.3));
1009 let so3 = so3t.exp(None);
1010 let so3n = SO3Tangent::new(Vector3::new(-0.1, -0.2, -0.3));
1011 let so3_inv = so3n.exp(None);
1012 assert!((so3_inv.x() + so3.x()).abs() < TOLERANCE);
1013 assert!((so3_inv.y() + so3.y()).abs() < TOLERANCE);
1014 assert!((so3_inv.z() + so3.z()).abs() < TOLERANCE);
1015 assert!((so3_inv.w() - so3.w()).abs() < TOLERANCE);
1016 }
1017
1018 #[test]
1019 fn test_so3_log() {
1020 let so3 = SO3::identity();
1022 let so3_log = so3.log(None);
1023 assert!(so3_log.x().abs() < TOLERANCE);
1024 assert!(so3_log.y().abs() < TOLERANCE);
1025 assert!(so3_log.z().abs() < TOLERANCE);
1026
1027 let so3 = SO3::random();
1029 let so3_log = so3.log(None);
1030 let so3_inv_log = so3.inverse(None).log(None);
1031 assert!((so3_inv_log.x() + so3_log.x()).abs() < TOLERANCE);
1032 assert!((so3_inv_log.y() + so3_log.y()).abs() < TOLERANCE);
1033 assert!((so3_inv_log.z() + so3_log.z()).abs() < TOLERANCE);
1034 }
1035
1036 #[test]
1037 fn test_so3_tangent_hat() {
1038 let so3_tan = SO3Tangent::new(Vector3::new(1.0, 2.0, 3.0));
1039 let so3_lie = so3_tan.hat();
1040
1041 assert!((so3_lie[(0, 0)] - 0.0).abs() < TOLERANCE);
1042 assert!((so3_lie[(0, 1)] + 3.0).abs() < TOLERANCE);
1043 assert!((so3_lie[(0, 2)] - 2.0).abs() < TOLERANCE);
1044 assert!((so3_lie[(1, 0)] - 3.0).abs() < TOLERANCE);
1045 assert!((so3_lie[(1, 1)] - 0.0).abs() < TOLERANCE);
1046 assert!((so3_lie[(1, 2)] + 1.0).abs() < TOLERANCE);
1047 assert!((so3_lie[(2, 0)] + 2.0).abs() < TOLERANCE);
1048 assert!((so3_lie[(2, 1)] - 1.0).abs() < TOLERANCE);
1049 assert!((so3_lie[(2, 2)] - 0.0).abs() < TOLERANCE);
1050 }
1051
1052 #[test]
1053 fn test_so3_act() {
1054 let so3 = SO3::identity();
1055 let transformed_point = so3.act(&Vector3::new(1.0, 1.0, 1.0), None, None);
1056 assert!((transformed_point.x - 1.0).abs() < TOLERANCE);
1057 assert!((transformed_point.y - 1.0).abs() < TOLERANCE);
1058 assert!((transformed_point.z - 1.0).abs() < TOLERANCE);
1059
1060 let so3 = SO3::from_euler_angles(PI, PI / 2.0, PI / 4.0);
1061 let transformed_point = so3.act(&Vector3::new(1.0, 1.0, 1.0), None, None);
1062 assert!((transformed_point.x - 0.0).abs() < TOLERANCE);
1063 assert!((transformed_point.y + f64::consts::SQRT_2).abs() < 1e-10);
1064 assert!((transformed_point.z + 1.0).abs() < TOLERANCE);
1065 }
1066
1067 #[test]
1068 fn test_so3_tangent_angular_velocity() {
1069 let so3tan = SO3Tangent::new(Vector3::new(1.0, 2.0, 3.0));
1070 let ang_vel = so3tan.ang();
1071 assert!((ang_vel - Vector3::new(1.0, 2.0, 3.0)).norm() < TOLERANCE);
1072 }
1073
1074 #[test]
1075 fn test_so3_compose() {
1076 let so3_1 = SO3::random();
1077 let so3_2 = SO3::random();
1078 let composed = so3_1.compose(&so3_2, None, None);
1079 assert!(composed.is_valid(TOLERANCE));
1080
1081 let identity = SO3::identity();
1083 let composed_with_identity = so3_1.compose(&identity, None, None);
1084 assert!((composed_with_identity.distance(&so3_1)).abs() < TOLERANCE);
1085 }
1086
1087 #[test]
1088 fn test_so3_exp_log_consistency() {
1089 let tangent = SO3Tangent::new(Vector3::new(0.1, 0.2, 0.3));
1090 let so3 = tangent.exp(None);
1091 let recovered_tangent = so3.log(None);
1092 assert!((tangent.data - recovered_tangent.data).norm() < TOLERANCE);
1093 }
1094
1095 #[test]
1096 fn test_so3_right_left_jacobian_relationship() {
1097 let tangent = SO3Tangent::new(Vector3::zeros());
1099 let ljac = tangent.left_jacobian();
1100 let rjac = tangent.right_jacobian();
1101 assert!((ljac - rjac).norm() < TOLERANCE);
1102 assert!((ljac - Matrix3::identity()).norm() < TOLERANCE);
1103
1104 let tangent = SO3Tangent::new(Vector3::new(0.1, 0.2, 0.3));
1106 let ljac = tangent.left_jacobian();
1107 let rjac = tangent.right_jacobian();
1108
1109 assert!((ljac - rjac.transpose()).norm() < TOLERANCE);
1112 assert!((rjac - ljac.transpose()).norm() < TOLERANCE);
1113 }
1114
1115 #[test]
1116 fn test_so3_manifold_properties() {
1117 assert_eq!(SO3::DIM, 3);
1118 assert_eq!(SO3::DOF, 3);
1119 assert_eq!(SO3::REP_SIZE, 4);
1120 }
1121
1122 #[test]
1123 fn test_so3_normalize() {
1124 let mut so3 = SO3::from_quaternion_coeffs(0.5, 0.5, 0.5, 0.5);
1125 so3.normalize();
1126 assert!(so3.is_valid(TOLERANCE));
1127 }
1128
1129 #[test]
1130 fn test_so3_tangent_norms() {
1131 let tangent = SO3Tangent::new(Vector3::new(3.0, 4.0, 0.0));
1132 let norm = tangent.data.norm();
1133 assert!((norm - 5.0).abs() < TOLERANCE);
1134
1135 let squared_norm = tangent.data.norm_squared();
1136 assert!((squared_norm - 25.0).abs() < TOLERANCE);
1137 }
1138
1139 #[test]
1140 fn test_so3_tangent_zero() {
1141 let zero = SO3Tangent::zero();
1142 assert!(zero.data.norm() < TOLERANCE);
1143
1144 let tangent = SO3Tangent::new(Vector3::zeros());
1145 assert!(tangent.is_zero(TOLERANCE));
1146 }
1147
1148 #[test]
1149 fn test_so3_tangent_normalize() {
1150 let mut tangent = SO3Tangent::new(Vector3::new(3.0, 4.0, 0.0));
1151 tangent.normalize();
1152 assert!((tangent.data.norm() - 1.0).abs() < TOLERANCE);
1153 }
1154
1155 #[test]
1156 fn test_so3_adjoint() {
1157 let so3 = SO3::random();
1158 let adj = so3.adjoint();
1159 assert_eq!(adj.nrows(), 3);
1160 assert_eq!(adj.ncols(), 3);
1161
1162 let det = adj.determinant();
1164 assert!((det - 1.0).abs() < TOLERANCE);
1165 }
1166
1167 #[test]
1168 fn test_so3_small_angle_approximations() {
1169 let small_tangent = SO3Tangent::new(Vector3::new(1e-8, 2e-8, 3e-8));
1170 let so3 = small_tangent.exp(None);
1171 let recovered = so3.log(None);
1172 assert!((small_tangent.data - recovered.data).norm() < TOLERANCE);
1173 }
1174
1175 #[test]
1176 fn test_so3_specific_rotations() {
1177 let so3_x = SO3::from_axis_angle(&Vector3::x(), PI / 2.0);
1179 let point_y = Vector3::y();
1180 let rotated = so3_x.act(&point_y, None, None);
1181 assert!((rotated - Vector3::z()).norm() < TOLERANCE);
1182
1183 let so3_z = SO3::from_axis_angle(&Vector3::z(), PI / 2.0);
1185 let point_x = Vector3::x();
1186 let rotated = so3_z.act(&point_x, None, None);
1187 assert!((rotated - Vector3::y()).norm() < TOLERANCE);
1188 }
1189
1190 #[test]
1191 fn test_so3_from_components() {
1192 let so3 = SO3::from_quaternion_coeffs(0.0, 0.0, 0.0, 1.0);
1193 assert_eq!(so3.x(), 0.0);
1194 assert_eq!(so3.y(), 0.0);
1195 assert_eq!(so3.z(), 0.0);
1196 assert_eq!(so3.w(), 1.0);
1197 }
1198
1199 #[test]
1200 fn test_so3_tangent_from_components() {
1201 let tangent = SO3Tangent::from_components(1.0, 2.0, 3.0);
1202 assert_eq!(tangent.x(), 1.0);
1203 assert_eq!(tangent.y(), 2.0);
1204 assert_eq!(tangent.z(), 3.0);
1205 }
1206
1207 #[test]
1208 fn test_so3_consistency_with_manif() {
1209 let so3_1 = SO3::random();
1211 let so3_2 = SO3::random();
1212
1213 let so3_3 = SO3::random();
1215 let left_assoc = so3_1
1216 .compose(&so3_2, None, None)
1217 .compose(&so3_3, None, None);
1218 let right_assoc = so3_1.compose(&so3_2.compose(&so3_3, None, None), None, None);
1219
1220 assert!(left_assoc.distance(&right_assoc) < 1e-10);
1221 }
1222
1223 #[test]
1224 fn test_so3_tangent_accessors() {
1225 let tangent = SO3Tangent::new(Vector3::new(1.0, 2.0, 3.0));
1226 assert_eq!(tangent.x(), 1.0);
1227 assert_eq!(tangent.y(), 2.0);
1228 assert_eq!(tangent.z(), 3.0);
1229
1230 let coeffs = tangent.coeffs();
1231 assert_eq!(coeffs, Vector3::new(1.0, 2.0, 3.0));
1232 }
1233
1234 #[test]
1235 fn test_so3_between() {
1236 let so3_1 = SO3::random();
1237 let so3_2 = SO3::random();
1238 let between = so3_1.between(&so3_2, None, None);
1239
1240 let result = so3_1.compose(&between, None, None);
1242 assert!(result.distance(&so3_2) < TOLERANCE);
1243 }
1244
1245 #[test]
1246 fn test_so3_distance() {
1247 let so3_1 = SO3::random();
1248 let so3_2 = SO3::random();
1249 let distance = so3_1.distance(&so3_2);
1250 assert!(distance >= 0.0);
1251 assert!(so3_1.distance(&so3_1) < TOLERANCE);
1252 }
1253
1254 #[test]
1257 fn test_so3_vee() {
1258 let so3 = SO3::random();
1259 let tangent_log = so3.log(None);
1260 let tangent_vee = so3.vee();
1261
1262 assert!((tangent_log.data - tangent_vee.data).norm() < 1e-10);
1263 }
1264
1265 #[test]
1266 fn test_so3_is_approx() {
1267 let so3_1 = SO3::random();
1268 let so3_2 = so3_1.clone();
1269
1270 assert!(so3_1.is_approx(&so3_1, 1e-10));
1271 assert!(so3_1.is_approx(&so3_2, 1e-10));
1272
1273 let small_tangent = SO3Tangent::new(Vector3::new(1e-12, 1e-12, 1e-12));
1275 let so3_perturbed = so3_1.right_plus(&small_tangent, None, None);
1276 assert!(so3_1.is_approx(&so3_perturbed, 1e-10));
1277 }
1278
1279 #[test]
1280 fn test_so3_tangent_small_adj() {
1281 let axis_angle = Vector3::new(0.1, 0.2, 0.3);
1282 let tangent = SO3Tangent::new(axis_angle);
1283 let small_adj = tangent.small_adj();
1284 let hat_matrix = tangent.hat();
1285
1286 assert!((small_adj - hat_matrix).norm() < 1e-10);
1288 }
1289
1290 #[test]
1291 fn test_so3_tangent_lie_bracket() {
1292 let tangent_a = SO3Tangent::new(Vector3::new(0.1, 0.0, 0.0));
1293 let tangent_b = SO3Tangent::new(Vector3::new(0.0, 0.2, 0.0));
1294
1295 let bracket_ab = tangent_a.lie_bracket(&tangent_b);
1296 let bracket_ba = tangent_b.lie_bracket(&tangent_a);
1297
1298 assert!((bracket_ab.data + bracket_ba.data).norm() < 1e-10);
1300
1301 let bracket_aa = tangent_a.lie_bracket(&tangent_a);
1303 assert!(bracket_aa.is_zero(1e-10));
1304
1305 let bracket_hat = bracket_ab.hat();
1307 let expected = tangent_a.hat() * tangent_b.hat() - tangent_b.hat() * tangent_a.hat();
1308 assert!((bracket_hat - expected).norm() < 1e-10);
1309 }
1310
1311 #[test]
1312 fn test_so3_tangent_is_approx() {
1313 let tangent_1 = SO3Tangent::new(Vector3::new(0.1, 0.2, 0.3));
1314 let tangent_2 = SO3Tangent::new(Vector3::new(0.1 + 1e-12, 0.2, 0.3));
1315 let tangent_3 = SO3Tangent::new(Vector3::new(0.5, 0.6, 0.7));
1316
1317 assert!(tangent_1.is_approx(&tangent_1, 1e-10));
1318 assert!(tangent_1.is_approx(&tangent_2, 1e-10));
1319 assert!(!tangent_1.is_approx(&tangent_3, 1e-10));
1320 }
1321
1322 #[test]
1323 fn test_so3_generators() {
1324 let tangent = SO3Tangent::new(Vector3::new(1.0, 1.0, 1.0));
1325
1326 for i in 0..3 {
1328 let generator = tangent.generator(i);
1329
1330 assert!((generator + generator.transpose()).norm() < 1e-10);
1332
1333 assert!(generator.trace().abs() < 1e-10);
1335 }
1336
1337 let e1 = tangent.generator(0);
1339 let e2 = tangent.generator(1);
1340 let e3 = tangent.generator(2);
1341
1342 let expected_e1 = Matrix3::new(0.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 1.0, 0.0);
1344 let expected_e2 = Matrix3::new(0.0, 0.0, 1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0);
1345 let expected_e3 = Matrix3::new(0.0, -1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0);
1346
1347 assert!((e1 - expected_e1).norm() < 1e-10);
1348 assert!((e2 - expected_e2).norm() < 1e-10);
1349 assert!((e3 - expected_e3).norm() < 1e-10);
1350 }
1351
1352 #[test]
1353 #[should_panic]
1354 fn test_so3_generator_invalid_index() {
1355 let tangent = SO3Tangent::new(Vector3::new(1.0, 1.0, 1.0));
1356 let _generator = tangent.generator(3); }
1358
1359 #[test]
1360 fn test_so3_jacobi_identity() {
1361 let x = SO3Tangent::new(Vector3::new(0.1, 0.0, 0.0));
1363 let y = SO3Tangent::new(Vector3::new(0.0, 0.2, 0.0));
1364 let z = SO3Tangent::new(Vector3::new(0.0, 0.0, 0.3));
1365
1366 let term1 = x.lie_bracket(&y.lie_bracket(&z));
1367 let term2 = y.lie_bracket(&z.lie_bracket(&x));
1368 let term3 = z.lie_bracket(&x.lie_bracket(&y));
1369
1370 let jacobi_sum = SO3Tangent::new(term1.data + term2.data + term3.data);
1371 assert!(jacobi_sum.is_zero(1e-10));
1372 }
1373
1374 #[test]
1377 fn test_so3_right_jacobian_numerical() {
1378 let xi = Vector3::new(0.1, 0.2, 0.3);
1383 let tangent = SO3Tangent::new(xi);
1384 let jr_analytical = tangent.right_jacobian();
1385
1386 let delta_size = 1e-6;
1388 let test_deltas = vec![
1389 Vector3::new(delta_size, 0.0, 0.0),
1390 Vector3::new(0.0, delta_size, 0.0),
1391 Vector3::new(0.0, 0.0, delta_size),
1392 ];
1393
1394 for delta in test_deltas {
1395 let analytical_result = jr_analytical * delta;
1397
1398 let base = SO3Tangent::new(xi).exp(None);
1400 let perturbed = SO3Tangent::new(xi + delta).exp(None);
1401 let base_inv = base.inverse(None);
1402 let diff_element = base_inv.compose(&perturbed, None, None);
1403 let diff_log = diff_element.log(None);
1404 let numerical_result = Vector3::new(diff_log.x(), diff_log.y(), diff_log.z());
1405
1406 let error = (analytical_result - numerical_result).norm();
1407 assert!(
1408 error < 1e-7,
1409 "Right Jacobian verification failed: error = {}, delta = {:?}",
1410 error,
1411 delta
1412 );
1413 }
1414 }
1415
1416 #[test]
1417 fn test_so3_left_jacobian_numerical() {
1418 let xi = Vector3::new(0.1, 0.2, 0.3);
1423 let tangent = SO3Tangent::new(xi);
1424 let jl_analytical = tangent.left_jacobian();
1425
1426 let delta_size = 1e-6;
1428 let test_deltas = vec![
1429 Vector3::new(delta_size, 0.0, 0.0),
1430 Vector3::new(0.0, delta_size, 0.0),
1431 Vector3::new(0.0, 0.0, delta_size),
1432 ];
1433
1434 for delta in test_deltas {
1435 let analytical_result = jl_analytical * delta;
1437
1438 let base = SO3Tangent::new(xi).exp(None);
1440 let perturbed = SO3Tangent::new(xi + delta).exp(None);
1441 let base_inv = base.inverse(None);
1442 let diff_element = perturbed.compose(&base_inv, None, None);
1443 let diff_log = diff_element.log(None);
1444 let numerical_result = Vector3::new(diff_log.x(), diff_log.y(), diff_log.z());
1445
1446 let error = (analytical_result - numerical_result).norm();
1447 assert!(
1448 error < 1e-7,
1449 "Left Jacobian verification failed: error = {}, delta = {:?}",
1450 error,
1451 delta
1452 );
1453 }
1454 }
1455
1456 #[test]
1457 fn test_so3_accumulated_error_small_rotations() {
1458 let small_rotation = SO3::from_scaled_axis(Vector3::new(0.001, 0.002, -0.001));
1460 let mut accumulated = SO3::identity();
1461
1462 for _ in 0..1000 {
1463 accumulated = accumulated.compose(&small_rotation, None, None);
1464 }
1465
1466 let expected_tangent = SO3Tangent::new(Vector3::new(1.0, 2.0, -1.0));
1468 let expected = expected_tangent.exp(None);
1469
1470 assert!(accumulated.is_approx(&expected, 1e-6));
1472 }
1473
1474 #[test]
1475 fn test_so3_inverse_composition_chain() {
1476 let rotation = SO3::from_euler_angles(0.1, 0.2, 0.3);
1478 let inverse = rotation.inverse(None);
1479 let mut accumulated = SO3::identity();
1480
1481 for _ in 0..500 {
1482 accumulated = accumulated.compose(&rotation, None, None);
1483 accumulated = accumulated.compose(&inverse, None, None);
1484 }
1485
1486 assert!(accumulated.is_approx(&SO3::identity(), 1e-9));
1488 }
1489
1490 #[test]
1493 fn test_so3_antipodal_quaternions() {
1494 let q = UnitQuaternion::from_euler_angles(0.5, 0.3, 0.2);
1496 let so3_pos = SO3::new(q);
1497 let so3_neg = SO3::new(UnitQuaternion::from_quaternion(-q.quaternion()));
1498
1499 let log_pos = so3_pos.log(None);
1501 let log_neg = so3_neg.log(None);
1502
1503 let diff_norm = (log_pos.data - log_neg.data).norm();
1505 let sum_norm = (log_pos.data + log_neg.data).norm();
1506 assert!(diff_norm < 1e-10 || sum_norm < 1e-10);
1507 }
1508
1509 #[test]
1510 fn test_so3_near_pi_rotation() {
1511 let axis = Vector3::new(1.0, 0.0, 0.0).normalize();
1513 let angle = std::f64::consts::PI - 1e-8;
1514 let so3 = SO3::from_axis_angle(&axis, angle);
1515
1516 let tangent = so3.log(None);
1517 let recovered = tangent.exp(None);
1518
1519 assert!(so3.is_approx(&recovered, 1e-6));
1520 }
1521
1522 #[test]
1523 fn test_so3_right_jacobian_inverse_identity() {
1524 let tangent = SO3Tangent::new(Vector3::new(0.001, 0.002, 0.003));
1525 let jr = tangent.right_jacobian();
1526 let jr_inv = tangent.right_jacobian_inv();
1527 let product = jr * jr_inv;
1528 let identity = Matrix3::identity();
1529
1530 assert!(
1531 (product - identity).norm() < 1e-10,
1532 "Jr * Jr_inv should be identity, got error: {}",
1533 (product - identity).norm()
1534 );
1535 }
1536
1537 #[test]
1538 fn test_so3_left_jacobian_inverse_identity() {
1539 let tangent = SO3Tangent::new(Vector3::new(0.001, 0.002, 0.003));
1540 let jl = tangent.left_jacobian();
1541 let jl_inv = tangent.left_jacobian_inv();
1542 let product = jl * jl_inv;
1543 let identity = Matrix3::identity();
1544
1545 assert!(
1546 (product - identity).norm() < 1e-10,
1547 "Jl * Jl_inv should be identity, got error: {}",
1548 (product - identity).norm()
1549 );
1550 }
1551
1552 #[test]
1553 fn test_so3_from_quaternion_wxyz() {
1554 let so3_wxyz = SO3::from_quaternion_wxyz(1.0, 0.0, 0.0, 0.0);
1556 let so3_xyzw = SO3::from_quaternion_coeffs(0.0, 0.0, 0.0, 1.0);
1557 assert!(so3_wxyz.is_approx(&so3_xyzw, 1e-12));
1558
1559 let so3_wxyz = SO3::from_quaternion_wxyz(0.4, 0.1, 0.2, 0.3);
1561 let so3_xyzw = SO3::from_quaternion_coeffs(0.1, 0.2, 0.3, 0.4);
1562 assert!(so3_wxyz.is_approx(&so3_xyzw, 1e-12));
1563 }
1564
1565 #[test]
1566 fn test_so3_tangent_is_not_dynamic() {
1567 assert!(!SO3Tangent::is_dynamic());
1568 assert_eq!(SO3Tangent::DIM, 3);
1569 }
1570
1571 #[test]
1572 fn test_so3_small_angle_threshold_exp_log() {
1573 let near_threshold_angles = [1e-6, 1e-5, 1e-4, 1e-3];
1576
1577 for &angle in &near_threshold_angles {
1578 let tangent = SO3Tangent::new(Vector3::new(angle, 0.0, 0.0));
1579 let so3 = tangent.exp(None);
1580 let recovered = so3.log(None);
1581 assert!(
1582 (tangent.data - recovered.data).norm() < 1e-10,
1583 "SO3 exp-log round-trip failed for angle = {angle}"
1584 );
1585 }
1586 }
1587
1588 #[test]
1589 fn test_so3_display() {
1590 let r = SO3::identity();
1591 let s = format!("{r}");
1592 assert!(!s.is_empty(), "Display should produce output");
1593 }
1594
1595 #[test]
1596 fn test_so3_accessors_xyzw() {
1597 let r = SO3::from_axis_angle(&Vector3::z(), std::f64::consts::FRAC_PI_2);
1598 let _ = r.x();
1599 let _ = r.y();
1600 let _ = r.z();
1601 let _ = r.w();
1602 let q = r.to_quaternion();
1603 let q2 = r.quat();
1604 assert!((q.w - q2.w).abs() < 1e-10);
1605 }
1606
1607 #[test]
1608 fn test_so3_set_quaternion_and_coeffs() {
1609 let r_orig = SO3::from_euler_angles(0.1, 0.2, 0.3);
1611 let orig_coeffs = r_orig.coeffs();
1612 assert_eq!(orig_coeffs.len(), 4);
1613
1614 let mut r2 = SO3::identity();
1616 r2.set_quaternion(&orig_coeffs);
1617 let c2 = r2.coeffs();
1618 for i in 0..4 {
1619 assert!(
1620 (c2[i] - orig_coeffs[i]).abs() < 1e-9,
1621 "coeffs[{i}] mismatch"
1622 );
1623 }
1624 }
1625
1626 #[test]
1627 fn test_so3_from_scaled_axis_constructor() {
1628 let v = Vector3::new(0.1, 0.2, 0.3);
1629 let r = SO3::from_scaled_axis(v);
1630 assert!(r.is_valid(1e-6));
1631 }
1632
1633 #[test]
1634 fn test_so3_from_axis_angle_constructor() {
1635 let axis = Vector3::x();
1636 let r = SO3::from_axis_angle(&axis, std::f64::consts::FRAC_PI_4);
1637 assert!(r.is_valid(1e-6));
1638 }
1639
1640 #[test]
1641 fn test_so3_tangent_axis_angle_accessors() {
1642 let t = SO3Tangent::new(Vector3::new(0.1, 0.2, 0.3));
1643
1644 let av = t.axis_angle();
1646 assert!((av.norm() - t.angle()).abs() < 1e-10);
1647
1648 let ax = t.axis();
1650 assert!((ax.norm() - 1.0).abs() < 1e-9);
1651
1652 let c = t.coeffs();
1657 assert_eq!(c.len(), 3);
1658
1659 let ang = t.ang();
1661 assert!((ang.norm() - t.angle()).abs() < 1e-10);
1662 }
1663
1664 #[test]
1665 fn test_so3_right_jacobian_inv() {
1666 use crate::Tangent;
1667 let t = SO3Tangent::new(Vector3::new(0.1, 0.2, 0.3));
1668 let jr_inv = t.right_jacobian_inv();
1669 assert_eq!(jr_inv.nrows(), 3);
1670 assert_eq!(jr_inv.ncols(), 3);
1671
1672 let jr = t.right_jacobian();
1673 let product = jr_inv * jr;
1674 for i in 0..3 {
1675 for j in 0..3 {
1676 let expected = if i == j { 1.0 } else { 0.0 };
1677 assert!(
1678 (product[(i, j)] - expected).abs() < 1e-4,
1679 "product[{i},{j}] = {} != {expected}",
1680 product[(i, j)]
1681 );
1682 }
1683 }
1684 }
1685
1686 #[test]
1687 fn test_so3_left_jacobian_inv() {
1688 use crate::Tangent;
1689 let t = SO3Tangent::new(Vector3::new(0.1, 0.2, 0.3));
1690 let jl_inv = t.left_jacobian_inv();
1691 assert_eq!(jl_inv.nrows(), 3);
1692
1693 let jl = t.left_jacobian();
1694 let product = jl_inv * jl;
1695 for i in 0..3 {
1696 for j in 0..3 {
1697 let expected = if i == j { 1.0 } else { 0.0 };
1698 assert!(
1699 (product[(i, j)] - expected).abs() < 1e-4,
1700 "product[{i},{j}] = {} != {expected}",
1701 product[(i, j)]
1702 );
1703 }
1704 }
1705 }
1706
1707 #[test]
1708 fn test_so3_tangent_normalized() {
1709 use crate::Tangent;
1710 let t = SO3Tangent::new(Vector3::new(0.0, 0.0, 3.0));
1711 let tn = t.normalized();
1712 assert!((tn.angle() - 1.0).abs() < 1e-9);
1713 }
1714
1715 #[test]
1716 fn test_so3_tangent_is_zero() {
1717 use crate::Tangent;
1718 let zero = SO3Tangent::new(Vector3::zeros());
1719 assert!(zero.is_zero(1e-9));
1720 let nonzero = SO3Tangent::new(Vector3::new(0.1, 0.0, 0.0));
1721 assert!(!nonzero.is_zero(1e-9));
1722 }
1723
1724 #[test]
1725 fn test_so3_inverse_with_jacobian() {
1726 use crate::LieGroup;
1727 let r = SO3::from_euler_angles(0.1, 0.2, 0.3);
1728 let mut jac = Matrix3::zeros();
1729 let inv = r.inverse(Some(&mut jac));
1730 assert!(inv.is_valid(1e-6));
1731 assert!(jac[(0, 0)].is_finite());
1732 }
1733
1734 #[test]
1735 fn test_so3_compose_with_jacobians() {
1736 use crate::LieGroup;
1737 let r1 = SO3::from_euler_angles(0.1, 0.2, 0.3);
1738 let r2 = SO3::from_euler_angles(0.4, 0.1, 0.2);
1739 let mut j_self = Matrix3::zeros();
1740 let mut j_other = Matrix3::zeros();
1741 let result = r1.compose(&r2, Some(&mut j_self), Some(&mut j_other));
1742 assert!(result.is_valid(1e-6));
1743 assert!(j_self[(0, 0)].is_finite());
1744 assert!(j_other[(0, 0)].is_finite());
1745 }
1746
1747 #[test]
1748 fn test_so3_zero_jacobian() {
1749 use crate::LieGroup;
1750 let zj = SO3::zero_jacobian();
1751 for i in 0..3 {
1752 for j in 0..3 {
1753 assert!(zj[(i, j)].abs() < 1e-10);
1754 }
1755 }
1756 }
1757
1758 #[test]
1759 fn test_so3_act_with_jacobians() {
1760 use crate::LieGroup;
1761 let r = SO3::from_euler_angles(0.1, 0.2, 0.3);
1762 let v = Vector3::new(1.0, 0.0, 0.0);
1763 let mut j_self = Matrix3::zeros();
1764 let mut j_vec = Matrix3::zeros();
1765 let result = r.act(&v, Some(&mut j_self), Some(&mut j_vec));
1766 assert!(result.norm() > 0.0);
1767 assert!(j_self[(0, 0)].is_finite());
1768 assert!(j_vec[(0, 0)].is_finite());
1769 }
1770
1771 #[test]
1772 fn so3_param_slice_round_trip() {
1773 let g = SO3::random();
1774 let recovered = SO3::from_param_slice(g.as_param_slice());
1775 assert!(g.is_approx(&recovered, 1e-14));
1776 }
1777
1778 #[test]
1779 fn so3_tangent_slice_round_trip() {
1780 let t = SO3Tangent::random();
1781 let recovered = SO3Tangent::from_slice(t.as_slice());
1782 assert!(t.is_approx(&recovered, 1e-14));
1783 }
1784
1785 #[test]
1786 fn test_from_dvector_wxyz() {
1787 let dv = ::nalgebra::dvector![144., 96., 72., 83.] / 205.; let so3 = SO3::from_param_slice(dv.as_slice());
1789 assert_eq!(144. / 205., so3.w());
1790 assert_eq!(96. / 205., so3.x());
1791 assert_eq!(72. / 205., so3.y());
1792 assert_eq!(83. / 205., so3.z());
1793 }
1794
1795 #[test]
1796 fn test_from_s03_wxyz() {
1797 let so3 = SO3::from_quaternion_wxyz(144. / 205., 96. / 205., 72. / 205., 83. / 205.); let s = so3.as_param_slice();
1799 assert_eq!(144. / 205., s[0]);
1800 assert_eq!(96. / 205., s[1]);
1801 assert_eq!(72. / 205., s[2]);
1802 assert_eq!(83. / 205., s[3]);
1803 }
1804
1805 #[test]
1806 fn test_bijective_from_dvector() {
1807 let so3_expected =
1808 SO3::from_quaternion_wxyz(144. / 205., 96. / 205., 72. / 205., 83. / 205.); let slice_expected = so3_expected.as_param_slice();
1810 let so3_actual = SO3::from_param_slice(slice_expected);
1811 assert!(so3_expected.is_approx(&so3_actual, 1e-14));
1812 }
1813}