use super::hierarchy::MultigridHierarchy;
use super::smoother::{SmootherConfig, compute_residual, smooth};
use super::transfer::{prolongate, restrict};
use num_complex::Complex64;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CycleType {
VCycle,
WCycle,
FCycle,
}
#[derive(Debug, Clone)]
pub struct MultigridConfig {
pub cycle_type: CycleType,
pub pre_smoother: SmootherConfig,
pub post_smoother: SmootherConfig,
pub max_iterations: usize,
pub tolerance: f64,
pub cycles_per_iteration: usize,
}
impl Default for MultigridConfig {
fn default() -> Self {
Self {
cycle_type: CycleType::VCycle,
pre_smoother: SmootherConfig::default(),
post_smoother: SmootherConfig::default(),
max_iterations: 50,
tolerance: 1e-8,
cycles_per_iteration: 1,
}
}
}
#[derive(Debug)]
pub struct MultigridResult {
pub solution: Vec<Complex64>,
pub iterations: usize,
pub residual_norm: f64,
pub converged: bool,
}
pub fn solve_multigrid(
hierarchy: &MultigridHierarchy,
b: &[Complex64],
initial_guess: Option<&[Complex64]>,
config: &MultigridConfig,
) -> MultigridResult {
let n = hierarchy.finest().n_dofs;
let mut x = match initial_guess {
Some(guess) => guess.to_vec(),
None => vec![Complex64::new(0.0, 0.0); n],
};
let matrix = hierarchy.levels[0]
.system
.as_ref()
.expect("Finest level matrix not assembled");
let b_norm = b.iter().map(|v| v.norm_sqr()).sum::<f64>().sqrt();
let tol = config.tolerance * b_norm.max(1e-10);
let mut iterations = 0;
let mut residual = compute_residual(matrix, &x, b);
let mut res_norm = residual.iter().map(|v| v.norm_sqr()).sum::<f64>().sqrt();
while iterations < config.max_iterations && res_norm > tol {
for _ in 0..config.cycles_per_iteration {
match config.cycle_type {
CycleType::VCycle => {
v_cycle(
hierarchy,
0,
&mut x,
b,
&config.pre_smoother,
&config.post_smoother,
);
}
CycleType::WCycle => {
w_cycle(
hierarchy,
0,
&mut x,
b,
&config.pre_smoother,
&config.post_smoother,
);
}
CycleType::FCycle => {
f_cycle(
hierarchy,
0,
&mut x,
b,
&config.pre_smoother,
&config.post_smoother,
);
}
}
}
residual = compute_residual(matrix, &x, b);
res_norm = residual.iter().map(|v| v.norm_sqr()).sum::<f64>().sqrt();
iterations += 1;
}
MultigridResult {
solution: x,
iterations,
residual_norm: res_norm,
converged: res_norm <= tol,
}
}
fn v_cycle(
hierarchy: &MultigridHierarchy,
level: usize,
x: &mut [Complex64],
b: &[Complex64],
pre_config: &SmootherConfig,
post_config: &SmootherConfig,
) {
let n_levels = hierarchy.num_levels();
if level == n_levels - 1 {
let matrix = hierarchy.levels[level]
.system
.as_ref()
.expect("Coarse matrix not assembled");
let exact_config = SmootherConfig {
iterations: 20,
..pre_config.clone()
};
smooth(matrix, x, b, &exact_config);
return;
}
let matrix = hierarchy.levels[level]
.system
.as_ref()
.expect("Matrix not assembled");
smooth(matrix, x, b, pre_config);
let residual = compute_residual(matrix, x, b);
let restriction = hierarchy.levels[level]
.restriction
.as_ref()
.expect("Restriction operator not built");
let r_coarse = restrict(restriction, &residual);
let n_coarse = hierarchy.levels[level + 1].n_dofs;
let mut e_coarse = vec![Complex64::new(0.0, 0.0); n_coarse];
v_cycle(
hierarchy,
level + 1,
&mut e_coarse,
&r_coarse,
pre_config,
post_config,
);
let prolongation = hierarchy.levels[level + 1]
.prolongation
.as_ref()
.expect("Prolongation operator not built");
let e_fine = prolongate(prolongation, &e_coarse);
for i in 0..x.len() {
x[i] += e_fine[i];
}
smooth(matrix, x, b, post_config);
}
fn w_cycle(
hierarchy: &MultigridHierarchy,
level: usize,
x: &mut [Complex64],
b: &[Complex64],
pre_config: &SmootherConfig,
post_config: &SmootherConfig,
) {
let n_levels = hierarchy.num_levels();
if level == n_levels - 1 {
let matrix = hierarchy.levels[level]
.system
.as_ref()
.expect("Coarse matrix not assembled");
let exact_config = SmootherConfig {
iterations: 20,
..pre_config.clone()
};
smooth(matrix, x, b, &exact_config);
return;
}
let matrix = hierarchy.levels[level]
.system
.as_ref()
.expect("Matrix not assembled");
smooth(matrix, x, b, pre_config);
let residual = compute_residual(matrix, x, b);
let restriction = hierarchy.levels[level]
.restriction
.as_ref()
.expect("Restriction operator not built");
let r_coarse = restrict(restriction, &residual);
let n_coarse = hierarchy.levels[level + 1].n_dofs;
let mut e_coarse = vec![Complex64::new(0.0, 0.0); n_coarse];
w_cycle(
hierarchy,
level + 1,
&mut e_coarse,
&r_coarse,
pre_config,
post_config,
);
w_cycle(
hierarchy,
level + 1,
&mut e_coarse,
&r_coarse,
pre_config,
post_config,
);
let prolongation = hierarchy.levels[level + 1]
.prolongation
.as_ref()
.expect("Prolongation operator not built");
let e_fine = prolongate(prolongation, &e_coarse);
for i in 0..x.len() {
x[i] += e_fine[i];
}
smooth(matrix, x, b, post_config);
}
fn f_cycle(
hierarchy: &MultigridHierarchy,
level: usize,
x: &mut [Complex64],
b: &[Complex64],
pre_config: &SmootherConfig,
post_config: &SmootherConfig,
) {
let n_levels = hierarchy.num_levels();
if level == n_levels - 1 {
let matrix = hierarchy.levels[level]
.system
.as_ref()
.expect("Coarse matrix not assembled");
let exact_config = SmootherConfig {
iterations: 20,
..pre_config.clone()
};
smooth(matrix, x, b, &exact_config);
return;
}
let restriction = hierarchy.levels[level]
.restriction
.as_ref()
.expect("Restriction operator not built");
let b_coarse = restrict(restriction, b);
let n_coarse = hierarchy.levels[level + 1].n_dofs;
let mut x_coarse = vec![Complex64::new(0.0, 0.0); n_coarse];
f_cycle(
hierarchy,
level + 1,
&mut x_coarse,
&b_coarse,
pre_config,
post_config,
);
let prolongation = hierarchy.levels[level + 1]
.prolongation
.as_ref()
.expect("Prolongation operator not built");
let x_init = prolongate(prolongation, &x_coarse);
for (i, xi) in x_init.iter().enumerate() {
x[i] = *xi;
}
v_cycle(hierarchy, level, x, b, pre_config, post_config);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::basis::PolynomialDegree;
use crate::mesh::unit_square_triangles;
#[test]
fn test_v_cycle_reduces_residual() {
use crate::assembly::HelmholtzProblem;
use crate::boundary::apply_homogeneous_dirichlet;
use crate::multigrid::smoother::{SmootherConfig, residual_norm, smooth};
let mesh = unit_square_triangles(4);
let k = Complex64::new(0.0, 0.0);
let mut problem = HelmholtzProblem::assemble(&mesh, PolynomialDegree::P1, k, |_, _, _| {
Complex64::new(1.0, 0.0)
});
apply_homogeneous_dirichlet(&mut problem, &mesh, &[1, 2, 3, 4]);
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 {
iterations: 50,
..Default::default()
};
smooth(&matrix, &mut x, b, &config);
let final_norm = residual_norm(&matrix, &x, b);
assert!(
final_norm < initial_norm,
"Residual should decrease: {} -> {}",
initial_norm,
final_norm
);
}
#[test]
fn test_multigrid_config() {
let config = MultigridConfig {
cycle_type: CycleType::WCycle,
max_iterations: 100,
tolerance: 1e-10,
..Default::default()
};
assert_eq!(config.cycle_type, CycleType::WCycle);
assert_eq!(config.max_iterations, 100);
}
}