use crate::sparse::SparseMatrix;
use crate::Scalar;
use faer::{ComplexField, Conjugate, Entity, SimpleEntity};
use numra_core::LinalgError;
pub trait Preconditioner<S: Scalar> {
fn apply(&self, r: &[S], z: &mut [S]);
}
pub struct IdentityPreconditioner;
impl<S: Scalar> Preconditioner<S> for IdentityPreconditioner {
fn apply(&self, r: &[S], z: &mut [S]) {
z.copy_from_slice(r);
}
}
pub struct Jacobi<S: Scalar> {
inv_diag: Vec<S>,
}
impl<S: Scalar + SimpleEntity + Conjugate<Canonical = S> + ComplexField + Entity> Jacobi<S> {
pub fn new(a: &SparseMatrix<S>) -> Self {
let n = a.nrows();
let mut inv_diag = Vec::with_capacity(n);
for i in 0..n {
let d = a.get(i, i);
if d.abs() > S::EPSILON {
inv_diag.push(S::ONE / d);
} else {
inv_diag.push(S::ONE);
}
}
Self { inv_diag }
}
}
impl<S: Scalar> Preconditioner<S> for Jacobi<S> {
fn apply(&self, r: &[S], z: &mut [S]) {
for i in 0..r.len() {
z[i] = self.inv_diag[i] * r[i];
}
}
}
pub struct Ilu0<S: Scalar> {
l: Vec<Vec<S>>,
u: Vec<Vec<S>>,
n: usize,
}
impl<S: Scalar + SimpleEntity + Conjugate<Canonical = S> + ComplexField + Entity> Ilu0<S> {
pub fn new(a: &SparseMatrix<S>) -> Result<Self, LinalgError> {
let n = a.nrows();
if n != a.ncols() {
return Err(LinalgError::NotSquare {
nrows: n,
ncols: a.ncols(),
});
}
let mut pattern = vec![vec![false; n]; n];
let col_ptrs = a.col_ptrs();
let row_indices = a.row_indices();
for j in 0..n {
for idx in col_ptrs[j]..col_ptrs[j + 1] {
pattern[row_indices[idx]][j] = true;
}
}
let mut m = vec![vec![S::ZERO; n]; n];
for i in 0..n {
for j in 0..n {
if pattern[i][j] {
m[i][j] = a.get(i, j);
}
}
}
for i in 1..n {
for k in 0..i {
if !pattern[i][k] {
continue;
}
if m[k][k].abs() < S::EPSILON {
return Err(LinalgError::Singular { step: k });
}
m[i][k] = m[i][k] / m[k][k];
for j in (k + 1)..n {
if pattern[i][j] {
m[i][j] = m[i][j] - m[i][k] * m[k][j];
}
}
}
}
let mut l = vec![vec![S::ZERO; n]; n];
let mut u = vec![vec![S::ZERO; n]; n];
for i in 0..n {
l[i][i] = S::ONE;
for j in 0..i {
l[i][j] = m[i][j];
}
for j in i..n {
u[i][j] = m[i][j];
}
}
Ok(Self { l, u, n })
}
}
impl<S: Scalar> Preconditioner<S> for Ilu0<S> {
fn apply(&self, r: &[S], z: &mut [S]) {
let n = self.n;
let mut y = vec![S::ZERO; n];
for i in 0..n {
let mut sum = r[i];
for (j, yj) in y.iter().enumerate().take(i) {
sum -= self.l[i][j] * *yj;
}
y[i] = sum; }
for i in (0..n).rev() {
let mut sum = y[i];
for (j, zj) in z.iter().enumerate().take(n).skip(i + 1) {
sum -= self.u[i][j] * *zj;
}
z[i] = sum / self.u[i][i];
}
}
}
pub struct Ssor<S: Scalar> {
diag: Vec<S>,
lower: Vec<Vec<(usize, S)>>, upper: Vec<Vec<(usize, S)>>, omega: S,
n: usize,
}
impl<S: Scalar + SimpleEntity + Conjugate<Canonical = S> + ComplexField + Entity> Ssor<S> {
pub fn new(a: &SparseMatrix<S>, omega: S) -> Self {
let n = a.nrows();
let col_ptrs = a.col_ptrs();
let row_indices = a.row_indices();
let values = a.values();
let mut diag = vec![S::ZERO; n];
let mut lower: Vec<Vec<(usize, S)>> = vec![Vec::new(); n];
let mut upper: Vec<Vec<(usize, S)>> = vec![Vec::new(); n];
for j in 0..n {
for idx in col_ptrs[j]..col_ptrs[j + 1] {
let i = row_indices[idx];
let v = values[idx];
if i == j {
diag[i] = v;
} else if i > j {
lower[i].push((j, v));
} else {
upper[i].push((j, v));
}
}
}
Self {
diag,
lower,
upper,
omega,
n,
}
}
}
impl<S: Scalar> Preconditioner<S> for Ssor<S> {
fn apply(&self, r: &[S], z: &mut [S]) {
let n = self.n;
let w = self.omega;
let mut y = vec![S::ZERO; n];
for i in 0..n {
let mut sum = r[i];
for &(j, v) in &self.lower[i] {
sum -= v * y[j];
}
y[i] = w * sum / self.diag[i];
}
for i in (0..n).rev() {
let mut sum = self.diag[i] / w * y[i];
for &(j, v) in &self.upper[i] {
sum -= v * z[j];
}
z[i] = w * sum / self.diag[i];
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn spd_3x3() -> SparseMatrix<f64> {
let triplets = vec![
(0, 0, 4.0),
(0, 1, 1.0),
(1, 0, 1.0),
(1, 1, 4.0),
(1, 2, 1.0),
(2, 1, 1.0),
(2, 2, 4.0),
];
SparseMatrix::from_triplets(3, 3, &triplets).unwrap()
}
#[test]
fn test_jacobi_apply() {
let a = spd_3x3();
let jac = Jacobi::new(&a);
let r = [8.0, 12.0, 8.0];
let mut z = [0.0; 3];
jac.apply(&r, &mut z);
assert!((z[0] - 2.0).abs() < 1e-14);
assert!((z[1] - 3.0).abs() < 1e-14);
assert!((z[2] - 2.0).abs() < 1e-14);
}
#[test]
fn test_ilu0_exact_on_dense() {
let triplets = vec![
(0, 0, 4.0),
(0, 1, 1.0),
(1, 0, 1.0),
(1, 1, 4.0),
(1, 2, 1.0),
(2, 1, 1.0),
(2, 2, 4.0),
];
let a = SparseMatrix::from_triplets(3, 3, &triplets).unwrap();
let ilu = Ilu0::new(&a).unwrap();
let b = [5.0, 6.0, 5.0];
let mut z = [0.0; 3];
ilu.apply(&b, &mut z);
let az = a.mul_vec(&z).unwrap();
for i in 0..3 {
assert!(
(az[i] - b[i]).abs() < 1e-10,
"ILU residual at {}: {} vs {}",
i,
az[i],
b[i]
);
}
}
#[test]
fn test_ssor_reduces_residual() {
let a = spd_3x3();
let ssor = Ssor::new(&a, 1.0);
let b = [5.0, 6.0, 5.0];
let mut z = [0.0; 3];
ssor.apply(&b, &mut z);
let norm: f64 = z.iter().map(|x| x * x).sum::<f64>().sqrt();
assert!(norm > 0.1, "SSOR output should be nonzero");
}
#[test]
fn test_identity_preconditioner() {
let r = [1.0, 2.0, 3.0];
let mut z = [0.0; 3];
IdentityPreconditioner.apply(&r, &mut z);
assert_eq!(z, r);
}
}