use crate::assembly::HelmholtzMatrix;
use num_complex::Complex64;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SmootherType {
GaussSeidel,
Jacobi,
SymmetricGaussSeidel,
}
#[derive(Debug, Clone)]
pub struct SmootherConfig {
pub smoother_type: SmootherType,
pub iterations: usize,
pub omega: f64,
}
impl Default for SmootherConfig {
fn default() -> Self {
Self {
smoother_type: SmootherType::GaussSeidel,
iterations: 2,
omega: 2.0 / 3.0, }
}
}
pub fn smooth(
matrix: &HelmholtzMatrix,
x: &mut [Complex64],
b: &[Complex64],
config: &SmootherConfig,
) {
match config.smoother_type {
SmootherType::GaussSeidel => {
for _ in 0..config.iterations {
gauss_seidel_sweep(matrix, x, b, false);
}
}
SmootherType::Jacobi => {
for _ in 0..config.iterations {
jacobi_sweep(matrix, x, b, config.omega);
}
}
SmootherType::SymmetricGaussSeidel => {
for _ in 0..config.iterations {
gauss_seidel_sweep(matrix, x, b, false);
gauss_seidel_sweep(matrix, x, b, true);
}
}
}
}
fn gauss_seidel_sweep(
matrix: &HelmholtzMatrix,
x: &mut [Complex64],
b: &[Complex64],
backward: bool,
) {
#[allow(unused_variables)]
let n = matrix.dim;
let mut row_data: HashMap<usize, Vec<(usize, Complex64)>> = HashMap::new();
let mut diagonals: Vec<Complex64> = vec![Complex64::new(0.0, 0.0); n];
for k in 0..matrix.rows.len() {
let row = matrix.rows[k];
let col = matrix.cols[k];
let val = matrix.values[k];
if row == col {
diagonals[row] += val;
} else {
row_data.entry(row).or_default().push((col, val));
}
}
let indices: Box<dyn Iterator<Item = usize>> = if backward {
Box::new((0..n).rev())
} else {
Box::new(0..n)
};
for i in indices {
if diagonals[i].norm() < 1e-15 {
continue; }
let mut sigma = Complex64::new(0.0, 0.0);
if let Some(row_entries) = row_data.get(&i) {
for &(j, val) in row_entries {
sigma += val * x[j];
}
}
x[i] = (b[i] - sigma) / diagonals[i];
}
}
fn jacobi_sweep(matrix: &HelmholtzMatrix, x: &mut [Complex64], b: &[Complex64], omega: f64) {
#[allow(unused_variables)]
let n = matrix.dim;
let mut row_data: HashMap<usize, Vec<(usize, Complex64)>> = HashMap::new();
let mut diagonals: Vec<Complex64> = vec![Complex64::new(0.0, 0.0); n];
for k in 0..matrix.rows.len() {
let row = matrix.rows[k];
let col = matrix.cols[k];
let val = matrix.values[k];
if row == col {
diagonals[row] += val;
} else {
row_data.entry(row).or_default().push((col, val));
}
}
let mut x_new = vec![Complex64::new(0.0, 0.0); n];
for i in 0..n {
if diagonals[i].norm() < 1e-15 {
x_new[i] = x[i];
continue;
}
let mut sigma = Complex64::new(0.0, 0.0);
if let Some(row_entries) = row_data.get(&i) {
for &(j, val) in row_entries {
sigma += val * x[j];
}
}
let x_gs = (b[i] - sigma) / diagonals[i];
x_new[i] = Complex64::new(omega, 0.0) * x_gs + Complex64::new(1.0 - omega, 0.0) * x[i];
}
x.copy_from_slice(&x_new);
}
pub fn compute_residual(
matrix: &HelmholtzMatrix,
x: &[Complex64],
b: &[Complex64],
) -> Vec<Complex64> {
let mut r = b.to_vec();
for k in 0..matrix.rows.len() {
r[matrix.rows[k]] -= matrix.values[k] * x[matrix.cols[k]];
}
r
}
pub fn residual_norm(matrix: &HelmholtzMatrix, x: &[Complex64], b: &[Complex64]) -> f64 {
let r = compute_residual(matrix, x, b);
r.iter().map(|v| v.norm_sqr()).sum::<f64>().sqrt()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::assembly::HelmholtzProblem;
use crate::basis::PolynomialDegree;
use crate::mesh::unit_square_triangles;
#[test]
fn test_gauss_seidel_reduces_residual() {
let mesh = unit_square_triangles(4);
let k = Complex64::new(0.0, 0.0);
let problem = HelmholtzProblem::assemble(&mesh, PolynomialDegree::P1, k, |x, y, _z| {
Complex64::new((x * y).sin(), 0.0)
});
let matrix = problem.matrix.to_compressed();
let b = &problem.rhs;
let mut x = vec![Complex64::new(0.0, 0.0); matrix.dim];
let initial_norm = residual_norm(&matrix, &x, b);
let config = SmootherConfig::default();
smooth(&matrix, &mut x, b, &config);
let final_norm = residual_norm(&matrix, &x, b);
assert!(final_norm < initial_norm, "Residual should decrease");
}
#[test]
fn test_jacobi_with_damping() {
let mesh = unit_square_triangles(4);
let k = Complex64::new(0.0, 0.0);
let problem = HelmholtzProblem::assemble(&mesh, PolynomialDegree::P1, k, |_, _, _| {
Complex64::new(1.0, 0.0)
});
let matrix = problem.matrix.to_compressed();
let b = &problem.rhs;
let mut x = vec![Complex64::new(0.0, 0.0); matrix.dim];
let initial_norm = residual_norm(&matrix, &x, b);
let config = SmootherConfig {
smoother_type: SmootherType::Jacobi,
iterations: 5,
omega: 0.6,
};
smooth(&matrix, &mut x, b, &config);
let final_norm = residual_norm(&matrix, &x, b);
assert!(final_norm < initial_norm, "Jacobi should reduce residual");
}
}