use crate::ldu_matrix::fv_matrix::{SolverPerformance, SolverSettings};
use crate::ldu_matrix::ldu_matrix::LduMatrix;
pub fn conjugate_gradient(
ldu: &LduMatrix,
b: &[f64],
x0: Option<&[f64]>,
settings: &SolverSettings,
) -> (Vec<f64>, SolverPerformance) {
let n = ldu.n_cells;
debug_assert_eq!(b.len(), n);
let r_diag = dic_reciprocal_diag(ldu);
let mut x = match x0 {
Some(g) => {
debug_assert_eq!(g.len(), n);
g.to_vec()
}
None => vec![0.0_f64; n],
};
let mut r = ldu.residual(&x, b);
let mut z = vec![0.0_f64; n];
dic_precondition(ldu, &r_diag, &r, &mut z);
let mut p = z.clone();
let mut rz = dot(&r, &z); let b_norm: f64 = b.iter().map(|bi| bi * bi).sum::<f64>().sqrt().max(1e-300);
let mut n_iter = 0;
let mut final_residual = r.iter().map(|ri| ri * ri).sum::<f64>().sqrt() / b_norm;
if final_residual < settings.tolerance {
return (
x,
SolverPerformance {
n_iterations: 0,
final_residual,
converged: true,
},
);
}
for iter in 0..settings.max_iter {
let ap = ldu.multiply(&p); let pap = dot(&p, &ap);
if pap.abs() < 1e-300 {
break;
}
let alpha = rz / pap;
for c in 0..n {
x[c] += alpha * p[c];
}
for c in 0..n {
r[c] -= alpha * ap[c];
}
dic_precondition(ldu, &r_diag, &r, &mut z);
let rz_new = dot(&r, &z);
final_residual = r.iter().map(|ri| ri * ri).sum::<f64>().sqrt() / b_norm;
n_iter = iter + 1;
if final_residual < settings.tolerance {
break;
}
let beta = rz_new / rz;
for c in 0..n {
p[c] = z[c] + beta * p[c];
}
rz = rz_new;
}
(
x,
SolverPerformance {
n_iterations: n_iter,
final_residual,
converged: final_residual < settings.tolerance,
},
)
}
fn dic_reciprocal_diag(ldu: &LduMatrix) -> Vec<f64> {
let mut r_diag = ldu.diag.clone();
for f in 0..ldu.n_internal_faces {
let o = ldu.owner[f];
let nb = ldu.neighbour[f];
let d_o = r_diag[o];
if d_o.abs() > 1e-300 {
r_diag[nb] -= ldu.upper[f] * ldu.upper[f] / d_o;
}
}
for d in r_diag.iter_mut() {
*d = if d.abs() < 1e-300 { 1.0 } else { 1.0 / *d };
}
r_diag
}
fn dic_precondition(ldu: &LduMatrix, r_diag: &[f64], r: &[f64], w: &mut [f64]) {
let n = ldu.n_cells;
for c in 0..n {
w[c] = r_diag[c] * r[c];
}
for f in 0..ldu.n_internal_faces {
let o = ldu.owner[f];
let nb = ldu.neighbour[f];
w[nb] -= r_diag[nb] * ldu.upper[f] * w[o];
}
for f in (0..ldu.n_internal_faces).rev() {
let o = ldu.owner[f];
let nb = ldu.neighbour[f];
w[o] -= r_diag[o] * ldu.upper[f] * w[nb];
}
}
#[inline]
fn dot(a: &[f64], b: &[f64]) -> f64 {
a.iter().zip(b.iter()).map(|(ai, bi)| ai * bi).sum()
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
fn tridiag(n: usize) -> (LduMatrix, Vec<f64>) {
let h = 1.0 / (n + 1) as f64;
let owner: Vec<usize> = (0..n - 1).collect();
let neighbour: Vec<usize> = (1..n).collect();
let mut m = LduMatrix::new(n, owner, neighbour);
let coeff = 1.0 / (h * h);
m.diag = vec![2.0 * coeff; n];
m.upper = vec![-coeff; n - 1];
m.lower = vec![-coeff; n - 1];
let b = vec![1.0; n];
(m, b)
}
#[test]
fn cg_identity() {
let mut m = LduMatrix::new(3, vec![], vec![]);
m.diag = vec![2.0, 3.0, 4.0];
let b = vec![4.0, 9.0, 8.0];
let settings = SolverSettings {
tolerance: 1e-10,
max_iter: 100,
};
let (x, perf) = conjugate_gradient(&m, &b, None, &settings);
assert!(perf.converged, "residual = {}", perf.final_residual);
assert_relative_eq!(x[0], 2.0, epsilon = 1e-8);
assert_relative_eq!(x[1], 3.0, epsilon = 1e-8);
assert_relative_eq!(x[2], 2.0, epsilon = 1e-8);
}
#[test]
fn cg_solves_1d_poisson() {
let n = 50;
let h = 1.0 / (n + 1) as f64;
let (m, b) = tridiag(n);
let settings = SolverSettings {
tolerance: 1e-10,
max_iter: 1000,
};
let (x, perf) = conjugate_gradient(&m, &b, None, &settings);
assert!(
perf.converged,
"did not converge: residual = {}",
perf.final_residual
);
for i in 0..n {
let xi = (i + 1) as f64 * h;
let exact = xi * (1.0 - xi) / 2.0;
assert_relative_eq!(x[i], exact, epsilon = h * h);
}
}
#[test]
fn cg_faster_than_gs_on_symmetric() {
let n = 20;
let (m, b) = tridiag(n);
let settings = SolverSettings {
tolerance: 1e-10,
max_iter: 500,
};
let (_, perf) = conjugate_gradient(&m, &b, None, &settings);
assert!(perf.converged);
assert!(
perf.n_iterations <= n,
"expected <= {} iters, got {}",
n,
perf.n_iterations
);
}
}