pub struct ILU0 {
pub n: usize,
pub offsets: Vec<isize>,
pub l_diags: Vec<Vec<f32>>,
pub u_diags: Vec<Vec<f32>>,
}
impl ILU0 {
pub fn new_eps(
n: usize,
offsets: &[isize],
diags: &[Vec<f32>],
eps: f32, ) -> ILU0 {
let nbands = offsets.len();
let mut l_diags = vec![vec![0.0f32; n]; nbands];
let mut u_diags = vec![vec![0.0f32; n]; nbands];
for k in 0..nbands {
for i in 0..n {
if diags[k][i] != 0.0 {
if offsets[k] < 0 {
l_diags[k][i] = diags[k][i];
} else {
u_diags[k][i] = diags[k][i];
}
}
}
}
let main_idx = offsets
.iter()
.position(|&o| o == 0)
.expect("No main diagonal");
for i in 0..n {
for (k, &off) in offsets.iter().enumerate() {
if off >= 0 {
continue;
}
let j = (i as isize + off) as isize;
if j < 0 || j >= n as isize {
continue;
}
let j_usize = j as usize;
let ujj = u_diags[main_idx][j_usize];
if ujj.abs() < 1e-12 {
u_diags[main_idx][j_usize] = eps;
}
l_diags[k][i] /= u_diags[main_idx][j_usize];
}
for (k, &off) in offsets.iter().enumerate() {
if off < 0 {
continue;
}
let j = i as isize + off;
if j < 0 || j >= n as isize {
continue;
}
let mut sum = 0.0f32;
for (m, &off_m) in offsets.iter().enumerate() {
if off_m >= 0 {
continue;
}
let kcol = i as isize + off_m;
if kcol < 0 || kcol >= n as isize {
continue;
}
let kcol_usize = kcol as usize;
let offu = j as isize - kcol as isize;
if let Some(ku) = offsets.iter().position(|&o| o == offu) {
sum += l_diags[m][i] * u_diags[ku][kcol_usize];
}
}
u_diags[k][i] -= sum;
}
u_diags[main_idx][i] += eps;
}
Self {
n,
offsets: offsets.to_vec(),
l_diags,
u_diags,
}
}
pub fn new(n: usize, offsets: &[isize], diags: &[Vec<f32>]) -> ILU0 {
let idx_main = offsets
.iter()
.position(|&o| o == 0)
.expect("main diagonal offset not found");
let mut l_diags = vec![vec![0.0f32; n]; offsets.len()];
let mut u_diags = vec![vec![0.0f32; n]; offsets.len()];
for (k, diag) in diags.iter().enumerate() {
u_diags[k].clone_from(diag);
}
for i in 0..n {
for (k, &off) in offsets.iter().enumerate() {
if off >= 0 {
continue;
} let j = i as isize + off;
if j < 0 {
continue;
}
let j = j as usize;
let u_jj = u_diags[idx_main][j];
if u_jj.abs() < 1e-30 {
continue;
}
let l_ij = u_diags[k][i] / u_jj;
l_diags[k][i] = l_ij;
for (m, &offu) in offsets.iter().enumerate() {
if offu < 0 {
continue;
} let jj = j as isize + offu;
let ii = i as isize + offu;
if ii >= 0 && ii < n as isize && jj >= 0 && jj < n as isize {
u_diags[m][i] -= l_ij * u_diags[m][j];
}
}
}
}
Self {
n,
offsets: offsets.to_vec(),
l_diags,
u_diags,
}
}
}
pub fn ilu0_apply(ilu: &ILU0, r: &[f32], z: &mut [f32]) {
let n = ilu.n;
let idx_main = ilu
.offsets
.iter()
.position(|&o| o == 0)
.expect("main diagonal offset not found");
let mut y = vec![0.0f32; n];
for i in 0..n {
let mut sum = r[i];
for (k, &off) in ilu.offsets.iter().enumerate() {
if off >= 0 {
continue;
}
let j = i as isize + off;
if j >= 0 {
sum -= ilu.l_diags[k][i] * y[j as usize];
}
}
y[i] = sum;
}
for i in (0..n).rev() {
let mut sum = y[i];
for (k, &off) in ilu.offsets.iter().enumerate() {
if off <= 0 {
continue;
}
let j = i as isize + off;
if j < n as isize {
sum -= ilu.u_diags[k][i] * z[j as usize];
}
}
let diag = ilu.u_diags[idx_main][i];
z[i] = sum / diag;
}
}
#[cfg(test)]
mod tests {
use super::*;
fn banded_matvec(n: usize, offsets: &[isize], diags: &[Vec<f32>], x: &[f32]) -> Vec<f32> {
let mut y = vec![0.0f32; n];
for i in 0..n {
let mut sum = 0.0f32;
for (k, &off) in offsets.iter().enumerate() {
let j = i as isize + off;
if j >= 0 && j < n as isize {
sum += diags[k][i] * x[j as usize];
}
}
y[i] = sum;
}
y
}
#[test]
fn test_ilu0_apply_identity() {
let n = 5;
let offsets = [0isize];
let diags = vec![vec![1.0f32; n]];
let ilu = ILU0::new(n, &offsets, &diags);
let r = vec![1.0f32, 2.0, 3.0, 4.0, 5.0];
let mut z = vec![0.0f32; n];
ilu0_apply(&ilu, &r, &mut z);
assert!((0..n).all(|i| (z[i] - r[i]).abs() < 1e-6));
}
#[test]
#[allow(non_snake_case)]
fn test_ilu0_apply_tridiagonal() {
let n = 5;
let offsets = [-1isize, 0isize, 1isize];
let d_main = vec![4.0f32; n];
let d_lower = vec![1.0f32; n];
let d_upper = vec![1.0f32; n];
let diags = vec![d_lower.clone(), d_main.clone(), d_upper.clone()];
let ilu = ILU0::new(n, &offsets, &diags);
let r = vec![1.0f32, 2.0, 3.0, 4.0, 5.0];
let mut z = vec![0.0f32; n];
ilu0_apply(&ilu, &r, &mut z);
let Az = banded_matvec(n, &offsets, &diags, &z);
for i in 0..n {
assert!(
(Az[i] - r[i]).abs() < 1e-3,
"row {}: Az={} r={}",
i,
Az[i],
r[i]
);
}
}
}
#[cfg(test)]
mod tests_ilu {
use super::*;
#[allow(non_snake_case)]
fn build_dense_from_ilu(ilu: &ILU0) -> Vec<Vec<f32>> {
let n = ilu.n;
let _idx_main = ilu.offsets.iter().position(|&o| o == 0).unwrap();
let mut M = vec![vec![0.0f32; n]; n];
for i in 0..n {
for (_k, &off) in ilu.offsets.iter().enumerate() {
let j = i as isize + off;
if j < 0 || j >= n as isize {
continue;
}
let _j = j as usize;
}
}
let mut L = vec![vec![0.0f32; n]; n];
let mut U = vec![vec![0.0f32; n]; n];
for i in 0..n {
L[i][i] = 1.0;
}
for (k, &off) in ilu.offsets.iter().enumerate() {
for i in 0..n {
let j = i as isize + off;
if j < 0 || j >= n as isize {
continue;
}
let j = j as usize;
if off < 0 {
L[i][j] = ilu.l_diags[k][i];
} else {
U[i][j] = ilu.u_diags[k][i];
}
}
}
for i in 0..n {
for j in 0..n {
let mut sum = 0.0;
for k in 0..n {
sum += L[i][k] * U[k][j];
}
M[i][j] = sum;
}
}
M
}
#[test]
#[allow(non_snake_case)]
fn test_ilu0_preconditioner_identity() {
let n = 5;
let offsets = [0isize];
let diags = vec![vec![1.0f32; n]];
let ilu = ILU0::new(n, &offsets, &diags);
let r = vec![1.0f32, 2.0, 3.0, 4.0, 5.0];
let mut z = vec![0.0f32; n];
ilu0_apply(&ilu, &r, &mut z);
let M = build_dense_from_ilu(&ilu);
let mut Mz = vec![0.0f32; n];
for i in 0..n {
let mut sum = 0.0;
for j in 0..n {
sum += M[i][j] * z[j];
}
Mz[i] = sum;
}
for i in 0..n {
assert!(
(Mz[i] - r[i]).abs() < 1e-6,
"row {}: Mz={} r={}",
i,
Mz[i],
r[i]
);
}
}
#[test]
fn test_ilu0_sparsity_pattern() {
let n = 5;
let offsets = [-1isize, 0isize, 1isize];
let diags = vec![vec![1.0f32; n], vec![4.0f32; n], vec![1.0f32; n]];
let ilu = ILU0::new(n, &offsets, &diags);
let mut nnz_a = 0;
for (k, &_off) in offsets.iter().enumerate() {
nnz_a += diags[k].iter().filter(|&&v| v != 0.0).count();
}
let mut nnz_lu = 0;
for (k, &_off) in ilu.offsets.iter().enumerate() {
nnz_lu += ilu.l_diags[k].iter().filter(|&&v| v != 0.0).count();
nnz_lu += ilu.u_diags[k].iter().filter(|&&v| v != 0.0).count();
}
println!("nnz_a={} nnz_lu={}", nnz_a, nnz_lu);
assert!(nnz_lu <= nnz_a * 2, "nnz_lu={} nnz_a={}", nnz_lu, nnz_a);
}
#[test]
#[allow(non_snake_case)]
fn test_ilu0_preconditioner_tridiagonal() {
let n = 5;
let offsets = [-1isize, 0isize, 1isize];
let diags = vec![vec![1.0f32; n], vec![4.0f32; n], vec![1.0f32; n]];
let ilu = ILU0::new(n, &offsets, &diags);
let r = vec![1.0f32, 2.0, 3.0, 4.0, 5.0];
let mut z = vec![0.0f32; n];
ilu0_apply(&ilu, &r, &mut z);
let M = build_dense_from_ilu(&ilu);
let mut Mz = vec![0.0f32; n];
for i in 0..n {
let mut sum = 0.0;
for j in 0..n {
sum += M[i][j] * z[j];
}
Mz[i] = sum;
}
for i in 0..n {
assert!(
(Mz[i] - r[i]).abs() < 1e-5,
"row {}: Mz={} r={}",
i,
Mz[i],
r[i]
);
}
}
#[test]
fn test_ilu0_sparsity_pattern_nnz_exext() {
let n = 5;
let offsets = [-1isize, 0isize, 1isize];
let diags = vec![vec![1.0f32; n], vec![4.0f32; n], vec![1.0f32; n]];
let ilu = ILU0::new(n, &offsets, &diags);
let mut nnz_a = 0;
for (k, &off) in offsets.iter().enumerate() {
for i in 0..n {
let j = i as isize + off;
if j < 0 || j >= n as isize {
continue;
}
if diags[k][i] != 0.0 {
nnz_a += 1;
}
}
}
let mut nnz_lu = 0;
for (k, &off) in ilu.offsets.iter().enumerate() {
for i in 0..n {
let j = i as isize + off;
if j < 0 || j >= n as isize {
continue;
}
if off < 0 {
if ilu.l_diags[k][i] != 0.0 {
nnz_lu += 1;
}
} else {
if ilu.u_diags[k][i] != 0.0 {
nnz_lu += 1;
}
}
}
}
println!("nnz_a {} nnz_lu {}", nnz_a, nnz_lu);
assert_eq!(
nnz_a, nnz_lu,
"nnz pattern mismatch: A={} L+U={}",
nnz_a, nnz_lu
);
}
}