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}