use crate::parallel_solver::{CsrMatrix, ParallelPcgSolver, PcgStats};
use crate::solvers::amg::smoothers::symmetric_gs;
#[derive(Debug, Clone)]
pub struct AmgLevel {
pub a: CsrMatrix,
pub p: CsrMatrix,
pub pt: CsrMatrix,
pub diag: Vec<f64>,
}
#[derive(Debug, Clone)]
pub struct AmgHierarchy {
pub levels: Vec<AmgLevel>,
pub coarse_cutoff: usize,
}
#[derive(Debug, Clone, Copy)]
pub enum CycleKind {
V,
W,
}
pub fn v_cycle(
hier: &AmgHierarchy,
level: usize,
b: &[f64],
x: &mut [f64],
pcg: &ParallelPcgSolver,
) {
let last_level = hier.levels.len() - 1;
let lev = &hier.levels[level];
if level == last_level || lev.a.nrows <= hier.coarse_cutoff {
pcg.solve(&lev.a, b, x);
return;
}
symmetric_gs(&lev.a, b, x, 2);
let n = lev.a.nrows;
let mut ax = vec![0.0f64; n];
lev.a.spmv(x, &mut ax);
let r: Vec<f64> = b.iter().zip(ax.iter()).map(|(bi, ai)| bi - ai).collect();
let n_coarse = hier.levels[level + 1].a.nrows;
let mut r_c = vec![0.0f64; n_coarse];
lev.pt.spmv(&r, &mut r_c);
let mut x_c = vec![0.0f64; n_coarse];
v_cycle(hier, level + 1, &r_c, &mut x_c, pcg);
let mut p_xc = vec![0.0f64; n];
lev.p.spmv(&x_c, &mut p_xc);
for i in 0..n {
x[i] += p_xc[i];
}
symmetric_gs(&lev.a, b, x, 2);
}
pub fn w_cycle(
hier: &AmgHierarchy,
level: usize,
b: &[f64],
x: &mut [f64],
pcg: &ParallelPcgSolver,
) {
let last_level = hier.levels.len() - 1;
let lev = &hier.levels[level];
if level == last_level || lev.a.nrows <= hier.coarse_cutoff {
pcg.solve(&lev.a, b, x);
return;
}
symmetric_gs(&lev.a, b, x, 2);
let n = lev.a.nrows;
let mut ax = vec![0.0f64; n];
lev.a.spmv(x, &mut ax);
let r: Vec<f64> = b.iter().zip(ax.iter()).map(|(bi, ai)| bi - ai).collect();
let n_coarse = hier.levels[level + 1].a.nrows;
let mut r_c = vec![0.0f64; n_coarse];
lev.pt.spmv(&r, &mut r_c);
let mut x_c = vec![0.0f64; n_coarse];
w_cycle(hier, level + 1, &r_c, &mut x_c, pcg);
let mut a_c_xc = vec![0.0f64; n_coarse];
hier.levels[level + 1].a.spmv(&x_c, &mut a_c_xc);
let r_c2: Vec<f64> = r_c
.iter()
.zip(a_c_xc.iter())
.map(|(ri, ai)| ri - ai)
.collect();
let mut x_c2 = vec![0.0f64; n_coarse];
w_cycle(hier, level + 1, &r_c2, &mut x_c2, pcg);
for i in 0..n_coarse {
x_c[i] += x_c2[i];
}
let mut p_xc = vec![0.0f64; n];
lev.p.spmv(&x_c, &mut p_xc);
for i in 0..n {
x[i] += p_xc[i];
}
symmetric_gs(&lev.a, b, x, 2);
}
pub fn amg_solve(
hier: &AmgHierarchy,
b: &[f64],
x: &mut [f64],
kind: CycleKind,
max_cycles: usize,
tol: f64,
) -> PcgStats {
let n = hier.levels[0].a.nrows;
let pcg = ParallelPcgSolver::new(500, 1e-10);
let b_norm = b.iter().map(|bi| bi * bi).sum::<f64>().sqrt().max(1e-300);
let mut res_norm;
for cycle in 0..max_cycles {
let mut ax = vec![0.0f64; n];
hier.levels[0].a.spmv(x, &mut ax);
let r: Vec<f64> = b.iter().zip(ax.iter()).map(|(bi, ai)| bi - ai).collect();
res_norm = r.iter().map(|ri| ri * ri).sum::<f64>().sqrt();
if res_norm / b_norm < tol {
return PcgStats {
iterations: cycle,
residual_norm: res_norm,
converged: true,
};
}
match kind {
CycleKind::V => v_cycle(hier, 0, b, x, &pcg),
CycleKind::W => w_cycle(hier, 0, b, x, &pcg),
}
}
let mut ax = vec![0.0f64; n];
hier.levels[0].a.spmv(x, &mut ax);
let r: Vec<f64> = b.iter().zip(ax.iter()).map(|(bi, ai)| bi - ai).collect();
res_norm = r.iter().map(|ri| ri * ri).sum::<f64>().sqrt();
PcgStats {
iterations: max_cycles,
residual_norm: res_norm,
converged: res_norm / b_norm < tol,
}
}
#[cfg(test)]
mod tests {
use super::*;
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,
}
}
fn make_trivial_2level_hierarchy(n_fine: usize) -> AmgHierarchy {
let a_fine = make_1d_poisson(n_fine);
let n_coarse = n_fine.div_ceil(2);
let mut row_offsets = vec![0usize; n_fine + 1];
let mut col_indices = Vec::new();
let mut values = Vec::new();
for i in 0..n_fine {
let c_idx = i / 2; if i % 2 == 0 {
col_indices.push(c_idx);
values.push(1.0);
row_offsets[i + 1] = col_indices.len();
} else {
let left_c = i / 2; let right_c = i.div_ceil(2); if right_c < n_coarse {
col_indices.push(left_c);
values.push(0.5);
col_indices.push(right_c);
values.push(0.5);
} else {
col_indices.push(left_c);
values.push(1.0);
}
row_offsets[i + 1] = col_indices.len();
}
}
let p = CsrMatrix {
nrows: n_fine,
ncols: n_coarse,
row_offsets,
col_indices,
values,
};
use crate::solvers::amg::galerkin::{csr_transpose, galerkin_coarse};
let pt = csr_transpose(&p);
let a_coarse = galerkin_coarse(&a_fine, &p);
let diag_fine: Vec<f64> = (0..n_fine)
.map(|i| {
let rs = a_fine.row_offsets[i];
let re = a_fine.row_offsets[i + 1];
let mut d = 1.0;
for k in rs..re {
if a_fine.col_indices[k] == i {
d = a_fine.values[k];
break;
}
}
d
})
.collect();
let diag_coarse: Vec<f64> = (0..n_coarse)
.map(|i| {
let rs = a_coarse.row_offsets[i];
let re = a_coarse.row_offsets[i + 1];
let mut d = 1.0;
for k in rs..re {
if a_coarse.col_indices[k] == i {
d = a_coarse.values[k];
break;
}
}
d
})
.collect();
AmgHierarchy {
levels: vec![
AmgLevel {
a: a_fine,
p,
pt,
diag: diag_fine,
},
AmgLevel {
a: a_coarse,
p: CsrMatrix::identity(0),
pt: CsrMatrix::identity(0),
diag: diag_coarse,
},
],
coarse_cutoff: 4,
}
}
#[test]
fn test_vcycle_1d_poisson_reduces() {
let n = 32;
let hier = make_trivial_2level_hierarchy(n);
let a = &hier.levels[0].a;
let b: Vec<f64> = (0..n).map(|i| (i + 1) as f64).collect();
let mut x = vec![0.0f64; n];
let r0_norm: f64 = b.iter().map(|bi| bi * bi).sum::<f64>().sqrt();
let pcg = ParallelPcgSolver::new(500, 1e-10);
v_cycle(&hier, 0, &b, &mut x, &pcg);
let mut ax = vec![0.0f64; n];
a.spmv(&x, &mut ax);
let r1_norm: f64 = b
.iter()
.zip(ax.iter())
.map(|(bi, ai)| (bi - ai).powi(2))
.sum::<f64>()
.sqrt();
let ratio = r1_norm / r0_norm;
assert!(
ratio <= 0.5,
"V-cycle residual reduction ratio {ratio:.4} exceeds 0.5"
);
}
}