qualia_core_db/solvers/linear_algebra/
lu.rs1use crate::solvers::SolversError;
12
13#[derive(Debug, Clone)]
15pub struct Lu {
16 pub lu: Vec<f64>,
19 pub pivots: Vec<usize>,
21 pub sign: f64,
23 pub singular: bool,
25 pub n: usize,
26}
27
28impl Lu {
29 pub fn solve(&self, b: &[f64]) -> Option<Vec<f64>> {
35 if self.singular || b.len() != self.n {
36 return None;
37 }
38 let n = self.n;
39 let mut y: Vec<f64> = (0..n).map(|i| b[self.pivots[i]]).collect();
41 for i in 0..n {
43 let mut s = y[i];
44 for j in 0..i {
45 s -= self.lu[i * n + j] * y[j];
46 }
47 y[i] = s;
48 }
49 for i in (0..n).rev() {
51 let mut s = y[i];
52 for j in (i + 1)..n {
53 s -= self.lu[i * n + j] * y[j];
54 }
55 let diag = self.lu[i * n + i];
56 if diag == 0.0 {
57 return None;
58 }
59 y[i] = s / diag;
60 }
61 Some(y)
62 }
63
64 pub fn determinant(&self) -> f64 {
66 if self.singular {
67 return 0.0;
68 }
69 let mut det = self.sign;
70 for i in 0..self.n {
71 det *= self.lu[i * self.n + i];
72 }
73 det
74 }
75}
76
77pub fn lu_decompose(n: usize, data: &[f64]) -> Result<Lu, SolversError> {
81 if n == 0 || data.len() != n * n {
82 return Err(SolversError::InvalidDimension);
83 }
84 let mut a = data.to_vec();
85 let mut pivots: Vec<usize> = (0..n).collect();
86 let mut sign = 1.0_f64;
87 let mut singular = false;
88
89 for col in 0..n {
90 let mut pivot = col;
92 let mut maxv = a[col * n + col].abs();
93 for r in (col + 1)..n {
94 let v = a[r * n + col].abs();
95 if v > maxv {
96 maxv = v;
97 pivot = r;
98 }
99 }
100 if maxv == 0.0 {
101 singular = true;
102 continue; }
104 if pivot != col {
105 for k in 0..n {
106 a.swap(col * n + k, pivot * n + k);
107 }
108 pivots.swap(col, pivot);
109 sign = -sign;
110 }
111 let diag = a[col * n + col];
112 for r in (col + 1)..n {
113 let factor = a[r * n + col] / diag;
114 a[r * n + col] = factor; for k in (col + 1)..n {
116 a[r * n + k] -= factor * a[col * n + k];
117 }
118 }
119 }
120
121 Ok(Lu {
122 lu: a,
123 pivots,
124 sign,
125 singular,
126 n,
127 })
128}
129
130pub fn determinant(n: usize, data: &[f64]) -> Result<f64, SolversError> {
133 Ok(lu_decompose(n, data)?.determinant())
134}
135
136pub fn lu_solve(n: usize, a: &[f64], b: &[f64]) -> Option<Vec<f64>> {
139 lu_decompose(n, a).ok()?.solve(b)
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn determinant_2x2_and_3x3() {
148 assert!((determinant(2, &[1.0, 2.0, 3.0, 4.0]).unwrap() + 2.0).abs() < 1e-12);
150 let d = determinant(3, &[6.0, 1.0, 1.0, 4.0, -2.0, 5.0, 2.0, 8.0, 7.0]).unwrap();
152 assert!((d + 306.0).abs() < 1e-9, "det = {d}");
153 }
154
155 #[test]
156 fn singular_has_zero_determinant() {
157 let sing = lu_decompose(2, &[1.0, 2.0, 2.0, 4.0]).unwrap();
158 assert!(sing.singular && sing.determinant() == 0.0);
159 }
160
161 #[test]
162 fn reconstructs_permuted_a() {
163 let n = 3;
165 let a = [4.0, 3.0, 2.0, 2.0, 1.0, 3.0, 3.0, 2.0, 1.0];
166 let f = lu_decompose(n, &a).unwrap();
167 assert!(!f.singular);
168 let mut l = vec![0.0; n * n];
169 let mut u = vec![0.0; n * n];
170 for i in 0..n {
171 l[i * n + i] = 1.0;
172 for j in 0..n {
173 if j < i {
174 l[i * n + j] = f.lu[i * n + j];
175 } else {
176 u[i * n + j] = f.lu[i * n + j];
177 }
178 }
179 }
180 let mut pa = vec![0.0; n * n];
182 for i in 0..n {
183 for j in 0..n {
184 pa[i * n + j] = a[f.pivots[i] * n + j];
185 }
186 }
187 for i in 0..n {
189 for j in 0..n {
190 let mut s = 0.0;
191 for k in 0..n {
192 s += l[i * n + k] * u[k * n + j];
193 }
194 assert!((s - pa[i * n + j]).abs() < 1e-9);
195 }
196 }
197 }
198
199 #[test]
200 fn rejects_bad_dims() {
201 assert_eq!(
202 determinant(2, &[1.0, 2.0, 3.0]),
203 Err(SolversError::InvalidDimension)
204 );
205 }
206
207 #[test]
208 fn lu_solve_recovers_known_solution() {
209 let a = [2.0, 1.0, 1.0, 1.0, 3.0, 2.0, 1.0, 0.0, 0.0];
211 let x = lu_solve(3, &a, &[4.0, 6.0, 1.0]).unwrap();
212 for xi in &x {
213 assert!((xi - 1.0).abs() < 1e-9, "x = {x:?}");
214 }
215 let a2 = [4.0, 3.0, 6.0, 3.0];
217 let b2 = [10.0, 12.0];
218 let x2 = lu_solve(2, &a2, &b2).unwrap();
219 assert!((4.0 * x2[0] + 3.0 * x2[1] - 10.0).abs() < 1e-9);
220 assert!((6.0 * x2[0] + 3.0 * x2[1] - 12.0).abs() < 1e-9);
221 assert!(lu_solve(2, &[1.0, 2.0, 2.0, 4.0], &[1.0, 2.0]).is_none());
223 }
224}