#[path = "util/mod.rs"]
mod util;
use {
formoniq::{problems::elliptic, whitney_complex::WhitneyComplex},
simplicial::gen::cartesian::CartesianGrid,
util::{algebraic_convergence_rate, report, BoundaryCondition},
};
use std::f64::consts::PI;
fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let interactive = std::env::args()
.nth(1)
.is_some_and(|arg| arg == "-i" || arg == "--interactive");
if interactive {
interactive_mesh()
} else {
box_sweep();
Ok(())
}
}
fn box_sweep() {
let neigen = 6;
const BCS: [BoundaryCondition; 2] = [BoundaryCondition::Absolute, BoundaryCondition::Relative];
for dim in 0..=3 {
for grade in 0..=dim {
const MAX_DOFS: usize = 20_000;
let mut history: [Vec<Vec<f64>>; 2] = [const { Vec::new() }; 2];
let mut rows: [Vec<String>; 2] = [const { Vec::new() }; 2];
let mut prev_ndofs = 0;
for irefine in 0u32..=8 {
let nboxes_per_dim = 2usize.pow(irefine);
let grid = CartesianGrid::new_unit_scaled(dim, nboxes_per_dim, PI);
let (topology, coords) = grid.triangulate();
let metric = coords.to_edge_lengths_sq(&topology);
let whitney = WhitneyComplex::new(&topology, &metric);
let ndofs = whitney.ndofs(grade)
+ if grade > 0 {
whitney.ndofs(grade - 1)
} else {
0
};
if !history[0].is_empty() && (ndofs > MAX_DOFS || ndofs == prev_ndofs) {
break;
}
prev_ndofs = ndofs;
let ncells = topology.cells().len();
for (i, bc) in BCS.into_iter().enumerate() {
let relative = bc == BoundaryCondition::Relative;
let (eigenvals, _, _) = if relative {
elliptic::solve_evp(&whitney.relative(), grade, neigen).unwrap()
} else {
elliptic::solve_evp(&whitney, grade, neigen).unwrap()
};
let eigenvals: Vec<f64> = eigenvals.iter().copied().collect();
let rates: Option<Vec<f64>> = match history[i].as_slice() {
[.., older, newer] => Some(
(0..eigenvals.len().min(newer.len()).min(older.len()))
.map(|j| {
let (d_old, d_new) =
((newer[j] - older[j]).abs(), (eigenvals[j] - newer[j]).abs());
algebraic_convergence_rate(d_new, d_old)
})
.collect(),
),
_ => None,
};
let mut row = format!("| {irefine:>2} | {ncells:>7} |");
for (j, &lambda) in eigenvals.iter().enumerate() {
let rate = rates
.as_ref()
.and_then(|rates| rates.get(j).copied())
.filter(|_| lambda.abs() > 1e-6);
row.push_str(&format!(
" {:>7}({:>6})",
report::eigval(lambda),
report::rate(rate)
));
}
rows[i].push(row);
history[i].push(eigenvals);
}
}
for (i, bc) in BCS.into_iter().enumerate() {
println!(
"\nHodge-Laplace spectrum — dim {dim}, grade {grade}, {}",
bc.label()
);
println!(
"| {:>2} | {:>7} | lowest {neigen} eigenvalues (self-conv rate)",
"r", "ncells"
);
for row in &rows[i] {
println!("{row}");
}
}
}
}
}
fn interactive_mesh() -> Result<(), Box<dyn std::error::Error>> {
let prompt = |msg: &str| -> Result<String, Box<dyn std::error::Error>> {
println!("{msg}");
let mut line = String::new();
std::io::stdin().read_line(&mut line)?;
Ok(line.trim().to_string())
};
let path = std::path::PathBuf::from(prompt("Enter mesh file path (.msh).")?);
let (topology, coords) = match path.extension().and_then(|e| e.to_str()) {
Some("msh") => simplicial::io::gmsh::gmsh2coord_complex(&std::fs::read(path)?),
_ => return Err("Unknown or missing file extension.".into()),
};
let metric = coords.to_edge_lengths_sq(&topology);
let grade: usize = prompt("Enter exterior grade.")?.parse()?;
let neigen: usize = prompt("Enter number of eigenvalues.")?.parse()?;
let (eigenvals, _, _) =
elliptic::solve_evp(&WhitneyComplex::new(&topology, &metric), grade, neigen)?;
for (i, &lambda) in eigenvals.iter().enumerate() {
println!("eigenvalue {i}: {lambda:.4}");
}
Ok(())
}