mod bicgstab;
mod gmres;
mod preconditioner;
pub mod vecops;
#[cfg(test)]
mod hybrid_tests;
pub use bicgstab::{bicgstab, bicgstab_prepared};
pub use gmres::{gmres, gmres_prepared};
pub use preconditioner::{Ilu0Preconditioner, JacobiPreconditioner};
use crate::compute::ComputeBackend;
use crate::ldu_matrix::LduMatrix;
#[derive(Debug, Clone, Copy)]
pub struct KrylovSettings {
pub tolerance: f64,
pub max_iter: usize,
pub restart: usize,
}
impl Default for KrylovSettings {
fn default() -> Self {
Self {
tolerance: 1.0e-8,
max_iter: 1000,
restart: 30,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct KrylovResult {
pub n_iterations: usize,
pub final_residual: f64,
pub converged: bool,
}
pub enum Preconditioner {
Identity,
Jacobi(JacobiPreconditioner),
Ilu0(Ilu0Preconditioner),
}
impl Preconditioner {
pub fn identity() -> Self {
Preconditioner::Identity
}
pub fn jacobi(a: &LduMatrix) -> Self {
Preconditioner::Jacobi(JacobiPreconditioner::new(a))
}
pub fn ilu0(a: &LduMatrix) -> Self {
Preconditioner::Ilu0(Ilu0Preconditioner::new(a))
}
pub fn apply(&self, r: &[f64], z: &mut [f64]) {
self.apply_on(r, z, ComputeBackend::Serial);
}
pub fn apply_on(&self, r: &[f64], z: &mut [f64], backend: ComputeBackend) {
debug_assert_eq!(r.len(), z.len(), "Preconditioner::apply length mismatch");
match self {
Preconditioner::Identity => z.copy_from_slice(r),
Preconditioner::Jacobi(p) => preconditioner::jacobi_apply(p, r, z, backend),
Preconditioner::Ilu0(p) => preconditioner::ilu0_apply(p, r, z, backend),
}
}
}
#[cfg(test)]
mod tests {
use petir::mathf::RealMath;
use super::*;
use crate::matrix::SquareMatrix;
fn dense_of(a: &LduMatrix) -> SquareMatrix {
let mut m = SquareMatrix::new(a.n_cells);
for i in 0..a.n_cells {
m.set(i, i, a.diag[i]);
}
for f in 0..a.n_internal_faces {
let o = a.owner[f];
let n = a.neighbour[f];
m.set(o, n, a.upper[f]);
m.set(n, o, a.lower[f]);
}
m
}
fn spd_tridiag(n: usize, d: f64, e: f64) -> LduMatrix {
let mut owner = Vec::new();
let mut neigh = Vec::new();
for i in 0..n - 1 {
owner.push(i);
neigh.push(i + 1);
}
let mut a = LduMatrix::new(n, owner, neigh);
for i in 0..n {
a.diag[i] = d;
}
for f in 0..a.n_internal_faces {
a.lower[f] = e;
a.upper[f] = e;
}
a
}
fn nonsym_tridiag(n: usize, d: f64, lo: f64, up: f64) -> LduMatrix {
let mut owner = Vec::new();
let mut neigh = Vec::new();
for i in 0..n - 1 {
owner.push(i);
neigh.push(i + 1);
}
let mut a = LduMatrix::new(n, owner, neigh);
for i in 0..n {
a.diag[i] = d;
}
for f in 0..a.n_internal_faces {
a.lower[f] = lo;
a.upper[f] = up;
}
a
}
fn rel_err(x: &[f64], y: &[f64]) -> f64 {
let mut num = 0.0;
let mut den = 0.0;
for i in 0..x.len() {
num += (x[i] - y[i]).r_powi(2);
den += y[i].r_powi(2);
}
(num / den).sqrt()
}
#[test]
fn spd_known_solution() {
let n = 4;
let a = spd_tridiag(n, 2.0, -1.0);
let x_true = vec![1.0, 2.0, 3.0, 4.0];
let b = a.multiply(&x_true);
let settings = KrylovSettings {
tolerance: 1e-10,
max_iter: 200,
restart: 30,
};
let (xb, rb) = bicgstab(&a, &b, None, &Preconditioner::identity(), &settings);
assert!(rb.converged, "bicgstab did not converge: {:?}", rb);
assert!(rel_err(&xb, &x_true) < 1e-7);
let (xg, rg) = gmres(&a, &b, None, &Preconditioner::identity(), &settings);
assert!(rg.converged, "gmres did not converge: {:?}", rg);
assert!(rel_err(&xg, &x_true) < 1e-7);
}
#[test]
fn nonsymmetric_vs_dense_lu() {
let n = 6;
let a = nonsym_tridiag(n, 4.0, -1.0, -2.0); let b: Vec<f64> = (0..n).map(|i| (i as f64) - 2.5).collect();
let dense = dense_of(&a);
let x_ref = dense.solve(&b).expect("dense LU failed");
let settings = KrylovSettings {
tolerance: 1e-8,
max_iter: 500,
restart: 30,
};
for precond in [
Preconditioner::identity(),
Preconditioner::jacobi(&a),
Preconditioner::ilu0(&a),
] {
let (xb, rb) = bicgstab(&a, &b, None, &precond, &settings);
assert!(rb.converged, "bicgstab unconverged: {:?}", rb);
assert!(
rel_err(&xb, &x_ref) < 1e-6,
"bicgstab mismatch vs dense LU: {}",
rel_err(&xb, &x_ref)
);
let (xg, rg) = gmres(&a, &b, None, &precond, &settings);
assert!(rg.converged, "gmres unconverged: {:?}", rg);
assert!(
rel_err(&xg, &x_ref) < 1e-6,
"gmres mismatch vs dense LU: {}",
rel_err(&xg, &x_ref)
);
}
}
#[test]
fn preconditioning_reduces_iterations() {
let n = 40;
let a = nonsym_tridiag(n, 2.05, -1.0, -1.0);
let b = vec![1.0; n];
let settings = KrylovSettings {
tolerance: 1e-8,
max_iter: 2000,
restart: 50,
};
let (_, r_id) = bicgstab(&a, &b, None, &Preconditioner::identity(), &settings);
let (_, r_jac) = bicgstab(&a, &b, None, &Preconditioner::jacobi(&a), &settings);
let (_, r_ilu) = bicgstab(&a, &b, None, &Preconditioner::ilu0(&a), &settings);
assert!(r_id.converged && r_jac.converged && r_ilu.converged);
assert!(
r_ilu.n_iterations <= r_id.n_iterations,
"ILU(0) {} vs Identity {} iters",
r_ilu.n_iterations,
r_id.n_iterations
);
let (_, g_id) = gmres(&a, &b, None, &Preconditioner::identity(), &settings);
let (_, g_ilu) = gmres(&a, &b, None, &Preconditioner::ilu0(&a), &settings);
assert!(g_id.converged && g_ilu.converged);
assert!(g_ilu.n_iterations <= g_id.n_iterations);
}
#[test]
fn zero_rhs() {
let a = spd_tridiag(5, 3.0, -1.0);
let b = vec![0.0; 5];
let s = KrylovSettings::default();
let (xb, rb) = bicgstab(&a, &b, None, &Preconditioner::identity(), &s);
assert!(rb.converged && rb.n_iterations == 0 && xb.iter().all(|&v| v == 0.0));
let (xg, rg) = gmres(&a, &b, None, &Preconditioner::identity(), &s);
assert!(rg.converged && rg.n_iterations == 0 && xg.iter().all(|&v| v == 0.0));
}
#[test]
fn initial_guess() {
let a = spd_tridiag(4, 2.0, -1.0);
let x_true = vec![0.5, 1.5, -2.0, 3.0];
let b = a.multiply(&x_true);
let s = KrylovSettings {
tolerance: 1e-10,
max_iter: 200,
restart: 30,
};
let guess = x_true.clone();
let (x, r) = gmres(&a, &b, Some(&guess), &Preconditioner::ilu0(&a), &s);
assert!(r.converged);
assert!(rel_err(&x, &x_true) < 1e-7);
}
}