iron_shapes/
matrix3d.rs

1// Copyright (c) 2018-2020 Thomas Kramer.
2// SPDX-FileCopyrightText: 2018-2022 Thomas Kramer
3//
4// SPDX-License-Identifier: AGPL-3.0-or-later
5
6//! Data structures and functions for 3x3 matrices.
7
8use crate::CoordinateType;
9
10/// 3x3 matrix of the form.
11/// ```txt
12/// [[ m11, m12, m13 ],
13///  [ m21, m22, m23 ],
14///  [ m31, m32, m33 ]]
15/// ```
16#[derive(Clone, Hash, PartialEq, Eq, Debug)]
17#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
18pub struct Matrix3d<T: CoordinateType> {
19    /// m11
20    pub m11: T,
21    /// m12
22    pub m12: T,
23    /// m13
24    pub m13: T,
25    /// m21
26    pub m21: T,
27    /// m22
28    pub m22: T,
29    /// m23
30    pub m23: T,
31    /// m31
32    pub m31: T,
33    /// m32
34    pub m32: T,
35    /// m33
36    pub m33: T,
37}
38
39impl<T> Matrix3d<T>
40where
41    T: CoordinateType,
42{
43    /// Create a new 3x3 matrix with entries of the form:
44    /// ```txt
45    /// [[ m11, m12, m13 ],
46    ///  [ m21, m22, m23 ],
47    ///  [ m31, m32, m33 ]]
48    /// ```
49    pub fn new([[m11, m12, m13], [m21, m22, m23], [m31, m32, m33]]: [[T; 3]; 3]) -> Self {
50        Matrix3d {
51            m11,
52            m12,
53            m13,
54            m21,
55            m22,
56            m23,
57            m31,
58            m32,
59            m33,
60        }
61    }
62
63    /// Create a new 3x3 matrix with entries of the form:
64    /// ```txt
65    /// [[ m11, m12, m13 ],
66    ///  [ m21, m22, m23 ],
67    ///  [ m31, m32, m33 ]]
68    /// ```
69    fn new_from_flat([m11, m12, m13, m21, m22, m23, m31, m32, m33]: [T; 9]) -> Self {
70        Matrix3d {
71            m11,
72            m12,
73            m13,
74            m21,
75            m22,
76            m23,
77            m31,
78            m32,
79            m33,
80        }
81    }
82
83    /// Return the identity matrix.
84    pub fn identity() -> Self {
85        #![allow(clippy::just_underscores_and_digits)]
86        let _0 = T::zero();
87        let _1 = T::one();
88        Self::new_from_flat([_1, _0, _0, _0, _1, _0, _0, _0, _1])
89    }
90
91    /// Compute product of the matrix with a scalar.
92    pub fn mul_scalar(&self, rhs: T) -> Self {
93        Matrix3d::new_from_flat([
94            self.m11 * rhs,
95            self.m12 * rhs,
96            self.m13 * rhs,
97            self.m21 * rhs,
98            self.m22 * rhs,
99            self.m23 * rhs,
100            self.m31 * rhs,
101            self.m32 * rhs,
102            self.m33 * rhs,
103        ])
104    }
105
106    /// Element-wise addition of two matrices.
107    pub fn add(&self, rhs: &Self) -> Self {
108        Matrix3d::new_from_flat([
109            self.m11 + rhs.m11,
110            self.m12 + rhs.m12,
111            self.m13 + rhs.m13,
112            self.m21 + rhs.m21,
113            self.m22 + rhs.m22,
114            self.m23 + rhs.m23,
115            self.m31 + rhs.m31,
116            self.m32 + rhs.m32,
117            self.m33 + rhs.m33,
118        ])
119    }
120
121    /// Compute multiplication with a column vector `A*rhs`.
122    pub fn mul_column_vector(&self, rhs: &(T, T, T)) -> (T, T, T) {
123        (
124            self.m11 * rhs.0 + self.m12 * rhs.1 + self.m13 * rhs.2,
125            self.m21 * rhs.0 + self.m22 * rhs.1 + self.m23 * rhs.2,
126            self.m31 * rhs.0 + self.m32 * rhs.1 + self.m33 * rhs.2,
127        )
128    }
129
130    /// Matrix-matrix multiplication.
131    pub fn mul_matrix(&self, rhs: &Self) -> Self {
132        let a = self;
133        let b = rhs;
134        let c11 = a.m11 * b.m11 + a.m12 * b.m21 + a.m13 * b.m31;
135        let c12 = a.m11 * b.m12 + a.m12 * b.m22 + a.m13 * b.m32;
136        let c13 = a.m11 * b.m13 + a.m12 * b.m23 + a.m13 * b.m33;
137        let c21 = a.m21 * b.m11 + a.m22 * b.m21 + a.m23 * b.m31;
138        let c22 = a.m21 * b.m12 + a.m22 * b.m22 + a.m23 * b.m32;
139        let c23 = a.m21 * b.m13 + a.m22 * b.m23 + a.m23 * b.m33;
140        let c31 = a.m31 * b.m11 + a.m32 * b.m21 + a.m33 * b.m31;
141        let c32 = a.m31 * b.m12 + a.m32 * b.m22 + a.m33 * b.m32;
142        let c33 = a.m31 * b.m13 + a.m32 * b.m23 + a.m33 * b.m33;
143        Self::new_from_flat([c11, c12, c13, c21, c22, c23, c31, c32, c33])
144    }
145
146    /// Compute the transpose of the matrix.
147    pub fn transpose(&self) -> Self {
148        Self::new_from_flat([
149            self.m11, self.m21, self.m31, self.m12, self.m22, self.m32, self.m13, self.m23,
150            self.m33,
151        ])
152    }
153
154    /// Test if this matrix is the identity matrix.
155    pub fn is_identity(&self) -> bool {
156        self == &Self::identity()
157    }
158
159    /// Test if this matrix is unitary.
160    pub fn is_unitary(&self) -> bool {
161        self.mul_matrix(&self.transpose()).is_identity()
162    }
163
164    /// Compute the determinant of this matrix.
165    pub fn determinant(&self) -> T {
166        /*
167        ```python
168        import sympy as sp
169        m = sp.Matrix([[sp.Symbol(f"a.m{i}{j}") for j in range(1,4)] for i in range(1,4)])
170        m.det()
171        ```
172         */
173        let a = self;
174        a.m11 * a.m22 * a.m33 - a.m11 * a.m23 * a.m32 - a.m12 * a.m21 * a.m33
175            + a.m12 * a.m23 * a.m31
176            + a.m13 * a.m21 * a.m32
177            - a.m13 * a.m22 * a.m31
178    }
179
180    /// Compute the inverse matrix.
181    /// `None` will be returned if the determinant is zero and the matrix
182    /// is not invertible.
183    pub fn try_inverse(&self) -> Option<Self> {
184        /*
185        ```python
186        import sympy as sp
187        m = sp.Matrix([[sp.Symbol(f"a.m{i}{j}") for j in range(1,4)] for i in range(1,4)])
188        det = sp.Symbol('det')
189
190        # Compute inverse multiplied with determinant.
191        inv_det = m.inv() * m.det()
192        inv = inv_det / det
193        # Print inverse with factored-out determinant.
194        print(repr(inv).replace('[', '').replace(']', ''))
195        ```
196         */
197        let a = self;
198
199        // Compute determinant.
200        let det = a.determinant();
201        if !det.is_zero() {
202            Some(Self::new_from_flat([
203                (a.m22 * a.m33 - a.m23 * a.m32) / det,
204                (a.m13 * a.m32 - a.m12 * a.m33) / det,
205                (a.m12 * a.m23 - a.m13 * a.m22) / det,
206                (a.m23 * a.m31 - a.m21 * a.m33) / det,
207                (a.m11 * a.m33 - a.m13 * a.m31) / det,
208                (a.m13 * a.m21 - a.m11 * a.m23) / det,
209                (a.m21 * a.m32 - a.m22 * a.m31) / det,
210                (a.m12 * a.m31 - a.m11 * a.m32) / det,
211                (a.m11 * a.m22 - a.m12 * a.m21) / det,
212            ]))
213        } else {
214            None
215        }
216    }
217}
218
219impl<T: CoordinateType> Default for Matrix3d<T> {
220    fn default() -> Self {
221        Self::identity()
222    }
223}
224
225#[test]
226fn test_matrix_multiplication() {
227    let id: Matrix3d<i32> = Matrix3d::identity();
228    assert_eq!(id.mul_matrix(&id), id);
229}
230
231#[test]
232fn test_mul_column_vector() {
233    let id: Matrix3d<i32> = Matrix3d::identity();
234    let v = (1, 2, 3);
235    assert_eq!(id.mul_column_vector(&v), v);
236}
237
238#[test]
239fn test_inverse() {
240    let m = Matrix3d::new([[1.0, 4.0, 2.0], [1.0, 2.0, 4.0], [2.0, 0.0, 4.0]]);
241    let i = m.try_inverse().unwrap();
242    assert_eq!(m.mul_matrix(&i), Matrix3d::identity());
243    assert_eq!(i.mul_matrix(&m), Matrix3d::identity());
244}