la_stack/
lib.rs

1#![forbid(unsafe_code)]
2#![warn(missing_docs)]
3#![doc = include_str!("../README.md")]
4
5#[cfg(doc)]
6mod readme_doctests {
7    //! Executable versions of README examples.
8    /// ```rust
9    /// use la_stack::prelude::*;
10    ///
11    /// // This system requires pivoting (a[0][0] = 0), so it's a good LU demo.
12    /// let a = Matrix::<5>::from_rows([
13    ///     [0.0, 1.0, 1.0, 1.0, 1.0],
14    ///     [1.0, 0.0, 1.0, 1.0, 1.0],
15    ///     [1.0, 1.0, 0.0, 1.0, 1.0],
16    ///     [1.0, 1.0, 1.0, 0.0, 1.0],
17    ///     [1.0, 1.0, 1.0, 1.0, 0.0],
18    /// ]);
19    ///
20    /// let b = Vector::<5>::new([14.0, 13.0, 12.0, 11.0, 10.0]);
21    ///
22    /// let lu = a.lu(DEFAULT_PIVOT_TOL).unwrap();
23    /// let x = lu.solve_vec(b).unwrap().into_array();
24    ///
25    /// // Floating-point rounding is expected; compare with a tolerance.
26    /// let expected = [1.0, 2.0, 3.0, 4.0, 5.0];
27    /// for (x_i, e_i) in x.iter().zip(expected.iter()) {
28    ///     assert!((*x_i - *e_i).abs() <= 1e-12);
29    /// }
30    /// ```
31    fn solve_5x5_example() {}
32}
33
34mod lu;
35mod matrix;
36mod vector;
37
38use core::fmt;
39
40/// Default absolute pivot tolerance used for singularity detection.
41///
42/// This is intentionally conservative for geometric predicates and small systems.
43pub const DEFAULT_PIVOT_TOL: f64 = 1e-12;
44
45/// Linear algebra errors.
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
47pub enum LaError {
48    /// The matrix is (numerically) singular.
49    Singular {
50        /// The column where a suitable pivot could not be found.
51        pivot_col: usize,
52    },
53    /// A non-finite value (NaN/∞) was encountered.
54    NonFinite {
55        /// The column being processed when non-finite values were detected.
56        pivot_col: usize,
57    },
58}
59
60impl fmt::Display for LaError {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        match *self {
63            Self::Singular { pivot_col } => {
64                write!(f, "singular matrix at pivot column {pivot_col}")
65            }
66            Self::NonFinite { pivot_col } => {
67                write!(
68                    f,
69                    "non-finite value encountered at pivot column {pivot_col}"
70                )
71            }
72        }
73    }
74}
75
76impl std::error::Error for LaError {}
77
78pub use lu::Lu;
79pub use matrix::Matrix;
80pub use vector::Vector;
81
82/// Common imports for ergonomic usage.
83///
84/// This prelude re-exports the primary types and constants: [`Matrix`], [`Vector`], [`Lu`],
85/// [`LaError`], and [`DEFAULT_PIVOT_TOL`].
86pub mod prelude {
87    pub use crate::{DEFAULT_PIVOT_TOL, LaError, Lu, Matrix, Vector};
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    use approx::assert_abs_diff_eq;
95
96    #[test]
97    fn default_pivot_tol_is_expected() {
98        assert_abs_diff_eq!(DEFAULT_PIVOT_TOL, 1e-12, epsilon = 0.0);
99    }
100
101    #[test]
102    fn laerror_display_formats_singular() {
103        let err = LaError::Singular { pivot_col: 3 };
104        assert_eq!(err.to_string(), "singular matrix at pivot column 3");
105    }
106
107    #[test]
108    fn laerror_display_formats_nonfinite() {
109        let err = LaError::NonFinite { pivot_col: 2 };
110        assert_eq!(
111            err.to_string(),
112            "non-finite value encountered at pivot column 2"
113        );
114    }
115
116    #[test]
117    fn laerror_is_std_error_with_no_source() {
118        let err = LaError::Singular { pivot_col: 0 };
119        let e: &dyn std::error::Error = &err;
120        assert!(e.source().is_none());
121    }
122
123    #[test]
124    fn prelude_reexports_compile_and_work() {
125        use crate::prelude::*;
126
127        // Use the items so we know they are in scope and usable.
128        let m = Matrix::<2>::identity();
129        let v = Vector::<2>::new([1.0, 2.0]);
130        let _ = m.lu(DEFAULT_PIVOT_TOL).unwrap().solve_vec(v).unwrap();
131    }
132}