use super::DimensionMismatch;
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 + PartialEq,
{
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 + PartialEq,
{
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 {
if let Some(p) = (k..n).find(|&r| out_u[r * n + k] != zero)
&& p != k
{
for c in 0..n {
out_u.swap(k * n + c, p * n + c);
}
for c in 0..k {
out_l.swap(k * n + c, p * 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([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(0));
assert_eq!(l, [1.0, 0.0, 1.5, 1.0]);
assert_eq!(u, [4.0, 3.0, 0.0, -1.5]);
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, [4.0, 3.0, 6.0, 3.0]);
}
#[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));
}
}