hoomd_linear_algebra/lib.rs
1// Copyright (c) 2024-2026 The Regents of the University of Michigan.
2// Part of hoomd-rs, released under the BSD 3-Clause License.
3
4//! Linear algebra optimized for small matrices.
5//!
6//! This crate places an emphasis on generality and simplicity, with optimization efforts
7//! targeted at small matrices. Some complex routines (SVD, matrix inversion, etc.) will
8//! only be implemented for certain shapes, and generally consist of specialized algorithms
9//! optimal for those inputs.
10//!
11//! This crate should not be considered a replacement for a dedicated linear
12//! algebra library like [faer-rs] or [nalgebra]. Instead, it can be used as a
13//! simple and lightweight dependency for matrix methods common to molecular
14//! simulation and analysis.
15//!
16//! [faer-rs]: https://github.com/sarah-quinones/faer-rs.git
17//! [nalgebra]: https://github.com/dimforge/nalgebra
18//!
19//! ## Matrices
20//!
21//! Construct a small rectangular [`Matrix`] on the stack:
22//! ```
23//! use hoomd_linear_algebra::matrix::Matrix;
24//!
25//! let m = Matrix {
26//! rows: [[1.0, 2.0, 3.0], [-6.0, 3.0, 2.0]],
27//! };
28//! ```
29//!
30//! [`Matrix`]: matrix::Matrix
31//!
32//! Diagonal matrices ([`DiagonalMatrix`]) store only the diagonal elements:
33//! ```
34//! use hoomd_linear_algebra::matrix::DiagonalMatrix;
35//!
36//! let m = DiagonalMatrix {
37//! elements: [-2.0, 4.0, -5.0],
38//! };
39//! ```
40//!
41//! [`DiagonalMatrix`]: matrix::DiagonalMatrix
42//!
43//! [`Matrix22`], [`Matrix33`], and [`Matrix44`] are type aliases for commonly used
44//! matrix sizes. Construct a 2x2 matrix with every element set to 4:
45//! ```
46//! use hoomd_linear_algebra::{Full, matrix::Matrix22};
47//!
48//! let m = Matrix22::full(4.0);
49//! ```
50//!
51//! [`Matrix22`]: matrix::Matrix22
52//! [`Matrix33`]: matrix::Matrix33
53//! [`Matrix44`]: matrix::Matrix44
54//!
55//! Construct a 3x3 identity matrix $` \mathbf{I} `$:
56//! ```
57//! use hoomd_linear_algebra::{SquareMatrix, matrix::Matrix44};
58//!
59//! let m = Matrix44::identity();
60//! ```
61//!
62//! Index matrix entries by `(row, column)`:
63//! ```
64//! use hoomd_linear_algebra::matrix::Matrix;
65//!
66//! let m = Matrix {
67//! rows: [[1.0, 2.0, 3.0], [-6.0, 3.0, 2.0]],
68//! };
69//!
70//! let element = m[(1, 2)];
71//! ```
72//!
73//! ## Matrix Operations
74//!
75//! Matrices can be added:
76//! ```
77//! use hoomd_linear_algebra::{Full, GeneralMatrix, matrix::Matrix};
78//! let a = Matrix {
79//! rows: [[1.0, -3.0], [-2.0, 4.0]],
80//! };
81//! let b = Matrix {
82//! rows: [[4.0, -8.0], [6.0, 7.0]],
83//! };
84//!
85//! let mut c = a + b;
86//! c += a;
87//! ```
88//!
89//! subtracted:
90//! ```
91//! use hoomd_linear_algebra::{Full, GeneralMatrix, matrix::Matrix};
92//! let a = Matrix {
93//! rows: [[1.0, -3.0], [-2.0, 4.0]],
94//! };
95//! let b = Matrix {
96//! rows: [[4.0, -8.0], [6.0, 7.0]],
97//! };
98//!
99//! let mut c = a - b;
100//! c += a;
101//! ```
102//!
103//! multiplied by a scalar:
104//! ```
105//! use hoomd_linear_algebra::{Full, GeneralMatrix, matrix::Matrix};
106//! let a = Matrix {
107//! rows: [[4.0, -8.0], [6.0, 7.0]],
108//! };
109//! let b = -2.0;
110//!
111//! let mut c = a * b;
112//! c *= b;
113//! ```
114//!
115//! negated:
116//! ```
117//! use hoomd_linear_algebra::{Full, GeneralMatrix, matrix::Matrix};
118//! let a = Matrix {
119//! rows: [[1.0, -3.0], [-2.0, 4.0]],
120//! };
121//!
122//! let b = -a;
123//! ```
124//!
125//! and indexed (in row,column ordering):
126//! ```
127//! use hoomd_linear_algebra::{Full, GeneralMatrix, matrix::Matrix};
128//! let a = Matrix {
129//! rows: [[1.0, -3.0], [-2.0, 4.0]],
130//! };
131//!
132//! let element = a[(1, 0)];
133//! ```
134//!
135//! You can also perform matrix-matrix multiplication:
136//! ```
137//! use hoomd_linear_algebra::{MatMul, matrix::Matrix};
138//!
139//! let a = Matrix {
140//! rows: [[1.0, 2.0], [3.0, 4.0]],
141//! };
142//! let b = Matrix {
143//! rows: [[4.0, 3.0], [2.0, 1.0]],
144//! };
145//!
146//! let c = a.matmul(&b);
147//! ```
148//!
149//! and invert matrices:
150//! ```
151//! use hoomd_linear_algebra::{Invertible, matrix::Matrix};
152//!
153//! let a = Matrix {
154//! rows: [[1.0, 2.0], [3.0, 4.0]],
155//! };
156//!
157//! let b = a.inverse();
158//! ```
159//!
160//! ## Numerical Algorithms
161//!
162//! `hoomd-linear-algebra` implements a number of numerical algorithms on
163//! matrices:
164//!
165//! * [`Determinant`](matrix::Matrix::determinant)
166//! * [`Singular value decomposition (2x2)`](matrix::Matrix22::svd)
167//! * [`Singular value decomposition (3x3)`](matrix::Matrix33::svd)
168//! * [`Quadratic form`](QuadraticForm)
169//!
170//! # Complete documentation
171//!
172//! `hoomd-linear-algebra` is is a part of *hoomd-rs*. Read the [complete documentation]
173//! for more information.
174//!
175//! [complete documentation]: https://hoomd-rs.readthedocs.io
176
177use std::ops::{Add, AddAssign, Index, Mul, MulAssign, Neg, Sub, SubAssign};
178
179/// Structs implementing a large subset of Matrix traits.
180pub mod matrix;
181
182/// A lightweight representation of a diagonal matrix.
183mod diagonal;
184
185/// Compute the inverse of a matrix.
186///
187/// A matrix $`A`$ has an inverse $`A^{-1}`$ such that $`AA^{-1} = A^{-1}A = I`$.
188pub trait Invertible
189where
190 Self: Sized,
191{
192 /// Compute the inverse of a matrix.
193 ///
194 /// Returns `None` when the matrix is not invertible.
195 ///
196 /// # Example
197 ///
198 /// ```
199 /// use hoomd_linear_algebra::{Invertible, SquareMatrix, matrix::Matrix};
200 /// let m = Matrix::identity() * 5.0;
201 /// let m_inv = m.inverse();
202 ///
203 /// assert_eq!(m_inv, Some(Matrix::with_diagonal([1.0 / 5.0; 3])));
204 /// ```
205 #[must_use]
206 fn inverse(&self) -> Option<Self>;
207}
208
209/// Matrix multiplication.
210pub trait MatMul<Rhs> {
211 /// The type of the output matrix.
212 type Output;
213
214 /// Multiply two matrices.
215 ///
216 /// # Example
217 /// ```
218 /// use hoomd_linear_algebra::{
219 /// Full, GeneralMatrix, MatMul, SquareMatrix,
220 /// matrix::{DiagonalMatrix, Matrix, Matrix22},
221 /// };
222 ///
223 /// let a = Matrix22::full(5.0);
224 /// assert_eq!(a.matmul(&Matrix22::identity()), a);
225 ///
226 /// let b = Matrix::with_diagonal([3.0, 2.0]);
227 ///
228 /// assert_eq!(a.matmul(&b).rows, [[15.0, 10.0], [15.0, 10.0]]);
229 /// ```
230 #[must_use]
231 fn matmul(&self, rhs: &Rhs) -> Self::Output;
232}
233
234/// Common operations for all matrices.
235///
236/// Matrices can be added:
237/// ```
238/// use hoomd_linear_algebra::{Full, GeneralMatrix, matrix::Matrix};
239/// let a = Matrix {
240/// rows: [[1.0, -3.0], [-2.0, 4.0]],
241/// };
242/// let b = Matrix {
243/// rows: [[4.0, -8.0], [6.0, 7.0]],
244/// };
245///
246/// let mut c = a + b;
247/// c += a;
248/// ```
249///
250/// subtracted:
251/// ```
252/// use hoomd_linear_algebra::{Full, GeneralMatrix, matrix::Matrix};
253/// let a = Matrix {
254/// rows: [[1.0, -3.0], [-2.0, 4.0]],
255/// };
256/// let b = Matrix {
257/// rows: [[4.0, -8.0], [6.0, 7.0]],
258/// };
259///
260/// let mut c = a - b;
261/// c += a;
262/// ```
263///
264/// multiplied by a scalar:
265/// ```
266/// use hoomd_linear_algebra::{Full, GeneralMatrix, matrix::Matrix};
267/// let a = Matrix {
268/// rows: [[4.0, -8.0], [6.0, 7.0]],
269/// };
270/// let b = -2.0;
271///
272/// let mut c = a * b;
273/// c *= b;
274/// ```
275///
276/// negated:
277/// ```
278/// use hoomd_linear_algebra::{Full, GeneralMatrix, matrix::Matrix};
279/// let a = Matrix {
280/// rows: [[1.0, -3.0], [-2.0, 4.0]],
281/// };
282///
283/// let b = -a;
284/// ```
285///
286/// and indexed (in row,column ordering):
287/// ```
288/// use hoomd_linear_algebra::{Full, GeneralMatrix, matrix::Matrix};
289/// let a = Matrix {
290/// rows: [[1.0, -3.0], [-2.0, 4.0]],
291/// };
292///
293/// let element = a[(1, 0)];
294/// ```
295pub trait GeneralMatrix:
296 Add<Self, Output = Self>
297 + AddAssign<Self>
298 + Index<(usize, usize), Output = f64>
299 + Mul<f64, Output = Self>
300 + MulAssign<f64>
301 + Neg<Output = Self>
302 + Sized
303 + Sub<Self, Output = Self>
304 + SubAssign<Self>
305{
306 /// Fill a matrix with zeros.
307 ///
308 /// # Example
309 ///
310 /// ```
311 /// use hoomd_linear_algebra::{GeneralMatrix, matrix::Matrix};
312 ///
313 /// let a = Matrix::zeros();
314 ///
315 /// assert_eq!(a.rows, [[0.0, 0.0], [0.0, 0.0]]);
316 /// ```
317 #[must_use]
318 fn zeros() -> Self;
319
320 /// Get the shape of a matrix (``n_rows,n_columns``).
321 ///
322 /// # Example
323 ///
324 /// ```
325 /// use hoomd_linear_algebra::{GeneralMatrix, matrix::Matrix};
326 ///
327 /// let a = Matrix::<5, 7>::zeros();
328 /// assert_eq!(a.shape(), (5, 7));
329 /// ```
330 #[must_use]
331 fn shape(&self) -> (usize, usize);
332}
333
334/// Initialize matrices with identical elements.
335pub trait Full {
336 /// Construct a matrix the same value in every element.
337 ///
338 /// # Examples
339 /// ```
340 /// use hoomd_linear_algebra::{Full, matrix::Matrix22};
341 /// let m = Matrix22::full(5.0);
342 /// assert_eq!(m.rows, [[5.0, 5.0], [5.0, 5.0]]);
343 /// ```
344 #[must_use]
345 fn full(value: f64) -> Self;
346}
347
348/// Matrices that have the same number of rows and columns.
349pub trait SquareMatrix: GeneralMatrix {
350 /// Construct an N x N identity matrix.
351 ///
352 /// # Example
353 /// ```
354 /// use hoomd_linear_algebra::{SquareMatrix, matrix::Matrix22};
355 /// let m = Matrix22::identity();
356 /// assert_eq!(m.rows, [[1.0, 0.0], [0.0, 1.0]]);
357 /// ```
358 #[must_use]
359 fn identity() -> Self;
360}
361
362/// Solve the quadratic form.
363///
364/// ```math
365/// \mathbf{x}^{\intercal} \mathbf{A} \mathbf{x}
366/// ```
367pub trait QuadraticForm<const N: usize>: SquareMatrix {
368 /// Evaluate the quadratic form.
369 ///
370 /// The matrix `A` is given by `self` and the vector `x` in the argument.
371 #[must_use]
372 fn compute_quadratic_form(&self, x: &[f64; N]) -> f64;
373}