use crate::parallel_solver::ParallelPcgSolver;
use crate::solvers::amg::cycle::{AmgHierarchy, CycleKind, v_cycle, w_cycle};
pub trait Preconditioner: Send + Sync {
fn apply(&self, r: &[f64], z: &mut [f64]);
fn n(&self) -> usize;
}
#[derive(Debug, Clone)]
pub struct JacobiPreconditioner {
pub diag_inv: Vec<f64>,
}
impl Preconditioner for JacobiPreconditioner {
fn apply(&self, r: &[f64], z: &mut [f64]) {
for i in 0..r.len() {
z[i] = r[i] * self.diag_inv[i];
}
}
fn n(&self) -> usize {
self.diag_inv.len()
}
}
pub struct AmgPreconditioner {
pub hierarchy: AmgHierarchy,
pub cycle_kind: CycleKind,
pub pcg: ParallelPcgSolver,
}
impl Preconditioner for AmgPreconditioner {
fn apply(&self, r: &[f64], z: &mut [f64]) {
for zi in z.iter_mut() {
*zi = 0.0;
}
match self.cycle_kind {
CycleKind::V => v_cycle(&self.hierarchy, 0, r, z, &self.pcg),
CycleKind::W => w_cycle(&self.hierarchy, 0, r, z, &self.pcg),
}
}
fn n(&self) -> usize {
self.hierarchy.levels[0].a.nrows
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parallel_solver::CsrMatrix;
use crate::solvers::amg::classical::AmgClassical;
fn make_1d_poisson(n: usize) -> CsrMatrix {
let mut row_offsets = vec![0usize; n + 1];
let mut col_indices = Vec::new();
let mut values = Vec::new();
for i in 0..n {
if i > 0 {
col_indices.push(i - 1);
values.push(-1.0);
}
col_indices.push(i);
values.push(2.0);
if i + 1 < n {
col_indices.push(i + 1);
values.push(-1.0);
}
row_offsets[i + 1] = col_indices.len();
}
CsrMatrix {
nrows: n,
ncols: n,
row_offsets,
col_indices,
values,
}
}
#[test]
fn test_jacobi_preconditioner_apply() {
let a = CsrMatrix {
nrows: 4,
ncols: 4,
row_offsets: vec![0, 1, 2, 3, 4],
col_indices: vec![0, 1, 2, 3],
values: vec![2.0, 3.0, 4.0, 5.0],
};
let diag_inv = a.diagonal_preconditioner();
let jac = JacobiPreconditioner { diag_inv };
let r = vec![2.0, 6.0, 8.0, 15.0];
let mut z = vec![0.0f64; 4];
jac.apply(&r, &mut z);
assert!((z[0] - 1.0).abs() < 1e-14, "z[0] = {}", z[0]);
assert!((z[1] - 2.0).abs() < 1e-14, "z[1] = {}", z[1]);
assert!((z[2] - 2.0).abs() < 1e-14, "z[2] = {}", z[2]);
assert!((z[3] - 3.0).abs() < 1e-14, "z[3] = {}", z[3]);
}
#[test]
fn test_amg_preconditioner_reduces() {
let n = 64;
let a = make_1d_poisson(n);
let mut amg_builder = AmgClassical::new();
amg_builder.coarse_cutoff = 8; let hier = amg_builder.build(&a);
let pcg = ParallelPcgSolver::new(500, 1e-10);
let precond = AmgPreconditioner {
hierarchy: hier,
cycle_kind: CycleKind::V,
pcg,
};
let b: Vec<f64> = (0..n)
.map(|i| if i == 0 || i == n - 1 { 1.0 } else { 0.0 })
.collect();
let mut z = vec![0.0f64; n];
precond.apply(&b, &mut z);
let z_norm: f64 = z.iter().map(|v| v * v).sum::<f64>().sqrt();
assert!(
z_norm > 1e-10,
"AMG preconditioner produced trivially zero result: ||z|| = {z_norm}"
);
let mut az = vec![0.0f64; n];
a.spmv(&z, &mut az);
let res_norm: f64 = b
.iter()
.zip(az.iter())
.map(|(bi, ai)| (bi - ai).powi(2))
.sum::<f64>()
.sqrt();
let b_norm: f64 = b.iter().map(|v| v * v).sum::<f64>().sqrt();
assert!(
res_norm < b_norm * 0.1,
"AMG preconditioner did not reduce residual: ||r||/||b|| = {:.3e}",
res_norm / b_norm
);
}
}