use super::N_ODES;
#[allow(
clippy::float_cmp,
reason = "exact zero-pivot test mirrors DGEFA's fpclassify(.)==FP_ZERO singular-column check"
)]
pub(crate) fn lu_factor(
a: &mut [[f64; N_ODES]; N_ODES],
pivots: &mut [usize; N_ODES],
) -> Result<(), usize> {
let n = N_ODES;
let mut info: Option<usize> = None;
for k in 0..n - 1 {
let mut l = k;
let mut maxv = a[k][k].abs();
for i in (k + 1)..n {
let v = a[i][k].abs();
if v > maxv {
maxv = v;
l = i;
}
}
pivots[k] = l;
if a[l][k] == 0.0 {
info.get_or_insert(k);
continue;
}
if l != k {
let t = a[l][k];
a[l][k] = a[k][k];
a[k][k] = t;
}
let t = -1.0 / a[k][k];
for i in (k + 1)..n {
a[i][k] *= t;
}
for j in (k + 1)..n {
let t = a[l][j];
if l != k {
a[l][j] = a[k][j];
a[k][j] = t;
}
for i in (k + 1)..n {
a[i][j] += t * a[i][k];
}
}
}
pivots[n - 1] = n - 1;
if a[n - 1][n - 1] == 0.0 {
info.get_or_insert(n - 1);
}
match info {
Some(col) => Err(col),
None => Ok(()),
}
}
pub(crate) fn lu_solve(
a: &[[f64; N_ODES]; N_ODES],
pivots: &[usize; N_ODES],
y: &mut [f64; N_ODES],
) {
let n = N_ODES;
for k in 0..n - 1 {
let l = pivots[k];
let t = y[l];
if l != k {
y[l] = y[k];
y[k] = t;
}
for i in (k + 1)..n {
y[i] += t * a[i][k];
}
}
for k in (0..n).rev() {
y[k] /= a[k][k];
let t = -y[k];
for i in 0..k {
y[i] += t * a[i][k];
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn matvec(a: &[[f64; N_ODES]; N_ODES], x: &[f64; N_ODES]) -> [f64; N_ODES] {
let mut out = [0.0; N_ODES];
for i in 0..N_ODES {
for j in 0..N_ODES {
out[i] += a[i][j] * x[j];
}
}
out
}
#[test]
fn lu_solves_diagonally_dominant_system() {
let mut a = [[0.0_f64; N_ODES]; N_ODES];
for i in 0..N_ODES {
for j in 0..N_ODES {
#[allow(
clippy::cast_precision_loss,
reason = "tiny integer indices i,j ≤ 6 are exact in f64"
)]
let v = if i == j {
4.0 + i as f64
} else {
0.1 * (i as f64 - j as f64)
};
a[i][j] = v;
}
}
let x_true = [1.0, -2.0, 3.0, 0.5, -1.5, 2.25];
let b = matvec(&a, &x_true);
let mut lu = a;
let mut pivots = [0usize; N_ODES];
lu_factor(&mut lu, &mut pivots).expect("non-singular");
let mut y = b;
lu_solve(&lu, &pivots, &mut y);
for i in 0..N_ODES {
assert!(
(y[i] - x_true[i]).abs() < 1e-12,
"LU solve component {i}: got {}, want {} (residual {:e})",
y[i],
x_true[i],
(y[i] - x_true[i]).abs()
);
}
}
#[test]
fn lu_solves_system_requiring_a_row_swap() {
let mut a = [[0.0_f64; N_ODES]; N_ODES];
for i in 0..N_ODES {
a[i][i] = 1.0;
}
a[0][0] = 0.01;
a[3][0] = 7.0;
a[0][3] = 2.0;
a[3][3] = 1.0;
let x_true = [2.0, 1.0, -1.0, 4.0, -3.0, 0.7];
let b = matvec(&a, &x_true);
let mut lu = a;
let mut pivots = [0usize; N_ODES];
lu_factor(&mut lu, &mut pivots).expect("non-singular");
let mut y = b;
lu_solve(&lu, &pivots, &mut y);
for i in 0..N_ODES {
assert!(
(y[i] - x_true[i]).abs() < 1e-12,
"LU solve (with swap) component {i}: got {}, want {}",
y[i],
x_true[i]
);
}
}
#[test]
fn lu_factor_flags_singular_matrix() {
let mut a = [[0.0_f64; N_ODES]; N_ODES];
for i in 1..N_ODES {
a[i][i] = 1.0;
}
let mut pivots = [0usize; N_ODES];
assert!(
lu_factor(&mut a, &mut pivots).is_err(),
"a matrix with a zero column must be reported singular"
);
}
}