use super::{DimensionMismatch, abs};
use crate::scalar::Scalar;
use crate::storage::Storage;
pub fn lu<S, T>(
a: &S,
rows: usize,
cols: usize,
out_l: &mut [T],
out_u: &mut [T],
) -> Result<usize, DimensionMismatch>
where
S: Storage<Item = T>,
T: Scalar + PartialOrd,
{
lu_partial_pivot(a, rows, cols, out_l, out_u)
}
pub fn lu_partial_pivot<S, T>(
a: &S,
rows: usize,
cols: usize,
out_l: &mut [T],
out_u: &mut [T],
) -> Result<usize, DimensionMismatch>
where
S: Storage<Item = T>,
T: Scalar + PartialOrd,
{
if rows != cols {
return Err(DimensionMismatch);
}
let n = rows;
let len = n * n;
if a.len() != len || out_l.len() != len || out_u.len() != len {
return Err(DimensionMismatch);
}
for (i, slot) in out_u.iter_mut().enumerate() {
let Some(&x) = a.get(i) else {
return Err(DimensionMismatch);
};
*slot = x;
}
let zero = T::zero();
let one = T::one();
for slot in out_l.iter_mut() {
*slot = zero;
}
for i in 0..n {
out_l[i * n + i] = one;
}
let mut swap_count = 0;
for k in 0..n {
let mut best_row = k;
let mut best_abs = abs(out_u[k * n + k]);
for r in (k + 1)..n {
let candidate_abs = abs(out_u[r * n + k]);
if candidate_abs > best_abs {
best_abs = candidate_abs;
best_row = r;
}
}
if best_row != k {
for c in 0..n {
out_u.swap(k * n + c, best_row * n + c);
}
for c in 0..k {
out_l.swap(k * n + c, best_row * n + c);
}
swap_count += 1;
}
let pivot = out_u[k * n + k];
if pivot == zero {
continue;
}
for i in (k + 1)..n {
let factor = out_u[i * n + k].div(pivot);
out_l[i * n + k] = factor;
for c in k..n {
let term = factor.mul(out_u[k * n + c]);
out_u[i * n + c] = out_u[i * n + c].sub(term);
}
}
}
Ok(swap_count)
}
#[cfg(test)]
mod tests {
use super::{DimensionMismatch, lu, lu_partial_pivot};
use crate::algorithm::matrix::mul_matrix;
use crate::storage::StaticStorage;
#[test]
fn lu_of_known_matrix_with_no_pivoting_needed() {
let a = StaticStorage::new([6.0, 3.0, 4.0, 3.0]);
let mut l = [0.0; 4];
let mut u = [0.0; 4];
assert_eq!(lu(&a, 2, 2, &mut l, &mut u), Ok(0));
assert_eq!(l, [1.0, 0.0, 4.0 / 6.0, 1.0]);
assert_eq!(u, [6.0, 3.0, 0.0, 1.0]);
let mut lu_product = [0.0; 4];
mul_matrix(
&StaticStorage::new(l),
2,
2,
&StaticStorage::new(u),
2,
2,
&mut lu_product,
)
.unwrap();
assert_eq!(lu_product, [6.0, 3.0, 4.0, 3.0]);
}
#[test]
fn lu_pivots_to_the_largest_magnitude_entry_even_when_neither_is_zero() {
let a = StaticStorage::new([4.0, 3.0, 6.0, 3.0]);
let mut l = [0.0; 4];
let mut u = [0.0; 4];
assert_eq!(lu(&a, 2, 2, &mut l, &mut u), Ok(1));
assert_eq!(l, [1.0, 0.0, 4.0 / 6.0, 1.0]);
assert_eq!(u, [6.0, 3.0, 0.0, 1.0]);
let mut lu_product = [0.0; 4];
mul_matrix(
&StaticStorage::new(l),
2,
2,
&StaticStorage::new(u),
2,
2,
&mut lu_product,
)
.unwrap();
assert_eq!(lu_product, [6.0, 3.0, 4.0, 3.0]);
}
#[test]
fn lu_pivots_away_from_a_tiny_nonzero_entry_toward_a_much_larger_one() {
let a = StaticStorage::new([1e-300_f64, 1.0, 1.0, 1.0]);
let mut l = [0.0_f64; 4];
let mut u = [0.0_f64; 4];
assert_eq!(lu(&a, 2, 2, &mut l, &mut u), Ok(1));
assert!(l.iter().all(|x: &f64| x.abs() < 1e10));
}
#[test]
fn lu_pivots_when_a_zero_sits_on_the_diagonal() {
let a = StaticStorage::new([0.0, 1.0, 1.0, 1.0]);
let mut l = [0.0; 4];
let mut u = [0.0; 4];
assert_eq!(lu_partial_pivot(&a, 2, 2, &mut l, &mut u), Ok(1));
assert_eq!(l, [1.0, 0.0, 0.0, 1.0]);
assert_eq!(u, [1.0, 1.0, 0.0, 1.0]);
let mut lu_product = [0.0; 4];
mul_matrix(
&StaticStorage::new(l),
2,
2,
&StaticStorage::new(u),
2,
2,
&mut lu_product,
)
.unwrap();
assert_eq!(lu_product, [1.0, 1.0, 0.0, 1.0]);
}
#[test]
fn lu_of_singular_matrix_leaves_a_zero_pivot_instead_of_erroring() {
let a = StaticStorage::new([0.0, 0.0, 0.0, 5.0]);
let mut l = [0.0; 4];
let mut u = [0.0; 4];
assert_eq!(lu(&a, 2, 2, &mut l, &mut u), Ok(0));
assert_eq!(l, [1.0, 0.0, 0.0, 1.0]);
assert_eq!(u, [0.0, 0.0, 0.0, 5.0]);
let mut lu_product = [0.0; 4];
mul_matrix(
&StaticStorage::new(l),
2,
2,
&StaticStorage::new(u),
2,
2,
&mut lu_product,
)
.unwrap();
assert_eq!(lu_product, [0.0, 0.0, 0.0, 5.0]);
}
#[test]
fn lu_of_non_square_matrix_is_an_error_not_a_panic() {
let a = StaticStorage::new([1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
let mut l = [0.0; 9];
let mut u = [0.0; 9];
assert_eq!(lu(&a, 2, 3, &mut l, &mut u), Err(DimensionMismatch));
}
#[test]
fn lu_mismatched_output_length_is_an_error_not_a_panic() {
let a = StaticStorage::new([1.0, 2.0, 3.0, 4.0]);
let mut l = [0.0; 3];
let mut u = [0.0; 4];
assert_eq!(lu(&a, 2, 2, &mut l, &mut u), Err(DimensionMismatch));
}
}