Skip to main content

apex_manifolds/
rn.rs

1//! Rn - n-dimensional Euclidean Space
2//!
3//! This module implements the n-dimensional Euclidean space Rⁿ with vector addition
4//! as the group operation.
5//!
6//! Rⁿ elements are represented using nalgebra's `DVector<f64>` for dynamic sizing.
7//! Rⁿ tangent elements are also represented as `DVector<f64>` since the tangent space
8//! is isomorphic to the manifold itself.
9//!
10//! The implementation follows the [manif](https://github.com/artivis/manif) C++ library
11//! conventions and provides all operations required by the LieGroup and Tangent traits.
12
13use crate::{Interpolatable, LieGroup, Tangent};
14use nalgebra::{DMatrix, DVector, Matrix3, Vector3};
15use std::{
16    fmt,
17    fmt::{Display, Formatter},
18};
19
20/// Rⁿ group element representing n-dimensional Euclidean vectors.
21///
22/// Internally represented using nalgebra's `DVector<f64>` for dynamic sizing.
23#[derive(Clone, PartialEq)]
24pub struct Rn {
25    /// Internal representation as a dynamic vector
26    data: DVector<f64>,
27}
28
29impl Display for Rn {
30    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
31        write!(f, "Rn(dim: {}, data: [", self.data.len())?;
32        for (i, val) in self.data.iter().enumerate() {
33            if i > 0 {
34                write!(f, ", ")?;
35            }
36            write!(f, "{:.4}", val)?;
37        }
38        write!(f, "])")
39    }
40}
41
42// Conversion traits for integration with generic Problem
43impl From<DVector<f64>> for Rn {
44    fn from(data: DVector<f64>) -> Self {
45        Rn::new(data)
46    }
47}
48
49impl From<Rn> for DVector<f64> {
50    fn from(rn: Rn) -> Self {
51        rn.data
52    }
53}
54
55/// Rⁿ tangent space element representing elements in the Lie algebra rⁿ.
56///
57/// For Euclidean space, the tangent space is isomorphic to the manifold itself,
58/// so this is also represented as a `DVector<f64>`.
59#[derive(Clone, PartialEq)]
60pub struct RnTangent {
61    /// Internal data: n-dimensional vector
62    data: DVector<f64>,
63}
64impl From<DVector<f64>> for RnTangent {
65    fn from(data: DVector<f64>) -> Self {
66        RnTangent::new(data)
67    }
68}
69
70impl From<RnTangent> for DVector<f64> {
71    fn from(rn: RnTangent) -> Self {
72        rn.data
73    }
74}
75
76impl Display for RnTangent {
77    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
78        write!(f, "rn(dim: {}, data: [", self.data.len())?;
79        for (i, val) in self.data.iter().enumerate() {
80            if i > 0 {
81                write!(f, ", ")?;
82            }
83            write!(f, "{:.4}", val)?;
84        }
85        write!(f, "])")
86    }
87}
88
89impl Rn {
90    /// Space dimension - dimension of the ambient space that the group acts on
91    /// Note: For Rⁿ this is dynamic and determined at runtime
92    pub const DIM: usize = 0;
93
94    /// Degrees of freedom - dimension of the tangent space
95    /// Note: For Rⁿ this is dynamic and determined at runtime
96    pub const DOF: usize = 0;
97
98    /// Representation size - size of the underlying data representation
99    /// Note: For Rⁿ this is dynamic and determined at runtime
100    pub const REP_SIZE: usize = 0;
101
102    /// Get the identity element of the group.
103    ///
104    /// Returns the neutral element e such that e ∘ g = g ∘ e = g for any group element g.
105    /// Note: Default to 3D identity for compatibility, but this should be created with specific dimension
106    pub fn identity() -> Self {
107        Rn::new(DVector::zeros(3))
108    }
109
110    /// Get the identity matrix for Jacobians.
111    ///
112    /// Returns the identity matrix in the appropriate dimension for Jacobian computations.
113    /// Note: Default to 3x3 identity, but this should be created with specific dimension
114    pub fn jacobian_identity() -> DMatrix<f64> {
115        DMatrix::identity(3, 3)
116    }
117
118    /// Create a new Rⁿ element from a vector.
119    ///
120    /// # Arguments
121    /// * `data` - Vector data
122    #[inline]
123    pub fn new(data: DVector<f64>) -> Self {
124        Rn { data }
125    }
126
127    /// Create Rⁿ from a slice.
128    ///
129    /// # Arguments
130    /// * `slice` - Data slice
131    pub fn from_slice(slice: &[f64]) -> Self {
132        Rn::new(DVector::from_row_slice(slice))
133    }
134
135    /// Create Rⁿ from individual components (up to 6D for convenience).
136    pub fn from_vec(components: Vec<f64>) -> Self {
137        Rn::new(DVector::from_vec(components))
138    }
139
140    /// Get the underlying vector.
141    #[inline]
142    pub fn data(&self) -> &DVector<f64> {
143        &self.data
144    }
145
146    /// Get the dimension of the space.
147    #[inline]
148    pub fn dim(&self) -> usize {
149        self.data.len()
150    }
151
152    /// Get a specific component.
153    #[inline]
154    pub fn component(&self, index: usize) -> f64 {
155        self.data[index]
156    }
157
158    /// Set a specific component.
159    pub fn set_component(&mut self, index: usize, value: f64) {
160        self.data[index] = value;
161    }
162
163    /// Get the norm (Euclidean length) of the vector.
164    #[inline]
165    pub fn norm(&self) -> f64 {
166        self.data.norm()
167    }
168
169    /// Get the squared norm of the vector.
170    #[inline]
171    pub fn norm_squared(&self) -> f64 {
172        self.data.norm_squared()
173    }
174
175    /// Convert Rn to a DVector
176    #[inline]
177    pub fn to_vector(&self) -> DVector<f64> {
178        self.data.clone()
179    }
180}
181
182impl LieGroup for Rn {
183    const NAME: &'static str = "Rn";
184
185    type TangentVector = RnTangent;
186    type JacobianMatrix = DMatrix<f64>;
187    type LieAlgebra = DMatrix<f64>;
188
189    /// Rⁿ inverse (negation for additive group).
190    ///
191    /// # Arguments
192    /// * `jacobian` - Optional Jacobian matrix of the inverse wrt self.
193    ///
194    /// # Notes
195    /// For Euclidean space with addition: -v
196    /// Jacobian of inverse: d(-v)/dv = -I
197    fn inverse(&self, jacobian: Option<&mut Self::JacobianMatrix>) -> Self {
198        let dim = self.data.len();
199        if let Some(jac) = jacobian {
200            *jac = -DMatrix::identity(dim, dim);
201        }
202        Rn::new(-&self.data)
203    }
204
205    /// Rⁿ composition (vector addition).
206    ///
207    /// # Arguments
208    /// * `other` - Another Rⁿ element.
209    /// * `jacobian_self` - Optional Jacobian matrix of the composition wrt self.
210    /// * `jacobian_other` - Optional Jacobian matrix of the composition wrt other.
211    ///
212    /// # Notes
213    /// For Euclidean space: v₁ + v₂
214    /// Jacobians: d(v₁ + v₂)/dv₁ = I, d(v₁ + v₂)/dv₂ = I
215    fn compose(
216        &self,
217        other: &Self,
218        jacobian_self: Option<&mut Self::JacobianMatrix>,
219        jacobian_other: Option<&mut Self::JacobianMatrix>,
220    ) -> Self {
221        assert_eq!(
222            self.data.len(),
223            other.data.len(),
224            "Rn elements must have the same dimension for composition"
225        );
226
227        let dim = self.data.len();
228        if let Some(jac_self) = jacobian_self {
229            *jac_self = DMatrix::identity(dim, dim);
230        }
231        if let Some(jac_other) = jacobian_other {
232            *jac_other = DMatrix::identity(dim, dim);
233        }
234
235        Rn::new(&self.data + &other.data)
236    }
237
238    /// Logarithmic map from manifold to tangent space.
239    ///
240    /// # Arguments
241    /// * `jacobian` - Optional Jacobian matrix of the tangent wrt to self.
242    ///
243    /// # Notes
244    /// For Euclidean space, log is identity: log(v) = v
245    /// Jacobian: dlog(v)/dv = I
246    fn log(&self, jacobian: Option<&mut Self::JacobianMatrix>) -> Self::TangentVector {
247        let dim = self.data.len();
248        if let Some(jac) = jacobian {
249            *jac = DMatrix::identity(dim, dim);
250        }
251        RnTangent::new(self.data.clone())
252    }
253
254    /// Vee operator: log(g)^∨.
255    ///
256    /// For Euclidean space, this is the same as log().
257    fn vee(&self) -> Self::TangentVector {
258        self.log(None)
259    }
260
261    /// Action on a 3-vector (for compatibility with the trait).
262    ///
263    /// # Arguments
264    /// * `vector` - A 3-vector.
265    /// * `jacobian_self` - Optional Jacobian of the new object wrt this.
266    /// * `jacobian_vector` - Optional Jacobian of the new object wrt input object.
267    ///
268    /// # Returns
269    /// The transformed 3-vector.
270    ///
271    /// # Notes
272    /// For Euclidean space, the action is translation: v + x
273    /// This only works if this Rⁿ element is 3-dimensional.
274    fn act(
275        &self,
276        vector: &Vector3<f64>,
277        jacobian_self: Option<&mut Self::JacobianMatrix>,
278        jacobian_vector: Option<&mut Matrix3<f64>>,
279    ) -> Vector3<f64> {
280        if let Some(jac_self) = jacobian_self {
281            *jac_self = DMatrix::identity(3, 3);
282        }
283        if let Some(jac_vector) = jacobian_vector {
284            *jac_vector = Matrix3::identity();
285        }
286
287        Vector3::new(
288            self.data[0] + vector.x,
289            self.data[1] + vector.y,
290            self.data[2] + vector.z,
291        )
292    }
293
294    /// Get the adjoint matrix of Rⁿ.
295    ///
296    /// # Notes
297    /// For Euclidean space (abelian group), adjoint is identity.
298    fn adjoint(&self) -> Self::JacobianMatrix {
299        let dim = self.data.len();
300        DMatrix::identity(dim, dim)
301    }
302
303    /// Generate a random element.
304    fn random() -> Self {
305        // Default to 3D random vector
306        let data = DVector::from_fn(3, |_, _| rand::random::<f64>() * 10.0 - 5.0);
307        Rn::new(data)
308    }
309
310    fn jacobian_identity() -> Self::JacobianMatrix {
311        // Default to 3D identity for compatibility
312        DMatrix::identity(3, 3)
313    }
314
315    fn zero_jacobian() -> Self::JacobianMatrix {
316        // Default to 3D zero matrix for compatibility
317        DMatrix::zeros(3, 3)
318    }
319
320    fn normalize(&mut self) {
321        let norm = self.data.norm();
322        if norm > 1e-12 {
323            self.data /= norm;
324        }
325    }
326
327    fn is_valid(&self, _tolerance: f64) -> bool {
328        self.data.iter().all(|x| x.is_finite())
329    }
330
331    fn as_param_slice(&self) -> &[f64] {
332        self.data.as_slice()
333    }
334
335    fn as_param_slice_mut(&mut self) -> &mut [f64] {
336        self.data.as_mut_slice()
337    }
338
339    fn from_param_slice(s: &[f64]) -> Self {
340        Rn::from_slice(s)
341    }
342
343    /// Check if the element is approximately equal to another element.
344    ///
345    /// # Arguments
346    /// * `other` - The other element to compare with
347    /// * `tolerance` - The tolerance for the comparison
348    fn is_approx(&self, other: &Self, tolerance: f64) -> bool {
349        if self.data.len() != other.data.len() {
350            return false;
351        }
352        let difference = self.right_minus(other, None, None);
353        difference.is_zero(tolerance)
354    }
355
356    // Explicit implementations of plus/minus operations for optimal performance
357    // These override the default LieGroup implementations to provide correct
358    // Jacobians for Euclidean space (simple identity matrices)
359
360    /// Right plus operation: v ⊞ δ = v + δ.
361    ///
362    /// For Euclidean space, this is simple vector addition.
363    ///
364    /// # Arguments
365    /// * `tangent` - Tangent vector perturbation
366    /// * `jacobian_self` - Optional Jacobian ∂(v ⊞ δ)/∂v = I
367    /// * `jacobian_tangent` - Optional Jacobian ∂(v ⊞ δ)/∂δ = I
368    ///
369    /// # Notes
370    /// For Euclidean space: v ⊞ δ = v + δ
371    /// Jacobians: ∂(v + δ)/∂v = I, ∂(v + δ)/∂δ = I
372    fn right_plus(
373        &self,
374        tangent: &Self::TangentVector,
375        jacobian_self: Option<&mut Self::JacobianMatrix>,
376        jacobian_tangent: Option<&mut Self::JacobianMatrix>,
377    ) -> Self {
378        assert_eq!(
379            self.data.len(),
380            tangent.data.len(),
381            "Rn element and tangent must have the same dimension"
382        );
383
384        let dim = self.data.len();
385
386        if let Some(jac_self) = jacobian_self {
387            *jac_self = DMatrix::identity(dim, dim);
388        }
389
390        if let Some(jac_tangent) = jacobian_tangent {
391            *jac_tangent = DMatrix::identity(dim, dim);
392        }
393
394        Rn::new(&self.data + &tangent.data)
395    }
396
397    /// Right minus operation: v₁ ⊟ v₂ = v₁ - v₂.
398    ///
399    /// For Euclidean space, this is simple vector subtraction.
400    ///
401    /// # Arguments
402    /// * `other` - The reference element v₂
403    /// * `jacobian_self` - Optional Jacobian ∂(v₁ ⊟ v₂)/∂v₁ = I
404    /// * `jacobian_other` - Optional Jacobian ∂(v₁ ⊟ v₂)/∂v₂ = -I
405    ///
406    /// # Notes
407    /// For Euclidean space: v₁ ⊟ v₂ = v₁ - v₂
408    /// Jacobians: ∂(v₁ - v₂)/∂v₁ = I, ∂(v₁ - v₂)/∂v₂ = -I
409    fn right_minus(
410        &self,
411        other: &Self,
412        jacobian_self: Option<&mut Self::JacobianMatrix>,
413        jacobian_other: Option<&mut Self::JacobianMatrix>,
414    ) -> Self::TangentVector {
415        assert_eq!(
416            self.data.len(),
417            other.data.len(),
418            "Rn elements must have the same dimension"
419        );
420
421        let dim = self.data.len();
422
423        if let Some(jac_self) = jacobian_self {
424            *jac_self = DMatrix::identity(dim, dim);
425        }
426
427        if let Some(jac_other) = jacobian_other {
428            *jac_other = -DMatrix::identity(dim, dim);
429        }
430
431        RnTangent::new(&self.data - &other.data)
432    }
433
434    /// Left plus operation: δ ⊞ v = δ + v.
435    ///
436    /// For Euclidean space (abelian group), left plus is the same as right plus.
437    ///
438    /// # Arguments
439    /// * `tangent` - Tangent vector perturbation
440    /// * `jacobian_tangent` - Optional Jacobian ∂(δ ⊞ v)/∂δ = I
441    /// * `jacobian_self` - Optional Jacobian ∂(δ ⊞ v)/∂v = I
442    ///
443    /// # Notes
444    /// For abelian groups: δ ⊞ v = v ⊞ δ = δ + v
445    fn left_plus(
446        &self,
447        tangent: &Self::TangentVector,
448        jacobian_tangent: Option<&mut Self::JacobianMatrix>,
449        jacobian_self: Option<&mut Self::JacobianMatrix>,
450    ) -> Self {
451        assert_eq!(
452            self.data.len(),
453            tangent.data.len(),
454            "Rn element and tangent must have the same dimension"
455        );
456
457        let dim = self.data.len();
458
459        if let Some(jac_tangent) = jacobian_tangent {
460            *jac_tangent = DMatrix::identity(dim, dim);
461        }
462
463        if let Some(jac_self) = jacobian_self {
464            *jac_self = DMatrix::identity(dim, dim);
465        }
466
467        // For abelian groups, left plus is the same as right plus
468        Rn::new(&tangent.data + &self.data)
469    }
470
471    /// Left minus operation: v₁ ⊟ v₂ = v₁ - v₂.
472    ///
473    /// For Euclidean space (abelian group), left minus is the same as right minus.
474    ///
475    /// # Arguments
476    /// * `other` - The reference element v₂
477    /// * `jacobian_self` - Optional Jacobian ∂(v₁ ⊟ v₂)/∂v₁ = I
478    /// * `jacobian_other` - Optional Jacobian ∂(v₁ ⊟ v₂)/∂v₂ = -I
479    ///
480    /// # Notes
481    /// For abelian groups: left minus = right minus
482    fn left_minus(
483        &self,
484        other: &Self,
485        jacobian_self: Option<&mut Self::JacobianMatrix>,
486        jacobian_other: Option<&mut Self::JacobianMatrix>,
487    ) -> Self::TangentVector {
488        // For abelian groups, left minus is the same as right minus
489        self.right_minus(other, jacobian_self, jacobian_other)
490    }
491
492    /// Get the dimension of the tangent space for this Rⁿ element.
493    ///
494    /// # Returns
495    /// The actual runtime dimension of this Rⁿ element.
496    ///
497    /// # Notes
498    /// Overrides the default implementation to return the dynamic size
499    /// based on the actual data vector length, since Rⁿ has variable dimension.
500    fn tangent_dim(&self) -> usize {
501        self.data.len()
502    }
503}
504
505impl RnTangent {
506    /// Create a new RnTangent from a vector.
507    ///
508    /// # Arguments
509    /// * `data` - Vector data
510    #[inline]
511    pub fn new(data: DVector<f64>) -> Self {
512        RnTangent { data }
513    }
514
515    /// Create RnTangent from a slice.
516    ///
517    /// # Arguments
518    /// * `slice` - Data slice
519    pub fn from_slice(slice: &[f64]) -> Self {
520        RnTangent::new(DVector::from_row_slice(slice))
521    }
522
523    /// Create RnTangent from individual components.
524    pub fn from_vec(components: Vec<f64>) -> Self {
525        RnTangent::new(DVector::from_vec(components))
526    }
527
528    /// Get the underlying vector.
529    #[inline]
530    pub fn data(&self) -> &DVector<f64> {
531        &self.data
532    }
533
534    /// Get the dimension of the tangent space.
535    #[inline]
536    pub fn dim(&self) -> usize {
537        self.data.len()
538    }
539
540    /// Get a specific component.
541    #[inline]
542    pub fn component(&self, index: usize) -> f64 {
543        self.data[index]
544    }
545
546    /// Set a specific component.
547    pub fn set_component(&mut self, index: usize, value: f64) {
548        self.data[index] = value;
549    }
550
551    /// Get the norm (Euclidean length) of the tangent vector.
552    #[inline]
553    pub fn norm(&self) -> f64 {
554        self.data.norm()
555    }
556
557    /// Get the squared norm of the tangent vector.
558    #[inline]
559    pub fn norm_squared(&self) -> f64 {
560        self.data.norm_squared()
561    }
562
563    /// Convert RnTangent to a DVector
564    #[inline]
565    pub fn to_vector(&self) -> DVector<f64> {
566        self.data.clone()
567    }
568
569    /// Create RnTangent from a DVector
570    pub fn from_vector(data: DVector<f64>) -> Self {
571        RnTangent::new(data)
572    }
573}
574
575impl Tangent<Rn> for RnTangent {
576    /// Dimension of the tangent space
577    /// Note: For Rⁿ this is dynamic and determined at runtime
578    const DIM: usize = 0;
579
580    /// Exponential map for Euclidean space (identity).
581    ///
582    /// # Arguments
583    /// * `jacobian` - Optional Jacobian matrix of the Rn element wrt this.
584    ///
585    /// # Notes
586    /// For Euclidean space: exp(v) = v
587    /// Jacobian: dexp(v)/dv = I
588    fn exp(&self, jacobian: Option<&mut <Rn as LieGroup>::JacobianMatrix>) -> Rn {
589        let dim = self.data.len();
590        if let Some(jac) = jacobian {
591            *jac = DMatrix::identity(dim, dim);
592        }
593        Rn::new(self.data.clone())
594    }
595
596    fn right_jacobian(&self) -> <Rn as LieGroup>::JacobianMatrix {
597        let dim = self.data.len();
598        DMatrix::identity(dim, dim)
599    }
600
601    fn left_jacobian(&self) -> <Rn as LieGroup>::JacobianMatrix {
602        let dim = self.data.len();
603        DMatrix::identity(dim, dim)
604    }
605
606    fn right_jacobian_inv(&self) -> <Rn as LieGroup>::JacobianMatrix {
607        let dim = self.data.len();
608        DMatrix::identity(dim, dim)
609    }
610
611    fn left_jacobian_inv(&self) -> <Rn as LieGroup>::JacobianMatrix {
612        let dim = self.data.len();
613        DMatrix::identity(dim, dim)
614    }
615
616    /// Hat operator: v^∧ (vector to matrix).
617    ///
618    /// For Euclidean space, this could be interpreted as a diagonal matrix
619    /// or simply return the vector as a column matrix.
620    fn hat(&self) -> <Rn as LieGroup>::LieAlgebra {
621        DMatrix::from_diagonal(&self.data)
622    }
623
624    /// Small adjoint (zero for abelian group).
625    fn small_adj(&self) -> <Rn as LieGroup>::JacobianMatrix {
626        let dim = self.data.len();
627        DMatrix::zeros(dim, dim)
628    }
629
630    /// Lie bracket for Euclidean space.
631    ///
632    /// For abelian groups: [v, w] = 0
633    fn lie_bracket(&self, _other: &Self) -> <Rn as LieGroup>::TangentVector {
634        RnTangent::new(DVector::zeros(self.data.len()))
635    }
636
637    /// Check if the tangent vector is approximately equal to another tangent vector.
638    ///
639    /// # Arguments
640    /// * `other` - The other tangent vector to compare with
641    /// * `tolerance` - The tolerance for the comparison
642    fn is_approx(&self, other: &Self, tolerance: f64) -> bool {
643        if self.data.len() != other.data.len() {
644            return false;
645        }
646        (&self.data - &other.data).norm() < tolerance
647    }
648
649    /// Get the i-th generator of the Lie algebra.
650    ///
651    /// For Euclidean space, generators are standard basis vectors.
652    fn generator(&self, i: usize) -> <Rn as LieGroup>::LieAlgebra {
653        let dim = self.data.len();
654        let mut generator_matrix = DMatrix::zeros(dim, dim);
655        generator_matrix[(i, i)] = 1.0;
656        generator_matrix
657    }
658
659    /// Zero tangent vector.
660    fn zero() -> <Rn as LieGroup>::TangentVector {
661        // Default to 3D zero vector for compatibility
662        RnTangent::new(DVector::zeros(3))
663    }
664
665    /// Random tangent vector.
666    fn random() -> <Rn as LieGroup>::TangentVector {
667        // Default to 3D random vector
668        let data = DVector::from_fn(3, |_, _| rand::random::<f64>() * 10.0 - 5.0);
669        RnTangent::new(data)
670    }
671
672    /// Check if the tangent vector is approximately zero.
673    fn is_zero(&self, tolerance: f64) -> bool {
674        self.data.norm() < tolerance
675    }
676
677    /// Normalize the tangent vector to unit norm.
678    fn normalize(&mut self) {
679        let norm = self.data.norm();
680        if norm > 1e-12 {
681            self.data /= norm;
682        }
683    }
684
685    /// Return a unit tangent vector in the same direction.
686    fn normalized(&self) -> <Rn as LieGroup>::TangentVector {
687        let mut result = self.clone();
688        result.normalize();
689        result
690    }
691
692    fn as_slice(&self) -> &[f64] {
693        self.data.as_slice()
694    }
695
696    fn from_slice(s: &[f64]) -> Self {
697        RnTangent::from_slice(s)
698    }
699}
700
701// Implement Interpolatable trait for Rn
702impl Interpolatable for Rn {
703    /// Linear interpolation in Euclidean space.
704    ///
705    /// For parameter t ∈ `[0,1]`: interp(v₁, v₂, 0) = v₁, interp(v₁, v₂, 1) = v₂.
706    ///
707    /// # Arguments
708    /// * `other` - Target element for interpolation
709    /// * `t` - Interpolation parameter in `[0,1]`
710    fn interp(&self, other: &Self, t: f64) -> Self {
711        assert_eq!(
712            self.data.len(),
713            other.data.len(),
714            "Rn elements must have the same dimension for interpolation"
715        );
716
717        let interpolated = &self.data * (1.0 - t) + &other.data * t;
718        Rn::new(interpolated)
719    }
720
721    /// Spherical linear interpolation (same as linear for Euclidean space).
722    fn slerp(&self, other: &Self, t: f64) -> Self {
723        self.interp(other, t)
724    }
725}
726
727// Additional convenience implementations
728impl Rn {
729    /// Create identity element with specific dimension.
730    ///
731    /// For Euclidean space, the identity (additive neutral element) is the zero vector.
732    /// The default `identity()` returns a 3D zero vector for compatibility.
733    /// Use this method when you need a specific dimension.
734    pub fn identity_with_dim(dim: usize) -> Self {
735        Rn::new(DVector::zeros(dim))
736    }
737
738    /// Create Rn with specific dimension filled with zeros.
739    pub fn zeros(dim: usize) -> Self {
740        Rn::new(DVector::zeros(dim))
741    }
742
743    /// Create Rn with specific dimension filled with ones.
744    pub fn ones(dim: usize) -> Self {
745        Rn::new(DVector::from_element(dim, 1.0))
746    }
747
748    /// Create Rn with specific dimension and random values.
749    pub fn random_with_dim(dim: usize) -> Self {
750        let data = DVector::from_fn(dim, |_, _| rand::random::<f64>() * 10.0 - 5.0);
751        Rn::new(data)
752    }
753
754    /// Create identity matrix for Jacobians with specific dimension.
755    pub fn jacobian_identity_with_dim(dim: usize) -> DMatrix<f64> {
756        DMatrix::identity(dim, dim)
757    }
758}
759
760impl RnTangent {
761    /// Create zero tangent vector with specific dimension.
762    pub fn zero_with_dim(dim: usize) -> Self {
763        RnTangent::new(DVector::zeros(dim))
764    }
765
766    /// Create RnTangent with specific dimension filled with zeros.
767    pub fn zeros(dim: usize) -> Self {
768        RnTangent::new(DVector::zeros(dim))
769    }
770
771    /// Create RnTangent with specific dimension filled with ones.
772    pub fn ones(dim: usize) -> Self {
773        RnTangent::new(DVector::from_element(dim, 1.0))
774    }
775
776    /// Create RnTangent with specific dimension and random values.
777    pub fn random_with_dim(dim: usize) -> Self {
778        let data = DVector::from_fn(dim, |_, _| rand::random::<f64>() * 2.0 - 1.0);
779        RnTangent::new(data)
780    }
781}
782
783#[cfg(test)]
784mod tests {
785    use super::*;
786    use crate::{Interpolatable, LieGroup, Tangent};
787
788    #[test]
789    fn test_rn_basic_operations() {
790        // Test 3D Euclidean space
791        let v1 = Rn::from_vec(vec![1.0, 2.0, 3.0]);
792        let v2 = Rn::from_vec(vec![4.0, 5.0, 6.0]);
793
794        // Test composition (addition)
795        let sum = v1.compose(&v2, None, None);
796        assert_eq!(sum.component(0), 5.0);
797        assert_eq!(sum.component(1), 7.0);
798        assert_eq!(sum.component(2), 9.0);
799
800        // Test identity
801        let identity = Rn::zeros(3);
802        let result = v1.compose(&identity, None, None);
803        assert_eq!(result.component(0), v1.component(0));
804        assert_eq!(result.component(1), v1.component(1));
805        assert_eq!(result.component(2), v1.component(2));
806
807        // Test inverse
808        let v1_inv = v1.inverse(None);
809        assert_eq!(v1_inv.component(0), -1.0);
810        assert_eq!(v1_inv.component(1), -2.0);
811        assert_eq!(v1_inv.component(2), -3.0);
812
813        // Test log/exp (should be identity for Euclidean space)
814        let tangent = v1.log(None);
815        let recovered = tangent.exp(None);
816        assert!((recovered.component(0) - v1.component(0)).abs() < 1e-10);
817        assert!((recovered.component(1) - v1.component(1)).abs() < 1e-10);
818        assert!((recovered.component(2) - v1.component(2)).abs() < 1e-10);
819    }
820
821    #[test]
822    fn test_rn_tangent_operations() {
823        let t1 = RnTangent::from_vec(vec![1.0, 2.0, 3.0]);
824        let t2 = RnTangent::from_vec(vec![4.0, 5.0, 6.0]);
825
826        // Test Lie bracket (should be zero for abelian group)
827        let bracket = t1.lie_bracket(&t2);
828        assert!(bracket.is_zero(1e-10));
829
830        // Test zero tangent
831        let zero = RnTangent::zeros(3);
832        assert!(zero.is_zero(1e-10));
833
834        // Test normalization
835        let mut t = RnTangent::from_vec(vec![3.0, 4.0, 0.0]);
836        t.normalize();
837        assert!((t.norm() - 1.0).abs() < 1e-10);
838    }
839
840    #[test]
841    fn test_rn_interpolation() {
842        let v1 = Rn::from_vec(vec![0.0, 0.0, 0.0]);
843        let v2 = Rn::from_vec(vec![10.0, 20.0, 30.0]);
844
845        // Test interpolation at t=0.5
846        let mid = v1.interp(&v2, 0.5);
847        assert_eq!(mid.component(0), 5.0);
848        assert_eq!(mid.component(1), 10.0);
849        assert_eq!(mid.component(2), 15.0);
850
851        // Test interpolation at endpoints
852        let start = v1.interp(&v2, 0.0);
853        let end = v1.interp(&v2, 1.0);
854        assert!(v1.is_approx(&start, 1e-10));
855        assert!(v2.is_approx(&end, 1e-10));
856    }
857
858    #[test]
859    fn test_rn_different_dimensions() {
860        // Test 2D
861        let v2d = Rn::from_vec(vec![1.0, 2.0]);
862        assert_eq!(v2d.dim(), 2);
863
864        // Test 1D
865        let v1d = Rn::from_vec(vec![5.0]);
866        assert_eq!(v1d.dim(), 1);
867
868        // Test higher dimensions
869        let v5d = Rn::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
870        assert_eq!(v5d.dim(), 5);
871        assert_eq!(v5d.component(4), 5.0);
872    }
873
874    #[test]
875    fn test_rn_action() {
876        // Test action on 3D vector (translation)
877        let translation = Rn::from_vec(vec![1.0, 2.0, 3.0]);
878        let point = Vector3::new(4.0, 5.0, 6.0);
879        let transformed = translation.act(&point, None, None);
880
881        assert_eq!(transformed.x, 5.0);
882        assert_eq!(transformed.y, 7.0);
883        assert_eq!(transformed.z, 9.0);
884    }
885
886    #[test]
887    fn test_rn_right_plus_operations() {
888        let v = Rn::from_vec(vec![1.0, 2.0, 3.0]);
889        let delta = RnTangent::from_vec(vec![0.1, 0.2, 0.3]);
890
891        // Test right plus without Jacobians
892        let result = v.right_plus(&delta, None, None);
893        assert!((result.component(0) - 1.1).abs() < 1e-10);
894        assert!((result.component(1) - 2.2).abs() < 1e-10);
895        assert!((result.component(2) - 3.3).abs() < 1e-10);
896
897        // Test right plus with Jacobians
898        let mut jac_self = DMatrix::zeros(3, 3);
899        let mut jac_tangent = DMatrix::zeros(3, 3);
900        let result_jac = v.right_plus(&delta, Some(&mut jac_self), Some(&mut jac_tangent));
901
902        // Verify result is the same
903        assert!((result_jac.component(0) - result.component(0)).abs() < 1e-10);
904        assert!((result_jac.component(1) - result.component(1)).abs() < 1e-10);
905        assert!((result_jac.component(2) - result.component(2)).abs() < 1e-10);
906
907        // Verify Jacobians are identity matrices
908        let identity = DMatrix::identity(3, 3);
909        assert!((jac_self - &identity).norm() < 1e-10);
910        assert!((jac_tangent - &identity).norm() < 1e-10);
911    }
912
913    #[test]
914    fn test_rn_right_minus_operations() {
915        let v1 = Rn::from_vec(vec![5.0, 7.0, 9.0]);
916        let v2 = Rn::from_vec(vec![1.0, 2.0, 3.0]);
917
918        // Test right minus without Jacobians
919        let result = v1.right_minus(&v2, None, None);
920        assert!((result.component(0) - 4.0).abs() < 1e-10);
921        assert!((result.component(1) - 5.0).abs() < 1e-10);
922        assert!((result.component(2) - 6.0).abs() < 1e-10);
923
924        // Test right minus with Jacobians
925        let mut jac_self = DMatrix::zeros(3, 3);
926        let mut jac_other = DMatrix::zeros(3, 3);
927        let result_jac = v1.right_minus(&v2, Some(&mut jac_self), Some(&mut jac_other));
928
929        // Verify result is the same
930        assert!((result_jac.component(0) - result.component(0)).abs() < 1e-10);
931        assert!((result_jac.component(1) - result.component(1)).abs() < 1e-10);
932        assert!((result_jac.component(2) - result.component(2)).abs() < 1e-10);
933
934        // Verify Jacobians
935        let identity = DMatrix::identity(3, 3);
936        let neg_identity = -&identity;
937        assert!((jac_self - &identity).norm() < 1e-10);
938        assert!((jac_other - &neg_identity).norm() < 1e-10);
939    }
940
941    #[test]
942    fn test_rn_left_plus_operations() {
943        let v = Rn::from_vec(vec![1.0, 2.0, 3.0]);
944        let delta = RnTangent::from_vec(vec![0.1, 0.2, 0.3]);
945
946        // Test left plus without Jacobians
947        let result = v.left_plus(&delta, None, None);
948        assert!((result.component(0) - 1.1).abs() < 1e-10);
949        assert!((result.component(1) - 2.2).abs() < 1e-10);
950        assert!((result.component(2) - 3.3).abs() < 1e-10);
951
952        // Test left plus with Jacobians
953        let mut jac_tangent = DMatrix::zeros(3, 3);
954        let mut jac_self = DMatrix::zeros(3, 3);
955        let result_jac = v.left_plus(&delta, Some(&mut jac_tangent), Some(&mut jac_self));
956
957        // Verify result is the same
958        assert!((result_jac.component(0) - result.component(0)).abs() < 1e-10);
959        assert!((result_jac.component(1) - result.component(1)).abs() < 1e-10);
960        assert!((result_jac.component(2) - result.component(2)).abs() < 1e-10);
961
962        // Verify Jacobians are identity matrices
963        let identity = DMatrix::identity(3, 3);
964        assert!((jac_tangent - &identity).norm() < 1e-10);
965        assert!((jac_self - &identity).norm() < 1e-10);
966    }
967
968    #[test]
969    fn test_rn_left_minus_operations() {
970        let v1 = Rn::from_vec(vec![5.0, 7.0, 9.0]);
971        let v2 = Rn::from_vec(vec![1.0, 2.0, 3.0]);
972
973        // Test left minus without Jacobians
974        let result = v1.left_minus(&v2, None, None);
975        assert!((result.component(0) - 4.0).abs() < 1e-10);
976        assert!((result.component(1) - 5.0).abs() < 1e-10);
977        assert!((result.component(2) - 6.0).abs() < 1e-10);
978
979        // Test left minus with Jacobians
980        let mut jac_self = DMatrix::zeros(3, 3);
981        let mut jac_other = DMatrix::zeros(3, 3);
982        let result_jac = v1.left_minus(&v2, Some(&mut jac_self), Some(&mut jac_other));
983
984        // Verify result is the same
985        assert!((result_jac.component(0) - result.component(0)).abs() < 1e-10);
986        assert!((result_jac.component(1) - result.component(1)).abs() < 1e-10);
987        assert!((result_jac.component(2) - result.component(2)).abs() < 1e-10);
988
989        // Verify Jacobians
990        let identity = DMatrix::identity(3, 3);
991        let neg_identity = -&identity;
992        assert!((jac_self - &identity).norm() < 1e-10);
993        assert!((jac_other - &neg_identity).norm() < 1e-10);
994    }
995
996    #[test]
997    fn test_rn_left_right_equivalence() {
998        // For abelian groups, left and right operations should be equivalent
999        let v1 = Rn::from_vec(vec![1.0, 2.0, 3.0]);
1000        let v2 = Rn::from_vec(vec![4.0, 5.0, 6.0]);
1001        let delta = RnTangent::from_vec(vec![0.1, 0.2, 0.3]);
1002
1003        // Test plus operations equivalence
1004        let right_plus = v1.right_plus(&delta, None, None);
1005        let left_plus = v1.left_plus(&delta, None, None);
1006        assert!(right_plus.is_approx(&left_plus, 1e-10));
1007
1008        // Test minus operations equivalence
1009        let right_minus = v1.right_minus(&v2, None, None);
1010        let left_minus = v1.left_minus(&v2, None, None);
1011        assert!(right_minus.is_approx(&left_minus, 1e-10));
1012    }
1013
1014    #[test]
1015    fn test_rn_plus_minus_different_dimensions() {
1016        // Test 2D operations
1017        let v2d = Rn::from_vec(vec![1.0, 2.0]);
1018        let delta2d = RnTangent::from_vec(vec![0.5, 1.0]);
1019        let result2d = v2d.right_plus(&delta2d, None, None);
1020        assert_eq!(result2d.dim(), 2);
1021        assert!((result2d.component(0) - 1.5).abs() < 1e-10);
1022        assert!((result2d.component(1) - 3.0).abs() < 1e-10);
1023
1024        // Test 5D operations
1025        let v5d = Rn::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
1026        let delta5d = RnTangent::from_vec(vec![0.1, 0.2, 0.3, 0.4, 0.5]);
1027        let result5d = v5d.right_plus(&delta5d, None, None);
1028        assert_eq!(result5d.dim(), 5);
1029        for i in 0..5 {
1030            assert!(
1031                (result5d.component(i) - (i as f64 + 1.0 + (i as f64 + 1.0) * 0.1)).abs() < 1e-10
1032            );
1033        }
1034    }
1035
1036    #[test]
1037    fn test_rn_plus_minus_edge_cases() {
1038        let v = Rn::from_vec(vec![1.0, 2.0, 3.0]);
1039
1040        // Test with zero tangent vector
1041        let zero_tangent = RnTangent::zeros(3);
1042        let result_zero = v.right_plus(&zero_tangent, None, None);
1043        assert!(v.is_approx(&result_zero, 1e-10));
1044
1045        // Test minus with itself (should give zero)
1046        let self_minus = v.right_minus(&v, None, None);
1047        assert!(self_minus.is_zero(1e-10));
1048
1049        // Test plus then minus (should recover original)
1050        let delta = RnTangent::from_vec(vec![0.5, 1.0, 1.5]);
1051        let plus_result = v.right_plus(&delta, None, None);
1052        let recovered_delta = plus_result.right_minus(&v, None, None);
1053        assert!(delta.is_approx(&recovered_delta, 1e-10));
1054    }
1055
1056    #[test]
1057    fn test_rn_jacobian_dimensions() {
1058        // Test that Jacobians have correct dimensions for different vector sizes
1059        let v1d = Rn::from_vec(vec![1.0]);
1060        let delta1d = RnTangent::from_vec(vec![0.1]);
1061        let mut jac1d = DMatrix::zeros(1, 1);
1062        v1d.right_plus(&delta1d, Some(&mut jac1d), None);
1063        assert_eq!(jac1d.nrows(), 1);
1064        assert_eq!(jac1d.ncols(), 1);
1065
1066        let v4d = Rn::from_vec(vec![1.0, 2.0, 3.0, 4.0]);
1067        let delta4d = RnTangent::from_vec(vec![0.1, 0.2, 0.3, 0.4]);
1068        let mut jac4d = DMatrix::zeros(4, 4);
1069        v4d.right_plus(&delta4d, Some(&mut jac4d), None);
1070        assert_eq!(jac4d.nrows(), 4);
1071        assert_eq!(jac4d.ncols(), 4);
1072
1073        // Verify they are identity matrices
1074        assert!((jac1d - DMatrix::identity(1, 1)).norm() < 1e-10);
1075        assert!((jac4d - DMatrix::identity(4, 4)).norm() < 1e-10);
1076    }
1077
1078    #[test]
1079    fn test_rn_addition_chain() {
1080        // Add same vector 1000 times
1081        let increment = Rn::new(DVector::from_vec(vec![0.001, -0.002, 0.003]));
1082        let mut accumulated = Rn::new(DVector::zeros(3));
1083
1084        for _ in 0..1000 {
1085            accumulated = accumulated.compose(&increment, None, None);
1086        }
1087
1088        let expected = Rn::new(DVector::from_vec(vec![1.0, -2.0, 3.0]));
1089        assert!((accumulated.to_vector() - expected.to_vector()).norm() < 1e-10);
1090    }
1091
1092    #[test]
1093    #[should_panic(expected = "Rn elements must have the same dimension")]
1094    fn test_rn_dimension_mismatch_compose() {
1095        let rn_2d = Rn::new(DVector::from_vec(vec![1.0, 2.0]));
1096        let rn_3d = Rn::new(DVector::from_vec(vec![3.0, 4.0, 5.0]));
1097
1098        let _ = rn_2d.compose(&rn_3d, None, None); // Should panic
1099    }
1100
1101    #[test]
1102    fn test_rn_zero_dimension() {
1103        let rn_0d = Rn::new(DVector::zeros(0));
1104        assert_eq!(rn_0d.dim(), 0);
1105
1106        let identity = Rn::new(DVector::zeros(0));
1107        let composed = rn_0d.compose(&identity, None, None);
1108        assert_eq!(composed.dim(), 0);
1109    }
1110
1111    #[test]
1112    fn test_rn_identity_with_dim() {
1113        for dim in [1, 2, 3, 5, 10] {
1114            let id = Rn::identity_with_dim(dim);
1115            assert_eq!(id.dim(), dim);
1116            assert!(id.norm() < 1e-15);
1117
1118            // Composing with identity should be no-op
1119            let v = Rn::random_with_dim(dim);
1120            let result = v.compose(&id, None, None);
1121            assert!(v.is_approx(&result, 1e-10));
1122        }
1123    }
1124
1125    #[test]
1126    fn test_rn_tangent_zero_with_dim() {
1127        for dim in [1, 2, 3, 5, 10] {
1128            let z = RnTangent::zero_with_dim(dim);
1129            assert_eq!(z.dim(), dim);
1130            assert!(z.is_zero(1e-15));
1131        }
1132    }
1133
1134    #[test]
1135    fn test_rn_tangent_is_dynamic() {
1136        assert!(RnTangent::is_dynamic());
1137    }
1138
1139    #[test]
1140    fn test_rn_display_format() {
1141        let rn = Rn::new(DVector::from_vec(vec![1.0, 2.0, 3.0]));
1142        let s = format!("{rn}");
1143        assert!(
1144            s.contains("1") && s.contains("2") && s.contains("3"),
1145            "got: {s}"
1146        );
1147
1148        let t = RnTangent::new(DVector::from_vec(vec![4.0, 5.0]));
1149        let st = format!("{t}");
1150        assert!(st.contains("4") && st.contains("5"), "got: {st}");
1151    }
1152
1153    #[test]
1154    fn test_rn_from_dvector_and_back() {
1155        let v = DVector::from_vec(vec![1.0, 2.0, 3.0]);
1156        let rn: Rn = Rn::from(v.clone());
1157        let v2: DVector<f64> = DVector::from(rn);
1158        assert_eq!(v, v2);
1159    }
1160
1161    #[test]
1162    fn test_rn_tangent_from_vec_and_back() {
1163        let v = vec![4.0f64, 5.0];
1164        let t = RnTangent::from_vec(v.clone());
1165        let v2 = t.to_vector();
1166        assert!((v2[0] - v[0]).abs() < 1e-10);
1167        assert!((v2[1] - v[1]).abs() < 1e-10);
1168    }
1169
1170    #[test]
1171    fn test_rn_from_slice_from_vec() {
1172        let rn = Rn::from_slice(&[1.0, 2.0, 3.0]);
1173        assert_eq!(rn.dim(), 3);
1174        assert!((rn.component(0) - 1.0).abs() < 1e-10);
1175
1176        let rn2 = Rn::from_vec(vec![4.0, 5.0]);
1177        assert_eq!(rn2.dim(), 2);
1178
1179        let t = RnTangent::from_slice(&[1.0, 2.0]);
1180        assert_eq!(t.dim(), 2);
1181        let t2 = RnTangent::from_vec(vec![3.0, 4.0]);
1182        assert_eq!(t2.dim(), 2);
1183    }
1184
1185    #[test]
1186    fn test_rn_accessors() {
1187        let mut rn = Rn::from_slice(&[1.0, 2.0, 3.0]);
1188        assert_eq!(rn.data().len(), 3);
1189        assert_eq!(rn.dim(), 3);
1190        assert!((rn.component(1) - 2.0).abs() < 1e-10);
1191        rn.set_component(1, 99.0);
1192        assert!((rn.component(1) - 99.0).abs() < 1e-10);
1193        let rn2 = Rn::from_slice(&[3.0, 4.0]);
1194        assert!((rn2.norm() - 5.0).abs() < 1e-10);
1195        assert!((rn2.norm_squared() - 25.0).abs() < 1e-10);
1196        let v = rn2.to_vector();
1197        assert_eq!(v.len(), 2);
1198    }
1199
1200    #[test]
1201    fn test_rn_tangent_accessors() {
1202        let mut t = RnTangent::from_slice(&[3.0, 4.0]);
1203        assert_eq!(t.data().len(), 2);
1204        assert_eq!(t.dim(), 2);
1205        assert!((t.component(0) - 3.0).abs() < 1e-10);
1206        t.set_component(0, 10.0);
1207        assert!((t.component(0) - 10.0).abs() < 1e-10);
1208
1209        let t2 = RnTangent::from_slice(&[3.0, 4.0]);
1210        assert!((t2.norm() - 5.0).abs() < 1e-10);
1211        assert!((t2.norm_squared() - 25.0).abs() < 1e-10);
1212
1213        let v = t2.to_vector();
1214        assert_eq!(v.len(), 2);
1215
1216        let t3 = RnTangent::from_vector(DVector::from_vec(vec![1.0, 2.0]));
1217        assert_eq!(t3.dim(), 2);
1218    }
1219
1220    #[test]
1221    fn test_rn_vee_adjoint() {
1222        let rn = Rn::from_slice(&[1.0, 2.0, 3.0]);
1223        let t = rn.vee();
1224        assert_eq!(t.dim(), 3);
1225
1226        let adj = rn.adjoint();
1227        assert_eq!(adj.nrows(), 3);
1228        assert_eq!(adj.ncols(), 3);
1229    }
1230
1231    #[test]
1232    fn test_rn_tangent_hat_small_adj_lie_bracket() {
1233        let t = RnTangent::from_slice(&[1.0, 2.0, 3.0]);
1234        let hat = t.hat();
1235        assert!(hat.nrows() > 0);
1236
1237        let sadj = t.small_adj();
1238        assert!(sadj.nrows() > 0);
1239
1240        let t2 = RnTangent::from_slice(&[0.5, 1.0, 1.5]);
1241        let bracket = t.lie_bracket(&t2);
1242        assert_eq!(bracket.dim(), 3);
1243        for i in 0..3 {
1244            assert!(bracket.component(i).abs() < 1e-10);
1245        }
1246    }
1247
1248    #[test]
1249    fn test_rn_tangent_generator_normalize_normalized() {
1250        // normalize in-place
1251        let mut t2 = RnTangent::from_slice(&[3.0, 4.0]);
1252        t2.normalize();
1253        assert!((t2.norm() - 1.0).abs() < 1e-9);
1254
1255        // normalized (returns new)
1256        let t3 = RnTangent::from_slice(&[0.0, 5.0]);
1257        let t3n = t3.normalized();
1258        assert!((t3n.norm() - 1.0).abs() < 1e-9);
1259
1260        // generator via instance method
1261        let t_gen = RnTangent::from_slice(&[0.0, 0.0]);
1262        let gen0 = t_gen.generator(0);
1263        // generator returns a DMatrix with 1.0 at (0,0)
1264        assert!((gen0[(0, 0)] - 1.0).abs() < 1e-10);
1265
1266        let t4 = RnTangent::from_slice(&[1.0, 2.0]);
1267        assert!(t4.is_approx(&t4, 1e-9));
1268    }
1269
1270    #[test]
1271    fn test_rn_tangent_is_zero() {
1272        let zero = RnTangent::from_slice(&[0.0, 0.0, 0.0]);
1273        assert!(zero.is_zero(1e-9));
1274
1275        let nonzero = RnTangent::from_slice(&[0.1, 0.0, 0.0]);
1276        assert!(!nonzero.is_zero(1e-9));
1277    }
1278
1279    #[test]
1280    fn test_rn_factory_methods() {
1281        let id = Rn::identity_with_dim(3);
1282        assert_eq!(id.dim(), 3);
1283
1284        let z = Rn::zeros(4);
1285        assert_eq!(z.dim(), 4);
1286        for i in 0..4 {
1287            assert!(z.component(i).abs() < 1e-10);
1288        }
1289
1290        let o = Rn::ones(3);
1291        for i in 0..3 {
1292            assert!((o.component(i) - 1.0).abs() < 1e-10);
1293        }
1294
1295        let r = Rn::random_with_dim(5);
1296        assert_eq!(r.dim(), 5);
1297
1298        let ji = Rn::jacobian_identity_with_dim(3);
1299        assert_eq!(ji.nrows(), 3);
1300        assert_eq!(ji.ncols(), 3);
1301    }
1302
1303    #[test]
1304    fn test_rn_tangent_factory_methods() {
1305        let zwd = RnTangent::zero_with_dim(4);
1306        assert_eq!(zwd.dim(), 4);
1307
1308        let z = RnTangent::zeros(3);
1309        for i in 0..3 {
1310            assert!(z.component(i).abs() < 1e-10);
1311        }
1312
1313        let o = RnTangent::ones(3);
1314        for i in 0..3 {
1315            assert!((o.component(i) - 1.0).abs() < 1e-10);
1316        }
1317
1318        let r = RnTangent::random_with_dim(5);
1319        assert_eq!(r.dim(), 5);
1320    }
1321
1322    #[test]
1323    fn test_rn_slerp() {
1324        let a = Rn::from_slice(&[0.0, 0.0]);
1325        let b = Rn::from_slice(&[4.0, 0.0]);
1326        let mid = a.slerp(&b, 0.5);
1327        assert!((mid.component(0) - 2.0).abs() < 1e-9);
1328    }
1329
1330    #[test]
1331    fn test_rn_normalize_is_valid() {
1332        let mut rn = Rn::from_slice(&[1.0, 2.0]);
1333        rn.normalize();
1334        assert!(rn.is_valid(1e-9));
1335        // After normalize, it should have unit norm
1336        assert!((rn.norm() - 1.0).abs() < 1e-9);
1337    }
1338
1339    #[test]
1340    fn rn_param_slice_round_trip() {
1341        let g = Rn::random_with_dim(4);
1342        let recovered = Rn::from_param_slice(g.as_param_slice());
1343        assert!(g.is_approx(&recovered, 1e-14));
1344    }
1345
1346    #[test]
1347    fn rn_tangent_slice_round_trip() {
1348        let t = RnTangent::from_slice(&[1.0, 2.0, 3.0]);
1349        let recovered = RnTangent::from_slice(t.as_slice());
1350        assert!(t.is_approx(&recovered, 1e-14));
1351    }
1352
1353    #[test]
1354    fn test_bijective_rn_tangent() {
1355        let tangent_expected = RnTangent::from_slice(&[1.0, 2.0, 3.0]);
1356        let slice_expected = tangent_expected.as_slice();
1357        let tangent_actual = RnTangent::from_slice(slice_expected);
1358        assert!(tangent_expected.is_approx(&tangent_actual, 1e-14));
1359    }
1360}