use {
crate::linalg::faer::FaerLu,
derham::{cochain::Cochain, interpolate::interpolant::WhitneyInterpolant, section::Section},
exterior::{multiform_gramian, Covariant},
simplicial::{
atlas::{MeshPoint, SimplexQuadRule},
geometry::{cell_volume, metric::mesh::MeshLengthsSq},
linalg::{CsrMatrix, Vector},
topology::complex::Complex,
},
};
use crate::{assemble::assemble_galvec, operators::SourceElVec, whitney_complex::WhitneyComplex};
pub fn fe_l2_error<F: Section<Covariant>>(
fe_cochain: &Cochain,
exact: &F,
topology: &Complex,
geometry: &MeshLengthsSq,
) -> f64 {
let dim = topology.dim();
let grade = fe_cochain.grade();
let qr = SimplexQuadRule::degree(dim, 3);
let fe_whitney = WhitneyInterpolant::new(fe_cochain.clone(), topology);
let error_sq: f64 = topology
.cells()
.handle_iter()
.map(|cell| {
let metric = geometry.cell_metric(cell);
let inner = multiform_gramian(&metric, grade);
let error_pointwise =
|point: &MeshPoint| inner.norm_sq((exact.at(point) - fe_whitney.at(point)).coeffs());
qr.integrate_cell(cell.idx(), &error_pointwise, cell_volume(&metric))
})
.sum();
error_sq.sqrt()
}
pub fn l2_projection<F: Sync + Section<Covariant>>(
field: &F,
whitney: WhitneyComplex,
qr: Option<SimplexQuadRule>,
) -> Cochain {
let grade = field.grade();
let mass = CsrMatrix::from(&whitney.mass(grade));
let load: Vector = assemble_galvec(
whitney.topology(),
whitney.geometry(),
SourceElVec::new(field, qr),
);
Cochain::new(grade, FaerLu::new(mass).solve(&load))
}
#[cfg(test)]
mod test {
use super::*;
use derham::section::CoordFieldExt;
use glatt::field::DiffFormClosure;
use simplicial::gen::cartesian::CartesianGrid;
use approx::assert_relative_eq;
#[test]
fn l2_projection_reproduces_whitney_forms() {
for dim in 1..=3 {
let (topology, coords) = CartesianGrid::new_unit(dim, 2).triangulate();
let lengths = coords.to_edge_lengths_sq(&topology);
let whitney = WhitneyComplex::new(&topology, &lengths);
for grade in 0..=dim {
let ndofs = topology.nsimplices(grade);
let cochain = Cochain::new(
grade,
Vector::from_iterator(ndofs, (0..ndofs).map(|i| ((i % 7) as f64) - 3.0)),
);
let field = WhitneyInterpolant::new(cochain.clone(), &topology);
let qr = SimplexQuadRule::degree(dim, 3);
let projected = l2_projection(&field, whitney, Some(qr));
assert_relative_eq!(projected.coeffs(), cochain.coeffs(), epsilon = 1e-9);
}
}
}
#[test]
fn l2_error_vanishes_on_the_discrete_space() {
for dim in 1..=3 {
let (topology, coords) = CartesianGrid::new_unit(dim, 2).triangulate();
let lengths = coords.to_edge_lengths_sq(&topology);
let whitney = WhitneyComplex::new(&topology, &lengths);
let exact = DiffFormClosure::coord_component(0, dim);
let exact = exact.pullback_on(&topology, &coords);
let projected = l2_projection(&exact, whitney, Some(SimplexQuadRule::degree(dim, 3)));
let error = fe_l2_error(&projected, &exact, &topology, &lengths);
assert!(error < 1e-9, "dim={dim} error={error}");
}
}
}