spatial_math/
matrix.rs

1// Copyright (C) 2020-2025 spatial-math authors. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::ops::{Add, Index, Mul, Sub};
16
17use nalgebra::{Dim, MatrixView3};
18
19use super::{Mat3, Vec3};
20use crate::Real;
21
22/// A memory-efficient 3×3 symmetric matrix for spatial algebra operations.
23///
24/// This structure stores a symmetric 3×3 matrix using only 6 elements instead of 9,
25/// exploiting the mathematical property that `a_ij = a_ji` for symmetric matrices.
26/// This provides significant memory savings and computational efficiency for
27/// inertia tensors and other symmetric operations in spatial algebra.
28///
29/// # Storage Format
30///
31/// The matrix is stored in column-major order, containing the lower triangular
32/// elements of the symmetric matrix:
33/// ```text
34/// Stored array: [a₀₀, a₁₀, a₂₀, a₁₁, a₂₁, a₂₂]
35///
36/// Full matrix:
37/// | a₀₀  a₀₁  a₀₂ |
38/// | a₁₀  a₁₁  a₁₂ |  where a_ij = a_ji
39/// | a₂₀  a₂₁  a₂₂ |
40/// ```
41///
42/// # Applications in Spatial Algebra
43///
44/// - **Rotational inertia tensors**: Always symmetric, stored efficiently
45/// - **Covariance matrices**: Common in state estimation and filtering
46/// - **Stiffness/damping matrices**: Used in compliant dynamics
47/// - **Quadratic forms**: Energy calculations and optimization
48///
49/// # Performance Benefits
50///
51/// - **Memory**: 33% reduction compared to full 3×3 matrix storage
52/// - **Cache efficiency**: Compact layout improves memory access patterns
53/// - **Computation**: Avoids redundant calculations on symmetric elements
54#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
55#[derive(Debug, Clone, Copy, Default)]
56pub struct SymmetricMat3([Real; 6]);
57
58impl SymmetricMat3 {
59    /// Identity matrix (diagonal elements are 1, off-diagonal are 0).
60    ///
61    /// Represents the 3×3 identity matrix in symmetric storage format.
62    pub const ONE: Self = Self([1.0, 0.0, 0.0, 1.0, 0.0, 1.0]);
63    /// Zero matrix (all elements are 0).
64    ///
65    /// Useful for initialization and as the additive identity.
66    pub const ZERO: Self = Self([0.0; 6]);
67
68    /// Create a symmetric matrix from a 6-element array in column-major order.
69    ///
70    /// # Input Format
71    ///
72    /// The array should contain the lower triangular elements in column-major order:
73    /// ```text
74    /// array = [a₀₀, a₁₀, a₂₀, a₁₁, a₂₁, a₂₂]
75    /// ```
76    ///
77    /// # Example
78    ///
79    /// ```rust
80    /// use spatial_math::SymmetricMat3;
81    ///
82    /// // Create matrix |1 2 3|
83    /// //               |2 4 5|
84    /// //               |3 5 6|
85    /// let matrix = SymmetricMat3::from_array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
86    /// ```
87    #[inline]
88    pub fn from_array(array: [Real; 6]) -> Self {
89        Self(array)
90    }
91
92    #[inline]
93    pub fn from_iterator(iter: impl IntoIterator<Item = Real>) -> Self {
94        let mut array: [Real; 6] = [0.0; 6];
95        for (i, val) in iter.into_iter().enumerate() {
96            array[i] = val;
97        }
98        Self(array)
99    }
100
101    #[rustfmt::skip]
102    #[inline]
103    pub fn mat3(&self) -> Mat3 {
104        Mat3::from_iterator([
105            self.0[0], self.0[1], self.0[2],
106            self.0[1], self.0[3], self.0[4],
107            self.0[2], self.0[4], self.0[5]
108        ])
109    }
110
111    #[inline]
112    pub fn mul_vec(&self, v: Vec3) -> Vec3 {
113        self.mat3() * v
114    }
115
116    #[inline]
117    #[must_use]
118    pub fn scale(&self, scalar: Real) -> Self {
119        Self([
120            self.0[0] * scalar,
121            self.0[1] * scalar,
122            self.0[2] * scalar,
123            self.0[3] * scalar,
124            self.0[4] * scalar,
125            self.0[5] * scalar,
126        ])
127    }
128
129    #[inline]
130    #[must_use]
131    pub fn add(&self, rhs: Self) -> Self {
132        Self([
133            self.0[0] + rhs.0[0],
134            self.0[1] + rhs.0[1],
135            self.0[2] + rhs.0[2],
136            self.0[3] + rhs.0[3],
137            self.0[4] + rhs.0[4],
138            self.0[5] + rhs.0[5],
139        ])
140    }
141
142    #[inline]
143    #[must_use]
144    pub fn sub(&self, rhs: Self) -> Self {
145        Self([
146            self.0[0] - rhs.0[0],
147            self.0[1] - rhs.0[1],
148            self.0[2] - rhs.0[2],
149            self.0[3] - rhs.0[3],
150            self.0[4] - rhs.0[4],
151            self.0[5] - rhs.0[5],
152        ])
153    }
154
155    #[inline]
156    pub fn into_array(self) -> [Real; 6] {
157        self.0
158    }
159}
160
161impl From<[Real; 6]> for SymmetricMat3 {
162    #[inline]
163    fn from(array: [Real; 6]) -> Self {
164        Self(array)
165    }
166}
167
168impl Index<(usize, usize)> for SymmetricMat3 {
169    type Output = Real;
170
171    #[inline]
172    fn index(&self, (row, col): (usize, usize)) -> &Self::Output {
173        let (row, col) = if col > row { (col, row) } else { (row, col) };
174        if col == 0 {
175            &self.0[row]
176        } else if col == 1 {
177            &self.0[row + col * 3 - 1]
178        } else {
179            &self.0[row + col * 3 - 3]
180        }
181    }
182}
183
184impl Mul<Vec3> for SymmetricMat3 {
185    type Output = Vec3;
186
187    #[inline]
188    fn mul(self, rhs: Vec3) -> Self::Output {
189        self.mul_vec(rhs)
190    }
191}
192
193impl<RStride, CStride> From<MatrixView3<'_, Real, RStride, CStride>> for SymmetricMat3
194where
195    RStride: Dim,
196    CStride: Dim,
197{
198    /// Takes the lower triangle of the matrix and converts it into a `SymmetricMat3`.
199    #[inline]
200    fn from(mat: MatrixView3<Real, RStride, CStride>) -> Self {
201        Self([
202            mat[(0, 0)],
203            mat[(1, 0)],
204            mat[(2, 0)],
205            mat[(1, 1)],
206            mat[(2, 1)],
207            mat[(2, 2)],
208        ])
209    }
210}
211
212impl From<Mat3> for SymmetricMat3 {
213    #[inline]
214    fn from(mat: Mat3) -> Self {
215        Self([
216            mat[(0, 0)],
217            mat[(1, 0)],
218            mat[(2, 0)],
219            mat[(1, 1)],
220            mat[(2, 1)],
221            mat[(2, 2)],
222        ])
223    }
224}
225
226impl Add<Self> for SymmetricMat3 {
227    type Output = Self;
228
229    #[inline]
230    fn add(self, rhs: Self) -> Self::Output {
231        SymmetricMat3::add(&self, rhs)
232    }
233}
234
235impl Sub<Self> for SymmetricMat3 {
236    type Output = Self;
237
238    #[inline]
239    fn sub(self, rhs: Self) -> Self::Output {
240        SymmetricMat3::sub(&self, rhs)
241    }
242}
243
244impl Mul<Real> for SymmetricMat3 {
245    type Output = Self;
246
247    #[inline]
248    fn mul(self, rhs: Real) -> Self::Output {
249        self.scale(rhs)
250    }
251}