use super::vecops::{axpy, dot, nrm2};
use super::{KrylovResult, KrylovSettings, Preconditioner};
use crate::ldu_matrix::LduMatrix;
const BREAKDOWN_TOL: f64 = 1.0e-30;
pub fn bicgstab(
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 mut x = match x0 {
Some(g) => g.to_vec(),
None => vec![0.0; n],
};
let mut r = a.residual(&x, b);
let rhat0 = r.clone();
let mut best_x = x.clone();
let mut best_rel = nrm2(&r) / bnorm;
if best_rel <= settings.tolerance {
return (
best_x,
KrylovResult {
n_iterations: 0,
final_residual: best_rel,
converged: true,
},
);
}
let mut rho_old: f64 = 1.0;
let mut alpha: f64 = 1.0;
let mut omega: f64 = 1.0;
let mut v = vec![0.0; n];
let mut p = vec![0.0; n];
let mut phat = vec![0.0; n];
let mut shat = vec![0.0; n];
let mut iters = 0usize;
let mut converged = false;
while iters < settings.max_iter {
iters += 1;
let rho = dot(&rhat0, &r);
if !rho.is_finite() || rho.abs() < BREAKDOWN_TOL {
break; }
if omega.abs() < BREAKDOWN_TOL {
break;
}
let beta = (rho / rho_old) * (alpha / omega);
for i in 0..n {
p[i] = r[i] + beta * (p[i] - omega * v[i]);
}
precond.apply(&p, &mut phat);
v = a.multiply(&phat);
let rhat_v = dot(&rhat0, &v);
if !rhat_v.is_finite() || rhat_v.abs() < BREAKDOWN_TOL {
break;
}
alpha = rho / rhat_v;
let mut s = r.clone();
axpy(-alpha, &v, &mut s);
let s_rel = nrm2(&s) / bnorm;
if s_rel <= settings.tolerance {
axpy(alpha, &phat, &mut x);
let rel = nrm2(&a.residual(&x, b)) / bnorm;
if rel < best_rel {
best_rel = rel;
best_x = x.clone();
}
converged = best_rel <= settings.tolerance;
break;
}
precond.apply(&s, &mut shat);
let t = a.multiply(&shat);
let tt = dot(&t, &t);
if !tt.is_finite() || tt.abs() < BREAKDOWN_TOL {
axpy(alpha, &phat, &mut x);
let rel = nrm2(&a.residual(&x, b)) / bnorm;
if rel < best_rel {
best_rel = rel;
best_x = x.clone();
}
break;
}
omega = dot(&t, &s) / tt;
axpy(alpha, &phat, &mut x);
axpy(omega, &shat, &mut x);
r = s;
axpy(-omega, &t, &mut r);
let rel = nrm2(&r) / bnorm;
if !rel.is_finite() {
break; }
if rel < best_rel {
best_rel = rel;
best_x = x.clone();
}
if best_rel <= settings.tolerance {
converged = true;
break;
}
if omega.abs() < BREAKDOWN_TOL {
break; }
rho_old = rho;
}
let true_rel = nrm2(&a.residual(&best_x, b)) / bnorm;
let true_rel = if true_rel.is_finite() { true_rel } else { best_rel };
(
best_x,
KrylovResult {
n_iterations: iters,
final_residual: true_rel,
converged: converged && true_rel <= settings.tolerance,
},
)
}