pub(crate) fn lu_factor_in_place(a: &mut [f64], n: usize, perm: &mut Vec<usize>) -> bool {
debug_assert_eq!(a.len(), n * n);
perm.clear();
perm.extend(0..n);
for k in 0..n {
let mut pivot_row = k;
let mut pivot_mag = a[k * n + k].abs();
for i in k + 1..n {
let mag = a[i * n + k].abs();
if mag > pivot_mag {
pivot_mag = mag;
pivot_row = i;
}
}
if pivot_mag == 0.0 {
return false; }
if pivot_row != k {
for col in 0..n {
a.swap(k * n + col, pivot_row * n + col);
}
perm.swap(k, pivot_row);
}
let pivot = a[k * n + k];
for i in k + 1..n {
let factor = a[i * n + k] / pivot;
a[i * n + k] = factor; for col in k + 1..n {
a[i * n + col] -= factor * a[k * n + col];
}
}
}
true
}
#[allow(clippy::needless_range_loop)]
pub(crate) fn lu_solve_into(lu: &[f64], perm: &[usize], n: usize, b: &[f64], out: &mut Vec<f64>) {
debug_assert_eq!(b.len(), n);
debug_assert_eq!(lu.len(), n * n);
out.clear();
out.extend((0..n).map(|i| b[perm[i]]));
for i in 0..n {
let mut sum = out[i];
for j in 0..i {
sum -= lu[i * n + j] * out[j];
}
out[i] = sum;
}
for i in (0..n).rev() {
let mut sum = out[i];
for j in i + 1..n {
sum -= lu[i * n + j] * out[j];
}
out[i] = sum / lu[i * n + i];
}
}
pub(crate) struct LuFactorization {
lu: Vec<f64>,
perm: Vec<usize>,
n: usize,
}
impl LuFactorization {
pub(crate) fn factor(mut a: Vec<f64>, n: usize) -> Option<LuFactorization> {
let mut perm: Vec<usize> = Vec::with_capacity(n);
if lu_factor_in_place(&mut a, n, &mut perm) {
Some(LuFactorization { lu: a, perm, n })
} else {
None
}
}
#[cfg(test)]
pub(crate) fn solve(&self, b: &[f64]) -> Vec<f64> {
let mut out = Vec::with_capacity(self.n);
lu_solve_into(&self.lu, &self.perm, self.n, b, &mut out);
out
}
pub(crate) fn solve_into(&self, b: &[f64], out: &mut Vec<f64>) {
lu_solve_into(&self.lu, &self.perm, self.n, b, out);
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn solves_a_known_system() {
let a = vec![2.0, 1.0, 1.0, 4.0, -6.0, 0.0, -2.0, 7.0, 2.0];
let lu = LuFactorization::factor(a, 3).unwrap();
let x = lu.solve(&[5.0, -2.0, 9.0]);
assert_relative_eq!(x[0], 1.0, epsilon = 1e-12);
assert_relative_eq!(x[1], 1.0, epsilon = 1e-12);
assert_relative_eq!(x[2], 2.0, epsilon = 1e-12);
}
#[test]
fn factor_once_solve_many() {
let a = vec![3.0, 0.0, 0.0, 4.0]; let lu = LuFactorization::factor(a, 2).unwrap();
let x1 = lu.solve(&[6.0, 8.0]);
let x2 = lu.solve(&[3.0, 4.0]);
assert_relative_eq!(x1[0], 2.0, epsilon = 1e-12);
assert_relative_eq!(x1[1], 2.0, epsilon = 1e-12);
assert_relative_eq!(x2[0], 1.0, epsilon = 1e-12);
assert_relative_eq!(x2[1], 1.0, epsilon = 1e-12);
}
#[test]
fn needs_pivoting() {
let a = vec![0.0, 1.0, 1.0, 0.0]; let lu = LuFactorization::factor(a, 2).unwrap();
let x = lu.solve(&[3.0, 7.0]);
assert_relative_eq!(x[0], 7.0, epsilon = 1e-12);
assert_relative_eq!(x[1], 3.0, epsilon = 1e-12);
}
#[test]
fn detects_singular() {
let a = vec![1.0, 2.0, 2.0, 4.0]; assert!(LuFactorization::factor(a, 2).is_none());
}
}