-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlib.rs
More file actions
194 lines (169 loc) · 6.41 KB
/
lib.rs
File metadata and controls
194 lines (169 loc) · 6.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
#![forbid(unsafe_code)]
#![warn(missing_docs)]
#![doc = include_str!("../README.md")]
#[cfg(doc)]
mod readme_doctests {
//! Executable versions of README examples.
/// ```rust
/// use la_stack::prelude::*;
///
/// // This system requires pivoting (a[0][0] = 0), so it's a good LU demo.
/// let a = Matrix::<5>::from_rows([
/// [0.0, 1.0, 1.0, 1.0, 1.0],
/// [1.0, 0.0, 1.0, 1.0, 1.0],
/// [1.0, 1.0, 0.0, 1.0, 1.0],
/// [1.0, 1.0, 1.0, 0.0, 1.0],
/// [1.0, 1.0, 1.0, 1.0, 0.0],
/// ]);
///
/// let b = Vector::<5>::new([14.0, 13.0, 12.0, 11.0, 10.0]);
///
/// let lu = a.lu(DEFAULT_PIVOT_TOL).unwrap();
/// let x = lu.solve_vec(b).unwrap().into_array();
///
/// // Floating-point rounding is expected; compare with a tolerance.
/// let expected = [1.0, 2.0, 3.0, 4.0, 5.0];
/// for (x_i, e_i) in x.iter().zip(expected.iter()) {
/// assert!((*x_i - *e_i).abs() <= 1e-12);
/// }
/// ```
fn solve_5x5_example() {}
/// ```rust
/// use la_stack::prelude::*;
///
/// // This matrix is symmetric positive-definite (A = L*L^T) so LDLT works without pivoting.
/// let a = Matrix::<5>::from_rows([
/// [1.0, 1.0, 0.0, 0.0, 0.0],
/// [1.0, 2.0, 1.0, 0.0, 0.0],
/// [0.0, 1.0, 2.0, 1.0, 0.0],
/// [0.0, 0.0, 1.0, 2.0, 1.0],
/// [0.0, 0.0, 0.0, 1.0, 2.0],
/// ]);
///
/// let det = a.ldlt(DEFAULT_SINGULAR_TOL).unwrap().det();
/// assert!((det - 1.0).abs() <= 1e-12);
/// ```
fn det_5x5_ldlt_example() {}
}
#[cfg(feature = "exact")]
mod exact;
mod ldlt;
mod lu;
mod matrix;
mod vector;
use core::fmt;
// ---------------------------------------------------------------------------
// Error-bound constants for determinant error analysis.
//
// These constants bound the absolute error of `det_direct()` relative to the
// *permanent* (sum of absolute products in the Leibniz expansion). The
// constants are conservative over-estimates following Shewchuk's methodology.
//
// These are NOT feature-gated because they use pure f64 arithmetic and are
// useful for adaptive-precision logic even without the `exact` feature.
// ---------------------------------------------------------------------------
const EPS: f64 = f64::EPSILON; // 2^-52
/// Error coefficient for D=2 determinant error bound.
///
/// Accounts for one f64 multiply + one FMA → 2 rounding events.
/// Used in computing the absolute error bound for 2×2 determinants.
pub const ERR_COEFF_2: f64 = 3.0 * EPS + 16.0 * EPS * EPS;
/// Error coefficient for D=3 determinant error bound.
///
/// Accounts for three 2×2 FMA minors + nested FMA combination.
/// Used in computing the absolute error bound for 3×3 determinants.
pub const ERR_COEFF_3: f64 = 8.0 * EPS + 64.0 * EPS * EPS;
/// Error coefficient for D=4 determinant error bound.
///
/// Accounts for six hoisted 2×2 minors → four 3×3 cofactors → FMA row combination.
/// Used in computing the absolute error bound for 4×4 determinants.
pub const ERR_COEFF_4: f64 = 12.0 * EPS + 128.0 * EPS * EPS;
/// Default absolute threshold used for singularity/degeneracy detection.
///
/// This is intentionally conservative for geometric predicates and small systems.
///
/// Conceptually, this is an absolute bound for deciding when a scalar should be treated
/// as "numerically zero" (e.g. LU pivots, LDLT diagonal entries).
pub const DEFAULT_SINGULAR_TOL: f64 = 1e-12;
/// Default absolute pivot magnitude threshold used for LU pivot selection / singularity detection.
///
/// This name is kept for backwards compatibility; prefer [`DEFAULT_SINGULAR_TOL`] when the
/// tolerance is not specifically about pivot selection.
pub const DEFAULT_PIVOT_TOL: f64 = DEFAULT_SINGULAR_TOL;
/// Linear algebra errors.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LaError {
/// The matrix is (numerically) singular.
Singular {
/// The factorization column/step where a suitable pivot/diagonal could not be found.
pivot_col: usize,
},
/// A non-finite value (NaN/∞) was encountered.
NonFinite {
/// The column where a non-finite value was detected.
col: usize,
},
}
impl fmt::Display for LaError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::Singular { pivot_col } => {
write!(f, "singular matrix at pivot column {pivot_col}")
}
Self::NonFinite { col } => {
write!(f, "non-finite value encountered at column {col}")
}
}
}
}
impl std::error::Error for LaError {}
pub use ldlt::Ldlt;
pub use lu::Lu;
pub use matrix::Matrix;
pub use vector::Vector;
/// Common imports for ergonomic usage.
///
/// This prelude re-exports the primary types and constants: [`Matrix`], [`Vector`], [`Lu`],
/// [`Ldlt`], [`LaError`], [`DEFAULT_PIVOT_TOL`], [`DEFAULT_SINGULAR_TOL`], and the determinant
/// error bound coefficients [`ERR_COEFF_2`], [`ERR_COEFF_3`], and [`ERR_COEFF_4`].
pub mod prelude {
pub use crate::{
DEFAULT_PIVOT_TOL, DEFAULT_SINGULAR_TOL, ERR_COEFF_2, ERR_COEFF_3, ERR_COEFF_4, LaError,
Ldlt, Lu, Matrix, Vector,
};
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
fn default_singular_tol_is_expected() {
assert_abs_diff_eq!(DEFAULT_SINGULAR_TOL, 1e-12, epsilon = 0.0);
assert_abs_diff_eq!(DEFAULT_PIVOT_TOL, DEFAULT_SINGULAR_TOL, epsilon = 0.0);
}
#[test]
fn laerror_display_formats_singular() {
let err = LaError::Singular { pivot_col: 3 };
assert_eq!(err.to_string(), "singular matrix at pivot column 3");
}
#[test]
fn laerror_display_formats_nonfinite() {
let err = LaError::NonFinite { col: 2 };
assert_eq!(err.to_string(), "non-finite value encountered at column 2");
}
#[test]
fn laerror_is_std_error_with_no_source() {
let err = LaError::Singular { pivot_col: 0 };
let e: &dyn std::error::Error = &err;
assert!(e.source().is_none());
}
#[test]
fn prelude_reexports_compile_and_work() {
use crate::prelude::*;
// Use the items so we know they are in scope and usable.
let m = Matrix::<2>::identity();
let v = Vector::<2>::new([1.0, 2.0]);
let _ = m.lu(DEFAULT_PIVOT_TOL).unwrap().solve_vec(v).unwrap();
let _ = m.ldlt(DEFAULT_SINGULAR_TOL).unwrap().solve_vec(v).unwrap();
}
}