use crate::ldu_matrix::ldu_matrix::LduMatrix;
pub fn gauss_seidel(
mat: &LduMatrix,
b: &[f64],
x: &mut Vec<f64>,
tol: f64,
max_iter: usize,
) -> (usize, f64) {
debug_assert_eq!(b.len(), mat.n_cells);
debug_assert_eq!(x.len(), mat.n_cells);
let inv_diag: Vec<f64> = mat.diag.iter().map(|d| 1.0 / d).collect();
let n = mat.n_cells;
let mut adj: Vec<Vec<(usize, f64)>> = vec![Vec::new(); n];
for f in 0..mat.n_internal_faces {
let o = mat.owner[f];
let nb = mat.neighbour[f];
adj[o].push((nb, mat.upper[f]));
adj[nb].push((o, mat.lower[f]));
}
for iter in 0..max_iter {
for c in 0..n {
let mut sigma = b[c];
for &(peer, coeff) in &adj[c] {
sigma -= coeff * x[peer];
}
x[c] = sigma * inv_diag[c];
}
let res = mat.normalised_residual(x, b);
if res < tol {
return (iter + 1, res);
}
}
let res = mat.normalised_residual(x, b);
(max_iter, res)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
fn tridiag_3x3() -> LduMatrix {
let mut m = LduMatrix::new(3, vec![0, 1], vec![1, 2]);
m.diag = vec![4.0, 4.0, 4.0]; m.upper = vec![-1.0, -1.0];
m.lower = vec![-1.0, -1.0];
m
}
#[test]
fn solves_diagonally_dominant_system() {
let mat = tridiag_3x3();
let b = vec![3.0, 2.0, 3.0]; let mut x = vec![0.0; 3];
let (iters, res) = gauss_seidel(&mat, &b, &mut x, 1e-8, 100);
assert!(res < 1e-7, "residual {res} after {iters} iters");
assert_relative_eq!(x[0], 1.0, epsilon = 1e-5);
assert_relative_eq!(x[1], 1.0, epsilon = 1e-5);
assert_relative_eq!(x[2], 1.0, epsilon = 1e-5);
}
#[test]
fn diagonal_system_one_iter() {
let mut m = LduMatrix::new(3, vec![], vec![]);
m.diag = vec![2.0, 3.0, 5.0];
let b = vec![4.0, 9.0, 15.0];
let mut x = vec![0.0; 3];
let (iters, _res) = gauss_seidel(&m, &b, &mut x, 1e-12, 10);
assert_eq!(iters, 1);
assert_relative_eq!(x[0], 2.0, epsilon = 1e-12);
assert_relative_eq!(x[1], 3.0, epsilon = 1e-12);
assert_relative_eq!(x[2], 3.0, epsilon = 1e-12);
}
}