pub(crate) struct BandLu {
n: usize,
bw: usize,
a: Vec<f64>,
stride: usize,
factored: bool,
}
impl BandLu {
pub(crate) fn zeros(n: usize, bw: usize) -> Self {
let stride = 2 * bw + 1;
BandLu {
n,
bw,
a: vec![0.0; n.saturating_mul(stride)],
stride,
factored: false,
}
}
#[inline]
fn idx(&self, i: usize, j: usize) -> usize {
debug_assert!(i.abs_diff(j) <= self.bw, "entry outside the band");
i * self.stride + (j + self.bw - i)
}
pub(crate) fn add(&mut self, i: usize, j: usize, val: f64) {
let k = self.idx(i, j);
self.a[k] += val;
}
pub(crate) fn factor(&mut self) -> bool {
let (n, bw, stride) = (self.n, self.bw, self.stride);
let scale = (0..n)
.map(|k| self.a[k * stride + bw].abs())
.fold(0.0_f64, f64::max);
let pivot_floor = 1e-10 * scale;
for k in 0..n {
let piv = self.a[k * stride + bw]; if !piv.is_finite() || piv.abs() <= pivot_floor {
return false;
}
let ihi = (k + bw).min(n - 1);
for i in (k + 1)..=ihi {
let f = self.a[i * stride + (k + bw - i)] / piv;
if !f.is_finite() {
return false;
}
self.a[i * stride + (k + bw - i)] = f; if f == 0.0 {
continue;
}
let jhi = (k + bw).min(n - 1);
for j in (k + 1)..=jhi {
let akj = self.a[k * stride + (j + bw - k)];
self.a[i * stride + (j + bw - i)] -= f * akj;
}
}
}
self.factored = true;
true
}
#[allow(clippy::needless_range_loop)]
pub(crate) fn solve_into(&self, b: &[f64], out: &mut Vec<f64>) {
debug_assert!(self.factored, "solve before a successful factor");
debug_assert_eq!(b.len(), self.n);
let (n, bw, stride) = (self.n, self.bw, self.stride);
out.clear();
out.extend_from_slice(b);
for i in 0..n {
let klo = i.saturating_sub(bw);
let mut sum = out[i];
for k in klo..i {
sum -= self.a[i * stride + (k + bw - i)] * out[k];
}
out[i] = sum;
}
for i in (0..n).rev() {
let khi = (i + bw).min(n - 1);
let mut sum = out[i];
for k in (i + 1)..=khi {
sum -= self.a[i * stride + (k + bw - i)] * out[k];
}
out[i] = sum / self.a[i * stride + bw];
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn solves_a_known_system() {
let mut m = BandLu::zeros(3, 1);
for (i, v) in [4.0, 4.0, 4.0].into_iter().enumerate() {
m.add(i, i, v);
}
m.add(1, 0, 1.0);
m.add(0, 1, 1.0);
m.add(2, 1, 1.0);
m.add(1, 2, 1.0);
assert!(m.factor());
let mut x = Vec::new();
m.solve_into(&[6.0, 12.0, 14.0], &mut x);
assert_relative_eq!(x[0], 1.0, epsilon = 1e-12);
assert_relative_eq!(x[1], 2.0, epsilon = 1e-12);
assert_relative_eq!(x[2], 3.0, epsilon = 1e-12);
}
#[test]
fn factor_once_solve_many() {
let mut m = BandLu::zeros(2, 1);
m.add(0, 0, 3.0);
m.add(1, 1, 4.0);
assert!(m.factor());
let mut x = Vec::new();
m.solve_into(&[6.0, 8.0], &mut x);
assert_relative_eq!(x[0], 2.0, epsilon = 1e-12);
assert_relative_eq!(x[1], 2.0, epsilon = 1e-12);
m.solve_into(&[3.0, 4.0], &mut x);
assert_relative_eq!(x[0], 1.0, epsilon = 1e-12);
assert_relative_eq!(x[1], 1.0, epsilon = 1e-12);
}
#[test]
fn solves_asymmetric_system() {
let mut m = BandLu::zeros(2, 1);
m.add(0, 0, 3.0);
m.add(0, 1, 1.0);
m.add(1, 0, -4.0);
m.add(1, 1, 2.0);
assert!(m.factor());
let mut x = Vec::new();
m.solve_into(&[5.0, 0.0], &mut x);
assert_relative_eq!(x[0], 1.0, epsilon = 1e-12);
assert_relative_eq!(x[1], 2.0, epsilon = 1e-12);
}
#[test]
#[allow(clippy::needless_range_loop)]
fn solves_pentadiagonal() {
let n = 6;
let mut m = BandLu::zeros(n, 2);
let dense = |i: usize, j: usize| -> f64 {
match i as isize - j as isize {
0 => 6.0,
1 => -1.0, -1 => -1.5, 2 => -0.5,
-2 => -0.25,
_ => 0.0,
}
};
for i in 0..n {
for j in 0..n {
let v = dense(i, j);
if v != 0.0 {
m.add(i, j, v);
}
}
}
assert!(m.factor());
let x_true = [1.0, -2.0, 3.0, 0.5, -1.5, 2.0];
let mut b = [0.0; 6];
for i in 0..n {
for j in 0..n {
b[i] += dense(i, j) * x_true[j];
}
}
let mut x = Vec::new();
m.solve_into(&b, &mut x);
for (got, want) in x.iter().zip(x_true.iter()) {
assert_relative_eq!(got, want, epsilon = 1e-10);
}
}
#[test]
fn detects_zero_pivot() {
let mut m = BandLu::zeros(2, 1);
m.add(0, 0, 0.0);
m.add(0, 1, 1.0);
m.add(1, 0, 1.0);
m.add(1, 1, 1.0);
assert!(!m.factor());
}
}