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