use crate::assembly::HelmholtzProblem;
use ndarray::Array1;
use num_complex::Complex64;
use solvers::iterative::{gmres_pipelined, gmres_preconditioned, gmres_preconditioned_with_guess};
use solvers::{
AdditiveSchwarzPreconditioner, AmgConfig, AmgPreconditioner, CsrMatrix, DiagonalPreconditioner,
GmresConfig, IdentityPreconditioner, IluColoringPreconditioner, IluFixedPointPreconditioner,
IluPreconditioner, gmres, lu_solve,
};
use std::time::Instant;
use thiserror::Error;
pub type GmresConfigF64 = GmresConfig<f64>;
#[derive(Debug, Clone)]
pub struct SolverConfig {
pub solver_type: SolverType,
pub gmres: GmresConfigF64,
pub verbosity: usize,
pub schwarz_subdomains: usize,
pub schwarz_overlap: usize,
}
impl Default for SolverConfig {
fn default() -> Self {
Self {
solver_type: SolverType::GmresIlu,
gmres: GmresConfigF64 {
max_iterations: 1000,
restart: 50,
tolerance: 1e-10,
print_interval: 0,
},
verbosity: 0,
schwarz_subdomains: 8,
schwarz_overlap: 2,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SolverType {
Direct,
Gmres,
GmresIlu,
GmresJacobi,
GmresIluColoring,
GmresIluFixedPoint,
GmresSchwarz,
GmresAmg,
GmresPipelined,
GmresPipelinedIlu,
GmresPipelinedAmg,
}
#[derive(Debug, Clone)]
pub struct Solution {
pub values: Array1<Complex64>,
pub iterations: usize,
pub residual: f64,
pub converged: bool,
}
#[derive(Debug, Error)]
pub enum SolverError {
#[error("Solver failed to converge after {0} iterations (residual: {1})")]
ConvergenceFailure(usize, f64),
#[error("Direct solver failed: singular matrix")]
SingularMatrix,
#[error("Matrix dimension mismatch: expected {expected}, got {actual}")]
DimensionMismatch { expected: usize, actual: usize },
}
pub fn solve(problem: &HelmholtzProblem, config: &SolverConfig) -> Result<Solution, SolverError> {
let start = Instant::now();
let csr = problem.matrix.to_csr();
let rhs = Array1::from(problem.rhs.clone());
let csr_time = start.elapsed();
if config.verbosity > 0 {
println!(
" [FEM] System: {} DOFs, {} nnz, sparsity {:.4}%, CSR convert: {:.1}ms",
csr.num_rows,
csr.nnz(),
csr.sparsity() * 100.0,
csr_time.as_secs_f64() * 1000.0
);
}
let solve_start = Instant::now();
let result = match config.solver_type {
SolverType::Direct => solve_direct(&csr, &rhs, config),
SolverType::Gmres => solve_gmres(&csr, &rhs, config),
SolverType::GmresIlu => solve_gmres_ilu(&csr, &rhs, config),
SolverType::GmresJacobi => solve_gmres_jacobi(&csr, &rhs, config),
SolverType::GmresIluColoring => solve_gmres_ilu_coloring(&csr, &rhs, config),
SolverType::GmresIluFixedPoint => solve_gmres_ilu_fixedpoint(&csr, &rhs, config),
SolverType::GmresSchwarz => solve_gmres_schwarz(&csr, &rhs, config),
SolverType::GmresAmg => solve_gmres_amg(&csr, &rhs, config),
SolverType::GmresPipelined => solve_gmres_pipelined(&csr, &rhs, config),
SolverType::GmresPipelinedIlu => solve_gmres_pipelined_ilu(&csr, &rhs, config),
SolverType::GmresPipelinedAmg => solve_gmres_pipelined_amg(&csr, &rhs, config),
};
let solve_time = solve_start.elapsed();
if config.verbosity > 0 {
if let Ok(ref sol) = result {
println!(
" [FEM] Solve: {} iters, residual {:.2e}, time {:.1}ms",
sol.iterations,
sol.residual,
solve_time.as_secs_f64() * 1000.0
);
}
}
result
}
fn solve_direct(
csr: &CsrMatrix<Complex64>,
rhs: &Array1<Complex64>,
config: &SolverConfig,
) -> Result<Solution, SolverError> {
if config.verbosity > 0 {
log::info!("Using direct LU solver");
}
let dense = csr.to_dense();
let solution = lu_solve(&dense, rhs).map_err(|_| SolverError::SingularMatrix)?;
let residual_vec = csr.matvec(&solution);
let residual: f64 = residual_vec
.iter()
.zip(rhs.iter())
.map(|(r, b)| (r - b).norm())
.sum::<f64>()
/ rhs.len() as f64;
Ok(Solution {
values: solution,
iterations: 0,
residual,
converged: true,
})
}
fn solve_gmres(
csr: &CsrMatrix<Complex64>,
rhs: &Array1<Complex64>,
config: &SolverConfig,
) -> Result<Solution, SolverError> {
if config.verbosity > 0 {
log::info!(
"Using GMRES solver (restart={}, tol={})",
config.gmres.restart,
config.gmres.tolerance
);
}
let result = gmres(csr, rhs, &config.gmres);
if config.verbosity > 0 {
log::info!(
"GMRES {} in {} iterations (residual: {:.2e})",
if result.converged {
"converged"
} else {
"did not converge"
},
result.iterations,
result.residual
);
}
if !result.converged {
return Err(SolverError::ConvergenceFailure(
result.iterations,
result.residual,
));
}
Ok(Solution {
values: result.x,
iterations: result.iterations,
residual: result.residual,
converged: result.converged,
})
}
fn solve_gmres_ilu(
csr: &CsrMatrix<Complex64>,
rhs: &Array1<Complex64>,
config: &SolverConfig,
) -> Result<Solution, SolverError> {
if config.verbosity > 0 {
log::info!(
"Using GMRES+ILU solver (restart={}, tol={})",
config.gmres.restart,
config.gmres.tolerance
);
}
let ilu_start = Instant::now();
let precond = IluPreconditioner::from_csr(csr);
let ilu_time = ilu_start.elapsed();
if config.verbosity > 0 {
println!(
" [FEM] ILU factorization: {:.1}ms",
ilu_time.as_secs_f64() * 1000.0
);
}
let result = gmres_preconditioned(csr, &precond, rhs, &config.gmres);
if config.verbosity > 0 {
log::info!(
"GMRES+ILU {} in {} iterations (residual: {:.2e})",
if result.converged {
"converged"
} else {
"did not converge"
},
result.iterations,
result.residual
);
}
if !result.converged {
return Err(SolverError::ConvergenceFailure(
result.iterations,
result.residual,
));
}
Ok(Solution {
values: result.x,
iterations: result.iterations,
residual: result.residual,
converged: result.converged,
})
}
fn solve_gmres_jacobi(
csr: &CsrMatrix<Complex64>,
rhs: &Array1<Complex64>,
config: &SolverConfig,
) -> Result<Solution, SolverError> {
if config.verbosity > 0 {
log::info!(
"Using GMRES+Jacobi solver (restart={}, tol={})",
config.gmres.restart,
config.gmres.tolerance
);
}
let precond = DiagonalPreconditioner::from_csr(csr);
let result = gmres_preconditioned(csr, &precond, rhs, &config.gmres);
if config.verbosity > 0 {
log::info!(
"GMRES+Jacobi {} in {} iterations (residual: {:.2e})",
if result.converged {
"converged"
} else {
"did not converge"
},
result.iterations,
result.residual
);
}
if !result.converged {
return Err(SolverError::ConvergenceFailure(
result.iterations,
result.residual,
));
}
Ok(Solution {
values: result.x,
iterations: result.iterations,
residual: result.residual,
converged: result.converged,
})
}
fn solve_gmres_ilu_coloring(
csr: &CsrMatrix<Complex64>,
rhs: &Array1<Complex64>,
config: &SolverConfig,
) -> Result<Solution, SolverError> {
if config.verbosity > 0 {
log::info!(
"Using GMRES+ILU-Coloring solver (restart={}, tol={})",
config.gmres.restart,
config.gmres.tolerance
);
}
let ilu_start = Instant::now();
let precond = IluColoringPreconditioner::from_csr(csr);
let ilu_time = ilu_start.elapsed();
if config.verbosity > 0 {
let (fwd_levels, bwd_levels, avg_fwd, avg_bwd) = precond.level_stats();
println!(
" [FEM] ILU-Coloring: {:.1}ms, {} fwd levels (avg {:.1}), {} bwd levels (avg {:.1})",
ilu_time.as_secs_f64() * 1000.0,
fwd_levels,
avg_fwd,
bwd_levels,
avg_bwd
);
}
let result = gmres_preconditioned(csr, &precond, rhs, &config.gmres);
if config.verbosity > 0 {
log::info!(
"GMRES+ILU-Coloring {} in {} iterations (residual: {:.2e})",
if result.converged {
"converged"
} else {
"did not converge"
},
result.iterations,
result.residual
);
}
if !result.converged {
return Err(SolverError::ConvergenceFailure(
result.iterations,
result.residual,
));
}
Ok(Solution {
values: result.x,
iterations: result.iterations,
residual: result.residual,
converged: result.converged,
})
}
fn solve_gmres_ilu_fixedpoint(
csr: &CsrMatrix<Complex64>,
rhs: &Array1<Complex64>,
config: &SolverConfig,
) -> Result<Solution, SolverError> {
const FP_ITERATIONS: usize = 10;
if config.verbosity > 0 {
log::info!(
"Using GMRES+ILU-FixedPoint solver (restart={}, tol={}, fp_iters={})",
config.gmres.restart,
config.gmres.tolerance,
FP_ITERATIONS
);
}
let ilu_start = Instant::now();
let precond = IluFixedPointPreconditioner::from_csr(csr, FP_ITERATIONS);
let ilu_time = ilu_start.elapsed();
if config.verbosity > 0 {
println!(
" [FEM] ILU-FixedPoint: {:.1}ms ({} iterations per apply)",
ilu_time.as_secs_f64() * 1000.0,
FP_ITERATIONS
);
}
let result = gmres_preconditioned(csr, &precond, rhs, &config.gmres);
if config.verbosity > 0 {
log::info!(
"GMRES+ILU-FixedPoint {} in {} iterations (residual: {:.2e})",
if result.converged {
"converged"
} else {
"did not converge"
},
result.iterations,
result.residual
);
}
if !result.converged {
return Err(SolverError::ConvergenceFailure(
result.iterations,
result.residual,
));
}
Ok(Solution {
values: result.x,
iterations: result.iterations,
residual: result.residual,
converged: result.converged,
})
}
fn solve_gmres_schwarz(
csr: &CsrMatrix<Complex64>,
rhs: &Array1<Complex64>,
config: &SolverConfig,
) -> Result<Solution, SolverError> {
let num_subdomains = config.schwarz_subdomains;
let overlap = config.schwarz_overlap;
if config.verbosity > 0 {
log::info!(
"Using GMRES+Schwarz solver (restart={}, tol={}, {} subdomains, {} overlap)",
config.gmres.restart,
config.gmres.tolerance,
num_subdomains,
overlap
);
}
let schwarz_start = Instant::now();
let precond = AdditiveSchwarzPreconditioner::from_csr(csr, num_subdomains, overlap);
let schwarz_time = schwarz_start.elapsed();
if config.verbosity > 0 {
let (num_subdomains, min_size, max_size, avg_size) = precond.stats();
println!(
" [FEM] Schwarz DD: {:.1}ms, {} subdomains (size: min={}, max={}, avg={:.1})",
schwarz_time.as_secs_f64() * 1000.0,
num_subdomains,
min_size,
max_size,
avg_size
);
}
let result = gmres_preconditioned(csr, &precond, rhs, &config.gmres);
if config.verbosity > 0 {
log::info!(
"GMRES+Schwarz {} in {} iterations (residual: {:.2e})",
if result.converged {
"converged"
} else {
"did not converge"
},
result.iterations,
result.residual
);
}
if !result.converged {
return Err(SolverError::ConvergenceFailure(
result.iterations,
result.residual,
));
}
Ok(Solution {
values: result.x,
iterations: result.iterations,
residual: result.residual,
converged: result.converged,
})
}
fn solve_gmres_amg(
csr: &CsrMatrix<Complex64>,
rhs: &Array1<Complex64>,
config: &SolverConfig,
) -> Result<Solution, SolverError> {
if config.verbosity > 0 {
log::info!(
"Using GMRES+AMG solver (restart={}, tol={})",
config.gmres.restart,
config.gmres.tolerance
);
}
let amg_start = Instant::now();
let amg_config = AmgConfig::for_parallel();
let precond = AmgPreconditioner::from_csr(csr, amg_config);
let amg_time = amg_start.elapsed();
if config.verbosity > 0 {
let diag = precond.diagnostics();
println!(
" [FEM] AMG setup: {:.1}ms, {} levels, grid complexity {:.2}, operator complexity {:.2}",
amg_time.as_secs_f64() * 1000.0,
diag.num_levels,
diag.grid_complexity,
diag.operator_complexity
);
}
let result = gmres_preconditioned(csr, &precond, rhs, &config.gmres);
if config.verbosity > 0 {
log::info!(
"GMRES+AMG {} in {} iterations (residual: {:.2e})",
if result.converged {
"converged"
} else {
"did not converge"
},
result.iterations,
result.residual
);
}
if !result.converged {
return Err(SolverError::ConvergenceFailure(
result.iterations,
result.residual,
));
}
Ok(Solution {
values: result.x,
iterations: result.iterations,
residual: result.residual,
converged: result.converged,
})
}
fn solve_gmres_pipelined(
csr: &CsrMatrix<Complex64>,
rhs: &Array1<Complex64>,
config: &SolverConfig,
) -> Result<Solution, SolverError> {
if config.verbosity > 0 {
log::info!(
"Using Pipelined GMRES solver (restart={}, tol={})",
config.gmres.restart,
config.gmres.tolerance
);
}
let precond = IdentityPreconditioner;
let result = gmres_pipelined(csr, &precond, rhs, None, &config.gmres);
if config.verbosity > 0 {
log::info!(
"Pipelined GMRES {} in {} iterations (residual: {:.2e})",
if result.converged {
"converged"
} else {
"did not converge"
},
result.iterations,
result.residual
);
}
if !result.converged {
return Err(SolverError::ConvergenceFailure(
result.iterations,
result.residual,
));
}
Ok(Solution {
values: result.x,
iterations: result.iterations,
residual: result.residual,
converged: result.converged,
})
}
fn solve_gmres_pipelined_ilu(
csr: &CsrMatrix<Complex64>,
rhs: &Array1<Complex64>,
config: &SolverConfig,
) -> Result<Solution, SolverError> {
if config.verbosity > 0 {
log::info!(
"Using Pipelined GMRES+ILU solver (restart={}, tol={})",
config.gmres.restart,
config.gmres.tolerance
);
}
let ilu_start = Instant::now();
let precond = IluPreconditioner::from_csr(csr);
let ilu_time = ilu_start.elapsed();
if config.verbosity > 0 {
println!(
" [FEM] ILU factorization: {:.1}ms",
ilu_time.as_secs_f64() * 1000.0
);
}
let result = gmres_pipelined(csr, &precond, rhs, None, &config.gmres);
if config.verbosity > 0 {
log::info!(
"Pipelined GMRES+ILU {} in {} iterations (residual: {:.2e})",
if result.converged {
"converged"
} else {
"did not converge"
},
result.iterations,
result.residual
);
}
if !result.converged {
return Err(SolverError::ConvergenceFailure(
result.iterations,
result.residual,
));
}
Ok(Solution {
values: result.x,
iterations: result.iterations,
residual: result.residual,
converged: result.converged,
})
}
fn solve_gmres_pipelined_amg(
csr: &CsrMatrix<Complex64>,
rhs: &Array1<Complex64>,
config: &SolverConfig,
) -> Result<Solution, SolverError> {
if config.verbosity > 0 {
log::info!(
"Using Pipelined GMRES+AMG solver (restart={}, tol={})",
config.gmres.restart,
config.gmres.tolerance
);
}
let amg_start = Instant::now();
let amg_config = AmgConfig::for_parallel();
let precond = AmgPreconditioner::from_csr(csr, amg_config);
let amg_time = amg_start.elapsed();
if config.verbosity > 0 {
let diag = precond.diagnostics();
println!(
" [FEM] AMG setup: {:.1}ms, {} levels, grid complexity {:.2}, operator complexity {:.2}",
amg_time.as_secs_f64() * 1000.0,
diag.num_levels,
diag.grid_complexity,
diag.operator_complexity
);
}
let result = gmres_pipelined(csr, &precond, rhs, None, &config.gmres);
if config.verbosity > 0 {
log::info!(
"Pipelined GMRES+AMG {} in {} iterations (residual: {:.2e})",
if result.converged {
"converged"
} else {
"did not converge"
},
result.iterations,
result.residual
);
}
if !result.converged {
return Err(SolverError::ConvergenceFailure(
result.iterations,
result.residual,
));
}
Ok(Solution {
values: result.x,
iterations: result.iterations,
residual: result.residual,
converged: result.converged,
})
}
fn solve_gmres_with_guess(
csr: &CsrMatrix<Complex64>,
rhs: &Array1<Complex64>,
x0: Option<&Array1<Complex64>>,
config: &SolverConfig,
) -> Result<Solution, SolverError> {
let precond = IdentityPreconditioner;
let result = gmres_preconditioned_with_guess(csr, &precond, rhs, x0, &config.gmres);
if !result.converged {
return Err(SolverError::ConvergenceFailure(
result.iterations,
result.residual,
));
}
Ok(Solution {
values: result.x,
iterations: result.iterations,
residual: result.residual,
converged: result.converged,
})
}
fn solve_gmres_ilu_with_guess(
csr: &CsrMatrix<Complex64>,
rhs: &Array1<Complex64>,
x0: Option<&Array1<Complex64>>,
config: &SolverConfig,
) -> Result<Solution, SolverError> {
let precond = IluPreconditioner::from_csr(csr);
let result = gmres_preconditioned_with_guess(csr, &precond, rhs, x0, &config.gmres);
if !result.converged {
return Err(SolverError::ConvergenceFailure(
result.iterations,
result.residual,
));
}
Ok(Solution {
values: result.x,
iterations: result.iterations,
residual: result.residual,
converged: result.converged,
})
}
fn solve_gmres_jacobi_with_guess(
csr: &CsrMatrix<Complex64>,
rhs: &Array1<Complex64>,
x0: Option<&Array1<Complex64>>,
config: &SolverConfig,
) -> Result<Solution, SolverError> {
let precond = DiagonalPreconditioner::from_csr(csr);
let result = gmres_preconditioned_with_guess(csr, &precond, rhs, x0, &config.gmres);
if !result.converged {
return Err(SolverError::ConvergenceFailure(
result.iterations,
result.residual,
));
}
Ok(Solution {
values: result.x,
iterations: result.iterations,
residual: result.residual,
converged: result.converged,
})
}
fn solve_gmres_ilu_coloring_with_guess(
csr: &CsrMatrix<Complex64>,
rhs: &Array1<Complex64>,
x0: Option<&Array1<Complex64>>,
config: &SolverConfig,
) -> Result<Solution, SolverError> {
let precond = IluColoringPreconditioner::from_csr(csr);
let result = gmres_preconditioned_with_guess(csr, &precond, rhs, x0, &config.gmres);
if !result.converged {
return Err(SolverError::ConvergenceFailure(
result.iterations,
result.residual,
));
}
Ok(Solution {
values: result.x,
iterations: result.iterations,
residual: result.residual,
converged: result.converged,
})
}
fn solve_gmres_ilu_fixedpoint_with_guess(
csr: &CsrMatrix<Complex64>,
rhs: &Array1<Complex64>,
x0: Option<&Array1<Complex64>>,
config: &SolverConfig,
) -> Result<Solution, SolverError> {
const FP_ITERATIONS: usize = 10;
let precond = IluFixedPointPreconditioner::from_csr(csr, FP_ITERATIONS);
let result = gmres_preconditioned_with_guess(csr, &precond, rhs, x0, &config.gmres);
if !result.converged {
return Err(SolverError::ConvergenceFailure(
result.iterations,
result.residual,
));
}
Ok(Solution {
values: result.x,
iterations: result.iterations,
residual: result.residual,
converged: result.converged,
})
}
fn solve_gmres_schwarz_with_guess(
csr: &CsrMatrix<Complex64>,
rhs: &Array1<Complex64>,
x0: Option<&Array1<Complex64>>,
config: &SolverConfig,
) -> Result<Solution, SolverError> {
let precond = AdditiveSchwarzPreconditioner::from_csr(
csr,
config.schwarz_subdomains,
config.schwarz_overlap,
);
let result = gmres_preconditioned_with_guess(csr, &precond, rhs, x0, &config.gmres);
if !result.converged {
return Err(SolverError::ConvergenceFailure(
result.iterations,
result.residual,
));
}
Ok(Solution {
values: result.x,
iterations: result.iterations,
residual: result.residual,
converged: result.converged,
})
}
fn solve_gmres_amg_with_guess(
csr: &CsrMatrix<Complex64>,
rhs: &Array1<Complex64>,
x0: Option<&Array1<Complex64>>,
config: &SolverConfig,
) -> Result<Solution, SolverError> {
let amg_config = AmgConfig::for_parallel();
let precond = AmgPreconditioner::from_csr(csr, amg_config);
let result = gmres_preconditioned_with_guess(csr, &precond, rhs, x0, &config.gmres);
if !result.converged {
return Err(SolverError::ConvergenceFailure(
result.iterations,
result.residual,
));
}
Ok(Solution {
values: result.x,
iterations: result.iterations,
residual: result.residual,
converged: result.converged,
})
}
fn solve_gmres_pipelined_with_guess(
csr: &CsrMatrix<Complex64>,
rhs: &Array1<Complex64>,
x0: Option<&Array1<Complex64>>,
config: &SolverConfig,
) -> Result<Solution, SolverError> {
let precond = IdentityPreconditioner;
let result = gmres_pipelined(csr, &precond, rhs, x0, &config.gmres);
if !result.converged {
return Err(SolverError::ConvergenceFailure(
result.iterations,
result.residual,
));
}
Ok(Solution {
values: result.x,
iterations: result.iterations,
residual: result.residual,
converged: result.converged,
})
}
fn solve_gmres_pipelined_ilu_with_guess(
csr: &CsrMatrix<Complex64>,
rhs: &Array1<Complex64>,
x0: Option<&Array1<Complex64>>,
config: &SolverConfig,
) -> Result<Solution, SolverError> {
let precond = IluPreconditioner::from_csr(csr);
let result = gmres_pipelined(csr, &precond, rhs, x0, &config.gmres);
if !result.converged {
return Err(SolverError::ConvergenceFailure(
result.iterations,
result.residual,
));
}
Ok(Solution {
values: result.x,
iterations: result.iterations,
residual: result.residual,
converged: result.converged,
})
}
fn solve_gmres_pipelined_amg_with_guess(
csr: &CsrMatrix<Complex64>,
rhs: &Array1<Complex64>,
x0: Option<&Array1<Complex64>>,
config: &SolverConfig,
) -> Result<Solution, SolverError> {
let amg_config = AmgConfig::for_parallel();
let precond = AmgPreconditioner::from_csr(csr, amg_config);
let result = gmres_pipelined(csr, &precond, rhs, x0, &config.gmres);
if !result.converged {
return Err(SolverError::ConvergenceFailure(
result.iterations,
result.residual,
));
}
Ok(Solution {
values: result.x,
iterations: result.iterations,
residual: result.residual,
converged: result.converged,
})
}
pub fn solve_csr(
csr: &CsrMatrix<Complex64>,
rhs: &Array1<Complex64>,
config: &SolverConfig,
) -> Result<Solution, SolverError> {
solve_csr_with_guess(csr, rhs, None, config)
}
pub fn solve_csr_with_guess(
csr: &CsrMatrix<Complex64>,
rhs: &Array1<Complex64>,
x0: Option<&Array1<Complex64>>,
config: &SolverConfig,
) -> Result<Solution, SolverError> {
if csr.num_rows != rhs.len() {
return Err(SolverError::DimensionMismatch {
expected: csr.num_rows,
actual: rhs.len(),
});
}
if let Some(guess) = x0 {
if guess.len() != rhs.len() {
return Err(SolverError::DimensionMismatch {
expected: rhs.len(),
actual: guess.len(),
});
}
}
match config.solver_type {
SolverType::Direct => solve_direct(csr, rhs, config),
SolverType::Gmres => solve_gmres_with_guess(csr, rhs, x0, config),
SolverType::GmresIlu => solve_gmres_ilu_with_guess(csr, rhs, x0, config),
SolverType::GmresJacobi => solve_gmres_jacobi_with_guess(csr, rhs, x0, config),
SolverType::GmresIluColoring => solve_gmres_ilu_coloring_with_guess(csr, rhs, x0, config),
SolverType::GmresIluFixedPoint => {
solve_gmres_ilu_fixedpoint_with_guess(csr, rhs, x0, config)
}
SolverType::GmresSchwarz => solve_gmres_schwarz_with_guess(csr, rhs, x0, config),
SolverType::GmresAmg => solve_gmres_amg_with_guess(csr, rhs, x0, config),
SolverType::GmresPipelined => solve_gmres_pipelined_with_guess(csr, rhs, x0, config),
SolverType::GmresPipelinedIlu => solve_gmres_pipelined_ilu_with_guess(csr, rhs, x0, config),
SolverType::GmresPipelinedAmg => solve_gmres_pipelined_amg_with_guess(csr, rhs, x0, config),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::assembly::HelmholtzProblem;
use crate::basis::PolynomialDegree;
use crate::mesh::unit_square_triangles;
#[test]
fn test_solve_helmholtz_direct() {
let mesh = unit_square_triangles(4);
let k = Complex64::new(1.0, 0.0);
let problem = HelmholtzProblem::assemble(&mesh, PolynomialDegree::P1, k, |_, _, _| {
Complex64::new(1.0, 0.0)
});
let config = SolverConfig {
solver_type: SolverType::Direct,
..Default::default()
};
let solution = solve(&problem, &config).expect("Solver should succeed");
assert!(solution.converged);
assert_eq!(solution.values.len(), problem.num_dofs());
}
#[test]
fn test_solve_helmholtz_gmres() {
let mesh = unit_square_triangles(4);
let k = Complex64::new(1.0, 0.0);
let problem = HelmholtzProblem::assemble(&mesh, PolynomialDegree::P1, k, |_, _, _| {
Complex64::new(1.0, 0.0)
});
let config = SolverConfig {
solver_type: SolverType::Gmres,
gmres: GmresConfigF64 {
max_iterations: 100,
restart: 20,
tolerance: 1e-8,
print_interval: 0,
},
..Default::default()
};
let solution = solve(&problem, &config).expect("Solver should succeed");
assert!(solution.converged);
}
#[test]
fn test_solve_helmholtz_gmres_ilu() {
let mesh = unit_square_triangles(4);
let k = Complex64::new(1.0, 0.0);
let problem = HelmholtzProblem::assemble(&mesh, PolynomialDegree::P1, k, |_, _, _| {
Complex64::new(1.0, 0.0)
});
let config = SolverConfig {
solver_type: SolverType::GmresIlu,
gmres: GmresConfigF64 {
max_iterations: 100,
restart: 20,
tolerance: 1e-8,
print_interval: 0,
},
..Default::default()
};
let solution = solve(&problem, &config).expect("Solver should succeed");
assert!(solution.converged);
}
#[test]
fn test_csr_conversion() {
let mesh = unit_square_triangles(4);
let k = Complex64::new(1.0, 0.0);
let problem = HelmholtzProblem::assemble(&mesh, PolynomialDegree::P1, k, |_, _, _| {
Complex64::new(1.0, 0.0)
});
let csr = problem.matrix.to_csr();
assert_eq!(csr.num_rows, problem.num_dofs());
assert_eq!(csr.num_cols, problem.num_dofs());
assert!(csr.nnz() > 0);
assert!(csr.nnz() <= problem.matrix.nnz());
}
#[test]
fn test_ilu_preconditioner_improves_convergence() {
let mesh = unit_square_triangles(8); let k = Complex64::new(2.0, 0.0);
let problem = HelmholtzProblem::assemble(&mesh, PolynomialDegree::P1, k, |x, y, _| {
Complex64::new(
(x * std::f64::consts::PI).sin() * (y * std::f64::consts::PI).sin(),
0.0,
)
});
let gmres_config = GmresConfigF64 {
max_iterations: 500,
restart: 30,
tolerance: 1e-8,
print_interval: 0,
};
let config_no_precond = SolverConfig {
solver_type: SolverType::Gmres,
gmres: gmres_config.clone(),
..Default::default()
};
let config_ilu = SolverConfig {
solver_type: SolverType::GmresIlu,
gmres: gmres_config,
..Default::default()
};
let sol_no_precond = solve(&problem, &config_no_precond).expect("Should converge");
let sol_ilu = solve(&problem, &config_ilu).expect("Should converge");
assert!(
sol_ilu.iterations <= sol_no_precond.iterations + 10,
"ILU should not significantly increase iterations: {} vs {}",
sol_ilu.iterations,
sol_no_precond.iterations
);
}
#[test]
fn test_solve_helmholtz_gmres_pipelined() {
let mesh = unit_square_triangles(4);
let k = Complex64::new(1.0, 0.0);
let problem = HelmholtzProblem::assemble(&mesh, PolynomialDegree::P1, k, |_, _, _| {
Complex64::new(1.0, 0.0)
});
let config = SolverConfig {
solver_type: SolverType::GmresPipelined,
gmres: GmresConfigF64 {
max_iterations: 100,
restart: 20,
tolerance: 1e-8,
print_interval: 0,
},
..Default::default()
};
let solution = solve(&problem, &config).expect("Solver should succeed");
assert!(solution.converged);
}
#[test]
fn test_solve_helmholtz_gmres_pipelined_ilu() {
let mesh = unit_square_triangles(4);
let k = Complex64::new(1.0, 0.0);
let problem = HelmholtzProblem::assemble(&mesh, PolynomialDegree::P1, k, |_, _, _| {
Complex64::new(1.0, 0.0)
});
let config = SolverConfig {
solver_type: SolverType::GmresPipelinedIlu,
gmres: GmresConfigF64 {
max_iterations: 100,
restart: 20,
tolerance: 1e-8,
print_interval: 0,
},
..Default::default()
};
let solution = solve(&problem, &config).expect("Solver should succeed");
assert!(solution.converged);
}
}