lars/matrix/mat2.rs
1//! 2×2 Matrix utilities.
2//!
3//! Provides a small, self-contained 2×2 matrix type [`Mat2`] with
4//! support for basic linear algebra operations, including addition,
5//! subtraction, scalar and matrix multiplication, and inversion.
6//!
7//! This type is designed to pair naturally with the [`Vec2`] struct
8//! for 2D linear transformations.
9
10use crate::Vec2;
11use derive_more::{Add, Constructor, Sub, Div};
12use std::ops::Mul;
13
14/// A 2×2 matrix of `f64` values.
15///
16/// The matrix is stored in **row-major order**:
17///
18/// ```text
19/// | a b |
20/// | c d |
21/// ```
22///
23/// # Examples
24/// ```
25/// use lars::{Mat2, Vec2};
26///
27/// let m = Mat2::IDENTITY;
28/// let v = Vec2::ONE;
29///
30/// assert_eq!(m * v, v);
31/// ```
32#[derive(Constructor, Copy, Clone, Debug, Add, Sub, PartialOrd, Div)]
33pub struct Mat2 {
34 /// Top-left element.
35 pub a: f64,
36 /// Top-right element.
37 pub b: f64,
38 /// Bottom-left element.
39 pub c: f64,
40 /// Bottom-right element.
41 pub d: f64,
42}
43
44const EPSILON: f64 = 1e-9;
45
46impl PartialEq for Mat2 {
47 fn eq(&self, other: &Self) -> bool {
48 (self.a - other.a).abs() < EPSILON &&
49 (self.b - other.b).abs() < EPSILON &&
50 (self.c - other.c).abs() < EPSILON &&
51 (self.d - other.d).abs() < EPSILON
52 }
53}
54
55impl Mat2 {
56 /// The **identity matrix**:
57 ///
58 /// ```text
59 /// | 1 0 |
60 /// | 0 1 |
61 /// ```
62 pub const IDENTITY: Mat2 = Mat2::new(1.0, 0.0, 0.0, 1.0);
63
64 /// The **zero matrix**:
65 ///
66 /// ```text
67 /// | 0 0 |
68 /// | 0 0 |
69 /// ```
70 pub const ZERO: Mat2 = Mat2::new(0.0, 0.0, 0.0, 0.0);
71
72 /// Returns the **determinant** of the matrix.
73 ///
74 /// Computed as:
75 /// \[
76 /// \det(M) = ad - bc
77 /// \]
78 ///
79 /// # Examples
80 /// ```
81 /// use lars::Mat2;
82 /// let m = Mat2::new(7.0, 2.0, 6.0, 2.0);
83 /// assert_eq!(m.determinant(), 2.0);
84 /// ```
85 pub fn determinant(&self) -> f64 {
86 self.a * self.d - self.b * self.c
87 }
88
89 /// Returns the **inverse** of the matrix, if it exists.
90 ///
91 /// Computed as:
92 /// \[
93 /// M^{-1} = \frac{1}{\det(M)} \begin{bmatrix} d & -b \\ -c & a \end{bmatrix}
94 /// \]
95 ///
96 /// # Panics
97 /// Panics if the matrix is singular (determinant = 0).
98 ///
99 /// # Examples
100 /// ```
101 /// use lars::Mat2;
102 /// let m = Mat2::new(7.0, 2.0, 6.0, 2.0);
103 /// assert_eq!(m.inverse(), Mat2::new(1.0, -1.0, -3.0, 3.5));
104 /// ```
105 pub fn inverse(&self) -> Mat2 {
106 if self.determinant() == 0.0 {
107 panic!("Matrix is singular and cannot be inverted.");
108 };
109 let rec_det = 1.0 / self.determinant();
110
111 rec_det * Mat2::new(self.d, -self.b, -self.c, self.a)
112 }
113}
114
115/// Implements **matrix–scalar multiplication** (`Mat2 * f64`).
116///
117/// Each element of the matrix is scaled by the scalar.
118///
119/// # Examples
120/// ```
121/// use lars::Mat2;
122/// let m = Mat2::new(1.0, 2.0, 3.0, 4.0);
123/// assert_eq!(m * 2.0, Mat2::new(2.0, 4.0, 6.0, 8.0));
124/// ```
125impl Mul<f64> for Mat2 {
126 type Output = Mat2;
127 fn mul(self, s: f64) -> Mat2 {
128 Mat2::new(self.a * s, self.b * s, self.c * s, self.d * s)
129 }
130}
131
132/// Implements **scalar–matrix multiplication** (`f64 * Mat2`).
133///
134/// # Examples
135/// ```
136/// use lars::Mat2;
137/// let m = Mat2::new(1.0, 2.0, 3.0, 4.0);
138/// assert_eq!(2.0 * m, Mat2::new(2.0, 4.0, 6.0, 8.0));
139/// ```
140impl Mul<Mat2> for f64 {
141 type Output = Mat2;
142 fn mul(self, s: Mat2) -> Mat2 {
143 Mat2::new(s.a * self, s.b * self, s.c * self, s.d * self)
144 }
145}
146
147/// Implements **matrix–vector multiplication** (`Mat2 * Vec2`).
148///
149/// Performs the linear transformation of the vector by the matrix.
150///
151/// \[
152/// \begin{bmatrix} a & b \\ c & d \end{bmatrix}
153/// \begin{bmatrix} x \\ y \end{bmatrix}
154/// =
155/// \begin{bmatrix} ax + by \\ cx + dy \end{bmatrix}
156/// \]
157///
158/// # Examples
159/// ```
160/// use lars::{Mat2, Vec2};
161/// let m = Mat2::new(1.0, 2.0, 3.0, 4.0);
162/// let v = Vec2::new(1.0, 1.0);
163/// assert_eq!(m * v, Vec2::new(3.0, 7.0));
164/// ```
165impl Mul<Vec2> for Mat2 {
166 type Output = Vec2;
167 fn mul(self, v: Vec2) -> Vec2 {
168 let x = self.a * v.x + self.b * v.y;
169 let y = self.c * v.x + self.d * v.y;
170 Vec2::new(x, y)
171 }
172}
173
174/// Implements **matrix–matrix multiplication** (`Mat2 * Mat2`).
175///
176/// Standard linear algebra multiplication:
177///
178/// \[
179/// M_1 \times M_2 =
180/// \begin{bmatrix}
181/// a_1a_2 + b_1c_2 & a_1b_2 + b_1d_2 \\
182/// c_1a_2 + d_1c_2 & c_1b_2 + d_1d_2
183/// \end{bmatrix}
184/// \]
185///
186/// # Examples
187/// ```
188/// use lars::Mat2;
189/// let a = Mat2::IDENTITY;
190/// let b = Mat2::new(1.0, 2.0, 3.0, 4.0);
191/// assert_eq!(a * b, b);
192/// ```
193impl Mul<Mat2> for Mat2 {
194 type Output = Mat2;
195 fn mul(self, m: Mat2) -> Mat2 {
196 let a = self.a * m.a + self.b * m.c;
197 let b = self.a * m.b + self.b * m.d;
198 let c = self.c * m.a + self.d * m.c;
199 let d = self.c * m.b + self.d * m.d;
200 Mat2::new(a, b, c, d)
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 #[test]
209 fn test_add() {
210 let m = Mat2::new(1.0, 2.0, 3.0, 4.0);
211 assert_eq!(m + m, Mat2::new(2.0, 4.0, 6.0, 8.0));
212 }
213
214 #[test]
215 fn sub() {
216 let m = Mat2::new(1.0, 2.0, 3.0, 4.0);
217 assert_eq!(m - m, Mat2::ZERO);
218 }
219
220 #[test]
221 fn test_s_mul1() {
222 let m = Mat2::new(1.0, 2.0, 3.0, 4.0);
223 assert_eq!(m * 1.0, m)
224 }
225
226 #[test]
227 fn test_s_mul2() {
228 let m = Mat2::new(1.0, 2.0, 3.0, 4.0);
229 assert_eq!(1.0 * m, m)
230 }
231
232 #[test]
233 fn test_v_mul1() {
234 let i = Mat2::IDENTITY;
235 let v = Vec2::new(1.0, 1.0);
236 assert_eq!(i * v, v)
237 }
238
239 #[test]
240 fn test_mat_mul() {
241 let a = Mat2::IDENTITY;
242 let b = Mat2::new(1.0, 2.0, 3.0, 4.0);
243 assert_eq!(a * b, b);
244 }
245
246 #[test]
247 fn test_determinant() {
248 let m = Mat2::new(7.0, 2.0, 6.0, 2.0);
249 assert_eq!(m.determinant(), 2.0);
250 }
251
252 #[test]
253 fn test_inverse() {
254 let m = Mat2::new(7.0, 2.0, 6.0, 2.0);
255 assert_eq!(m.inverse(), Mat2::new(1.0, -1.0, -3.0, 3.5))
256 }
257
258 #[test]
259 fn test_inverse_singular() {
260 let m = Mat2::new(1.0, 2.0, 2.0, 4.0);
261 let result = std::panic::catch_unwind(|| {
262 m.inverse();
263 });
264 assert!(result.is_err());
265 }
266}