use nalgebra::ComplexField;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct GmresParams {
pub tolerance: f64,
pub restart: usize,
pub max_iterations: usize,
}
pub const DEFAULT_TOLERANCE: f64 = 1e-10;
pub const DEFAULT_RESTART: usize = 100;
pub const DEFAULT_MAX_ITERATIONS: usize = 5000;
impl Default for GmresParams {
fn default() -> Self {
Self {
tolerance: DEFAULT_TOLERANCE,
restart: DEFAULT_RESTART,
max_iterations: DEFAULT_MAX_ITERATIONS,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct GmresOutcome {
pub iterations: usize,
pub relative_residual: f64,
pub converged: bool,
}
pub fn gmres<T, A, P>(
mut apply: A,
mut precondition: P,
b: &[T],
x: &mut [T],
params: &GmresParams,
) -> GmresOutcome
where
T: ComplexField<RealField = f64> + Copy,
A: FnMut(&[T]) -> Vec<T>,
P: FnMut(&[T]) -> Vec<T>,
{
let n = b.len();
assert_eq!(x.len(), n, "gmres: x and b differ in length");
let b_norm = norm(b);
if b_norm == 0.0 {
x.fill(T::zero());
return GmresOutcome {
iterations: 0,
relative_residual: 0.0,
converged: true,
};
}
let target = params.tolerance * b_norm;
let restart = params.restart.clamp(1, n);
let mut iterations = 0;
let mut residual = residual_of(&mut apply, b, x);
let mut r_norm = norm(&residual);
loop {
let outcome = |converged| GmresOutcome {
iterations,
relative_residual: r_norm / b_norm,
converged,
};
if r_norm <= target {
return outcome(true);
}
if iterations >= params.max_iterations || !r_norm.is_finite() {
return outcome(false);
}
let mut basis: Vec<Vec<T>> = Vec::with_capacity(restart + 1);
basis.push(residual.iter().map(|&r| r.unscale(r_norm)).collect());
let mut hessenberg: Vec<Vec<T>> = Vec::with_capacity(restart);
let mut rotations: Vec<(f64, T)> = Vec::with_capacity(restart);
let mut g = vec![T::zero(); restart + 1];
g[0] = T::from_real(r_norm);
while hessenberg.len() < restart && iterations < params.max_iterations {
let j = hessenberg.len();
let mut w = apply(&precondition(&basis[j]));
assert_eq!(w.len(), n, "gmres: apply returned the wrong length");
iterations += 1;
let mut column = vec![T::zero(); j + 2];
for _pass in 0..2 {
for (v, h) in basis.iter().zip(column.iter_mut()) {
let projection = dot(v, &w);
*h += projection;
axpy(-projection, v, &mut w);
}
}
let next_norm = norm(&w);
column[j + 1] = T::from_real(next_norm);
for (i, &(c, s)) in rotations.iter().enumerate() {
let (a, b) = (column[i], column[i + 1]);
column[i] = a.scale(c) + s * b;
column[i + 1] = b.scale(c) - s.conjugate() * a;
}
let (c, s, r) = givens(column[j], column[j + 1]);
column[j] = r;
column[j + 1] = T::zero();
rotations.push((c, s));
let gj = g[j];
g[j] = gj.scale(c);
g[j + 1] = -(s.conjugate() * gj);
hessenberg.push(column);
if next_norm == 0.0 || g[j + 1].modulus() <= target {
break;
}
basis.push(w.iter().map(|&wi| wi.unscale(next_norm)).collect());
}
let steps = hessenberg.len();
let mut y = vec![T::zero(); steps];
for i in (0..steps).rev() {
let mut sum = g[i];
for k in i + 1..steps {
sum -= hessenberg[k][i] * y[k];
}
y[i] = sum / hessenberg[i][i];
}
let mut update = vec![T::zero(); n];
for (v, &yk) in basis.iter().zip(&y) {
axpy(yk, v, &mut update);
}
let correction = precondition(&update);
assert_eq!(
correction.len(),
n,
"gmres: precondition returned the wrong length"
);
for (xi, ci) in x.iter_mut().zip(correction) {
*xi += ci;
}
residual = residual_of(&mut apply, b, x);
r_norm = norm(&residual);
}
}
fn residual_of<T, A>(apply: &mut A, b: &[T], x: &[T]) -> Vec<T>
where
T: ComplexField<RealField = f64> + Copy,
A: FnMut(&[T]) -> Vec<T>,
{
let ax = apply(x);
assert_eq!(ax.len(), b.len(), "gmres: apply returned the wrong length");
b.iter().zip(ax).map(|(&bi, ai)| bi - ai).collect()
}
fn dot<T: ComplexField<RealField = f64> + Copy>(u: &[T], v: &[T]) -> T {
u.iter()
.zip(v)
.fold(T::zero(), |sum, (&ui, &vi)| sum + ui.conjugate() * vi)
}
fn axpy<T: ComplexField<RealField = f64> + Copy>(a: T, x: &[T], y: &mut [T]) {
for (yi, &xi) in y.iter_mut().zip(x) {
*yi += a * xi;
}
}
fn norm<T: ComplexField<RealField = f64> + Copy>(v: &[T]) -> f64 {
let scale = v.iter().fold(0.0_f64, |m, e| m.max(e.modulus()));
if scale == 0.0 || !scale.is_finite() {
return scale;
}
let sum: f64 = v.iter().map(|e| e.unscale(scale).modulus_squared()).sum();
scale * sum.sqrt()
}
fn givens<T: ComplexField<RealField = f64> + Copy>(a: T, b: T) -> (f64, T, T) {
let (abs_a, abs_b) = (a.modulus(), b.modulus());
if abs_b == 0.0 {
return (1.0, T::zero(), a);
}
if abs_a == 0.0 {
return (0.0, T::one(), b);
}
let length = abs_a.hypot(abs_b);
let phase = a.unscale(abs_a);
let c = abs_a / length;
let s = phase * b.conjugate().unscale(length);
(c, s, phase.scale(length))
}
#[cfg(test)]
mod tests {
use super::*;
use nalgebra::{DMatrix, DVector};
use num_complex::Complex;
type C = Complex<f64>;
fn stream(seed: u64) -> impl FnMut() -> f64 {
let mut state = seed.wrapping_mul(0x9E37_79B9_7F4A_7C15) | 1;
move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
(state >> 11) as f64 / (1u64 << 52) as f64 - 1.0
}
}
fn complex_symmetric(n: usize, seed: u64) -> DMatrix<C> {
let mut next = stream(seed);
let mut a = DMatrix::from_fn(n, n, |_, _| C::new(next(), next()) * 0.3);
a = &a + a.transpose();
for i in 0..n {
a[(i, i)] += C::new(n as f64, 0.5 * n as f64);
}
a
}
fn apply_matrix<'a, T: ComplexField<RealField = f64> + Copy>(
a: &'a DMatrix<T>,
) -> impl FnMut(&[T]) -> Vec<T> + 'a {
move |v| (a * DVector::from_column_slice(v)).as_slice().to_vec()
}
#[test]
fn complex_symmetric_system_converges_to_the_direct_solution() {
let n = 60;
let a = complex_symmetric(n, 3);
let mut next = stream(7);
let b: Vec<C> = (0..n).map(|_| C::new(next(), next())).collect();
let mut x = vec![C::new(0.0, 0.0); n];
let params = GmresParams {
restart: 8,
..GmresParams::default()
};
let outcome = gmres(apply_matrix(&a), |v| v.to_vec(), &b, &mut x, ¶ms);
assert!(outcome.converged, "{outcome:?}");
assert!(outcome.relative_residual <= 1e-10);
let direct = a.clone().lu().solve(&DVector::from_vec(b)).unwrap();
let error = (DVector::from_vec(x) - &direct).norm() / direct.norm();
assert!(error < 1e-9, "error {error}");
}
#[test]
fn real_system_stays_real_and_matches_the_direct_solution() {
let n = 40;
let mut next = stream(11);
let mut a = DMatrix::from_fn(n, n, |_, _| next());
for i in 0..n {
a[(i, i)] += 2.0 * n as f64;
}
let b: Vec<f64> = (0..n).map(|_| next()).collect();
let mut x = vec![0.0; n];
let outcome = gmres(
apply_matrix(&a),
|v| v.to_vec(),
&b,
&mut x,
&GmresParams::default(),
);
assert!(outcome.converged, "{outcome:?}");
let direct = a.lu().solve(&DVector::from_vec(b)).unwrap();
assert!((DVector::from_vec(x) - &direct).norm() < 1e-9 * direct.norm());
}
#[test]
fn full_krylov_space_solves_exactly_in_n_steps() {
let n = 12;
let a = complex_symmetric(n, 5);
let b: Vec<C> = (0..n).map(|i| C::new(i as f64, 1.0)).collect();
let mut x = vec![C::new(0.0, 0.0); n];
let params = GmresParams {
tolerance: 1e-13,
restart: n,
max_iterations: n,
};
let outcome = gmres(apply_matrix(&a), |v| v.to_vec(), &b, &mut x, ¶ms);
assert!(outcome.converged, "{outcome:?}");
assert!(outcome.iterations <= n);
}
#[test]
fn exact_preconditioner_converges_in_one_iteration() {
let n = 20;
let a = complex_symmetric(n, 9);
let inverse = a.clone().try_inverse().unwrap();
let b: Vec<C> = (0..n).map(|i| C::new(1.0, -(i as f64))).collect();
let mut x = vec![C::new(0.0, 0.0); n];
let outcome = gmres(
apply_matrix(&a),
apply_matrix(&inverse),
&b,
&mut x,
&GmresParams::default(),
);
assert!(outcome.converged, "{outcome:?}");
assert_eq!(outcome.iterations, 1);
}
#[test]
fn zero_right_hand_side_is_solved_without_a_product() {
let mut x = vec![C::new(3.0, 1.0); 4];
let outcome = gmres(
|_: &[C]| -> Vec<C> { unreachable!() },
|v: &[C]| v.to_vec(),
&[C::new(0.0, 0.0); 4],
&mut x,
&GmresParams::default(),
);
assert_eq!(outcome.iterations, 0);
assert!(outcome.converged);
assert!(x.iter().all(|e| *e == C::new(0.0, 0.0)));
}
#[test]
fn iteration_cap_reports_non_convergence() {
let n = 30;
let a = complex_symmetric(n, 13);
let b = vec![C::new(1.0, 0.0); n];
let mut x = vec![C::new(0.0, 0.0); n];
let params = GmresParams {
tolerance: 1e-15,
restart: 2,
max_iterations: 3,
};
let outcome = gmres(apply_matrix(&a), |v| v.to_vec(), &b, &mut x, ¶ms);
assert!(!outcome.converged);
assert_eq!(outcome.iterations, 3);
assert!(outcome.relative_residual < 1.0);
}
#[test]
fn givens_zeroes_the_second_component() {
let (a, b) = (C::new(0.3, -1.2), C::new(-2.0, 0.7));
let (c, s, r) = givens(a, b);
let top = a * c + s * b;
let bottom = b * c - s.conj() * a;
assert!((top - r).norm() < 1e-15);
assert!(bottom.norm() < 1e-15);
assert!((r.norm() - a.norm().hypot(b.norm())).abs() < 1e-15);
}
}