use super::vecops::{axpy, dot, nrm2, scal};
use super::{KrylovResult, KrylovSettings, Preconditioner};
use crate::ldu_matrix::LduMatrix;
const HAPPY_TOL: f64 = 1.0e-300;
pub fn gmres(
a: &LduMatrix,
b: &[f64],
x0: Option<&[f64]>,
precond: &Preconditioner,
settings: &KrylovSettings,
) -> (Vec<f64>, KrylovResult) {
let n = a.n_cells;
let bnorm = nrm2(b);
if bnorm == 0.0 {
return (
vec![0.0; n],
KrylovResult {
n_iterations: 0,
final_residual: 0.0,
converged: true,
},
);
}
let m = if settings.restart == 0 {
settings.max_iter.max(1)
} else {
settings.restart
};
let mut x = match x0 {
Some(g) => g.to_vec(),
None => vec![0.0; n],
};
let tol = settings.tolerance;
let mut total_iters = 0usize;
let mut converged = false;
'outer: while total_iters < settings.max_iter {
let r = a.residual(&x, b);
let beta = nrm2(&r);
let rel = beta / bnorm;
if rel <= tol {
converged = true;
break;
}
let mut v: Vec<Vec<f64>> = Vec::with_capacity(m + 1);
let mut v0 = r;
scal(1.0 / beta, &mut v0);
v.push(v0);
let mut cs = vec![0.0f64; m];
let mut sn = vec![0.0f64; m];
let mut g = vec![0.0f64; m + 1];
g[0] = beta;
let mut hcols: Vec<Vec<f64>> = Vec::with_capacity(m);
let mut k = 0usize; for j in 0..m {
if total_iters >= settings.max_iter {
break;
}
total_iters += 1;
let mut mz = vec![0.0; n];
precond.apply(&v[j], &mut mz);
let mut w = a.multiply(&mz);
let mut hcol = vec![0.0f64; j + 2];
for i in 0..=j {
let hij = dot(&w, &v[i]);
hcol[i] = hij;
axpy(-hij, &v[i], &mut w);
}
let hnext = nrm2(&w);
hcol[j + 1] = hnext;
for i in 0..j {
let temp = cs[i] * hcol[i] + sn[i] * hcol[i + 1];
hcol[i + 1] = -sn[i] * hcol[i] + cs[i] * hcol[i + 1];
hcol[i] = temp;
}
let (c, s) = givens(hcol[j], hcol[j + 1]);
cs[j] = c;
sn[j] = s;
hcol[j] = c * hcol[j] + s * hcol[j + 1];
hcol[j + 1] = 0.0;
let g_temp = c * g[j] + s * g[j + 1];
g[j + 1] = -s * g[j] + c * g[j + 1];
g[j] = g_temp;
hcols.push(hcol);
k = j + 1;
let resid = g[j + 1].abs() / bnorm;
if hnext > HAPPY_TOL {
let mut vnew = w;
scal(1.0 / hnext, &mut vnew);
v.push(vnew);
}
if resid <= tol || hnext <= HAPPY_TOL {
break;
}
}
let mut y = vec![0.0f64; k];
for i in (0..k).rev() {
let mut sum = g[i];
for l in (i + 1)..k {
sum -= hcols[l][i] * y[l];
}
let diag = hcols[i][i];
y[i] = if diag.abs() > HAPPY_TOL { sum / diag } else { 0.0 };
}
let mut z = vec![0.0f64; n];
for i in 0..k {
axpy(y[i], &v[i], &mut z);
}
let mut mz = vec![0.0; n];
precond.apply(&z, &mut mz);
axpy(1.0, &mz, &mut x);
let true_rel = nrm2(&a.residual(&x, b)) / bnorm;
if !true_rel.is_finite() {
break 'outer; }
if true_rel <= tol {
converged = true;
break;
}
}
let final_rel = nrm2(&a.residual(&x, b)) / bnorm;
let final_rel = if final_rel.is_finite() { final_rel } else { 1.0 };
(
x,
KrylovResult {
n_iterations: total_iters,
final_residual: final_rel,
converged: converged && final_rel <= tol,
},
)
}
fn givens(a: f64, b: f64) -> (f64, f64) {
if b == 0.0 {
(1.0, 0.0)
} else if a == 0.0 {
(0.0, 1.0)
} else {
let r = a.hypot(b);
(a / r, b / r)
}
}