use crate::{
assemble::{assemble_galmat, GalMat},
operators::HodgeMassElmat,
};
use {
crate::linalg::quadratic_form_sparse,
derham::cochain::Cochain,
exterior::ExteriorGrade,
simplicial::{
geometry::metric::mesh::MeshLengthsSq,
linalg::{CooMatrix, CsrMatrix},
topology::{boundary::BoundaryComplex, complex::Complex, handle::KSimplexIdx, role::Facet},
Dim,
},
};
use std::collections::HashSet;
pub trait HilbertComplex {
fn dim(&self) -> Dim;
fn ndofs(&self, grade: ExteriorGrade) -> usize;
fn mass(&self, grade: ExteriorGrade) -> GalMat;
fn dif(&self, grade: ExteriorGrade) -> CsrMatrix;
fn codif_dif(&self, grade: ExteriorGrade) -> GalMat;
fn harmonic_dim(&self, grade: ExteriorGrade) -> usize;
fn inclusion(&self, grade: ExteriorGrade) -> CsrMatrix;
}
#[derive(Clone, Copy)]
pub struct WhitneyComplex<'a> {
topology: &'a Complex,
geometry: &'a MeshLengthsSq,
}
impl<'a> WhitneyComplex<'a> {
pub fn new(topology: &'a Complex, geometry: &'a MeshLengthsSq) -> Self {
Self { topology, geometry }
}
pub fn dim(&self) -> Dim {
self.topology.dim()
}
pub fn topology(&self) -> &'a Complex {
self.topology
}
pub fn geometry(&self) -> &'a MeshLengthsSq {
self.geometry
}
pub fn ndofs(&self, grade: ExteriorGrade) -> usize {
self.topology.nsimplices(grade)
}
pub fn mass(&self, grade: ExteriorGrade) -> GalMat {
assemble_galmat(
self.topology,
self.geometry,
HodgeMassElmat::new(self.dim(), grade),
)
}
pub fn dif(&self, grade: ExteriorGrade) -> CsrMatrix {
CsrMatrix::from(&self.topology.coboundary_operator(grade))
}
pub fn codif_dif(&self, grade: ExteriorGrade) -> GalMat {
if grade == self.dim() {
return GalMat::new(self.ndofs(grade), self.ndofs(grade));
}
let dif = self.dif(grade);
let mass = CsrMatrix::from(&self.mass(grade + 1));
GalMat::from(&(dif.transpose() * mass * dif))
}
pub fn norm_l2(&self, u: &Cochain) -> f64 {
let mass = CsrMatrix::from(&self.mass(u.grade()));
quadratic_form_sparse(&mass, u.coeffs()).sqrt()
}
pub fn seminorm_hdif(&self, u: &Cochain) -> f64 {
self.norm_l2(&u.dif(self.topology))
}
pub fn relative(self) -> RelativeWhitneyComplex<'a> {
RelativeWhitneyComplex::new(self)
}
pub fn relative_to(self, constrained: &BoundaryWhitneyComplex) -> RelativeWhitneyComplex<'a> {
RelativeWhitneyComplex::with_constrained(self, |grade| {
if grade <= constrained.topology().dim() {
constrained
.boundary_complex()
.parent_kidxs(grade)
.iter()
.copied()
.collect()
} else {
HashSet::new()
}
})
}
}
impl<'a> WhitneyComplex<'a> {
pub fn boundary(&self) -> Option<BoundaryWhitneyComplex> {
let facets = self.topology.boundary_facets();
(!facets.is_empty()).then(|| self.boundary_part(facets))
}
pub fn boundary_part(&self, facets: Vec<Facet>) -> BoundaryWhitneyComplex {
let boundary = self.topology.facet_subcomplex(facets);
let geometry = boundary.trace_lengths_sq(self.geometry);
BoundaryWhitneyComplex { boundary, geometry }
}
}
impl HilbertComplex for WhitneyComplex<'_> {
fn dim(&self) -> Dim {
WhitneyComplex::dim(self)
}
fn ndofs(&self, grade: ExteriorGrade) -> usize {
WhitneyComplex::ndofs(self, grade)
}
fn mass(&self, grade: ExteriorGrade) -> GalMat {
WhitneyComplex::mass(self, grade)
}
fn dif(&self, grade: ExteriorGrade) -> CsrMatrix {
WhitneyComplex::dif(self, grade)
}
fn codif_dif(&self, grade: ExteriorGrade) -> GalMat {
WhitneyComplex::codif_dif(self, grade)
}
fn harmonic_dim(&self, grade: ExteriorGrade) -> usize {
self.topology.betti_number(grade)
}
fn inclusion(&self, grade: ExteriorGrade) -> CsrMatrix {
let n = WhitneyComplex::ndofs(self, grade);
let mut coo = CooMatrix::new(n, n);
for i in 0..n {
coo.push(i, i, 1.0);
}
CsrMatrix::from(&coo)
}
}
pub struct BoundaryWhitneyComplex {
boundary: BoundaryComplex,
geometry: MeshLengthsSq,
}
impl BoundaryWhitneyComplex {
pub fn whitney_complex(&self) -> WhitneyComplex<'_> {
WhitneyComplex::new(self.boundary.complex(), &self.geometry)
}
pub fn topology(&self) -> &Complex {
self.boundary.complex()
}
pub fn geometry(&self) -> &MeshLengthsSq {
&self.geometry
}
pub fn boundary_complex(&self) -> &BoundaryComplex {
&self.boundary
}
pub fn ndofs(&self, grade: ExteriorGrade) -> usize {
self.boundary.complex().nsimplices(grade)
}
pub fn trace(&self, grade: ExteriorGrade) -> CsrMatrix {
CsrMatrix::from(&self.boundary.trace_operator(grade))
}
pub fn trace_cochain(&self, u: &Cochain) -> Cochain {
Cochain::new(u.grade(), self.trace(u.grade()) * u.coeffs())
}
pub fn extend_cochain(&self, g: &Cochain) -> Cochain {
Cochain::new(g.grade(), self.trace(g.grade()).transpose() * g.coeffs())
}
}
pub struct RelativeWhitneyComplex<'a> {
full: WhitneyComplex<'a>,
interior_simps: Vec<Vec<KSimplexIdx>>,
}
impl<'a> RelativeWhitneyComplex<'a> {
pub fn new(full: WhitneyComplex<'a>) -> Self {
Self::with_constrained(full, |grade| {
full
.topology()
.boundary_simplices(grade)
.into_iter()
.map(|idx| idx.kidx)
.collect()
})
}
fn with_constrained(
full: WhitneyComplex<'a>,
constrained: impl Fn(ExteriorGrade) -> HashSet<KSimplexIdx>,
) -> Self {
let interior_simps = (0..=full.dim())
.map(|grade| {
let constrained = constrained(grade);
(0..full.ndofs(grade))
.filter(|kidx| !constrained.contains(kidx))
.collect()
})
.collect();
Self {
full,
interior_simps,
}
}
pub fn full(&self) -> WhitneyComplex<'a> {
self.full
}
pub fn dim(&self) -> Dim {
self.full.dim()
}
pub fn ndofs(&self, grade: ExteriorGrade) -> usize {
self.interior_simps[grade].len()
}
pub fn inclusion(&self, grade: ExteriorGrade) -> CsrMatrix {
let mut coo = CooMatrix::new(self.full.ndofs(grade), self.ndofs(grade));
for (relative, &full) in self.interior_simps[grade].iter().enumerate() {
coo.push(full, relative, 1.0);
}
CsrMatrix::from(&coo)
}
pub fn mass(&self, grade: ExteriorGrade) -> GalMat {
let incl = self.inclusion(grade);
let mass = CsrMatrix::from(&self.full.mass(grade));
GalMat::from(&(incl.transpose() * mass * incl))
}
pub fn dif(&self, grade: ExteriorGrade) -> CsrMatrix {
self.inclusion(grade + 1).transpose() * self.full.dif(grade) * self.inclusion(grade)
}
pub fn codif_dif(&self, grade: ExteriorGrade) -> GalMat {
if grade == self.dim() {
return GalMat::new(self.ndofs(grade), self.ndofs(grade));
}
let dif = self.dif(grade);
let mass = CsrMatrix::from(&self.mass(grade + 1));
GalMat::from(&(dif.transpose() * mass * dif))
}
pub fn extend_by_zero(&self, u: &Cochain) -> Cochain {
Cochain::new(u.grade(), self.inclusion(u.grade()) * u.coeffs())
}
pub fn restrict(&self, u: &Cochain) -> Cochain {
Cochain::new(
u.grade(),
self.inclusion(u.grade()).transpose() * u.coeffs(),
)
}
}
impl HilbertComplex for RelativeWhitneyComplex<'_> {
fn dim(&self) -> Dim {
RelativeWhitneyComplex::dim(self)
}
fn ndofs(&self, grade: ExteriorGrade) -> usize {
RelativeWhitneyComplex::ndofs(self, grade)
}
fn mass(&self, grade: ExteriorGrade) -> GalMat {
RelativeWhitneyComplex::mass(self, grade)
}
fn dif(&self, grade: ExteriorGrade) -> CsrMatrix {
RelativeWhitneyComplex::dif(self, grade)
}
fn codif_dif(&self, grade: ExteriorGrade) -> GalMat {
RelativeWhitneyComplex::codif_dif(self, grade)
}
fn harmonic_dim(&self, grade: ExteriorGrade) -> usize {
self.full.topology().relative_betti_number(grade)
}
fn inclusion(&self, grade: ExteriorGrade) -> CsrMatrix {
RelativeWhitneyComplex::inclusion(self, grade)
}
}