use crate::{
assemble::{GalMat, GalVec},
whitney_complex::HilbertComplex,
};
use {
crate::linalg::{
eigen::{sparse_shift_invert_eigen, EigenError},
faer::FaerLu,
},
derham::cochain::Cochain,
exterior::ExteriorGrade,
};
use itertools::Itertools;
use simplicial::linalg::{CooMatrix, CooMatrixExt, CsrMatrix, Matrix, Vector};
use std::mem;
pub fn solve_source<C: HilbertComplex>(
complex: &C,
source_galvec: GalVec,
grade: ExteriorGrade,
) -> Result<(Cochain, Cochain, Cochain), EigenError> {
let harmonics = solve_harmonics(complex, grade)?;
let galmats = MixedGalMats::compute(complex, grade);
let mass_u = CsrMatrix::from(&galmats.mass_u);
let mass_harmonics = &mass_u * &harmonics;
let sigma_len = galmats.sigma_len();
let u_len = galmats.u_len();
let mut galmat = galmats.mixed_hodge_laplacian();
galmat.grow(mass_harmonics.ncols(), mass_harmonics.ncols());
for (mut r, mut c) in (0..mass_harmonics.nrows()).cartesian_product(0..mass_harmonics.ncols()) {
let v = mass_harmonics[(r, c)];
r += sigma_len;
c += sigma_len + u_len;
galmat.push(r, c, v);
}
for (mut r, mut c) in (0..mass_harmonics.nrows()).cartesian_product(0..mass_harmonics.ncols()) {
let v = mass_harmonics[(r, c)];
mem::swap(&mut r, &mut c);
r += sigma_len + u_len;
c += sigma_len;
galmat.push(r, c, v);
}
let system_matrix = CsrMatrix::from(&galmat);
let source = complex.inclusion(grade).transpose() * source_galvec;
#[allow(clippy::toplevel_ref_arg)]
let rhs = na::stack![
Vector::zeros(sigma_len);
source;
Vector::zeros(harmonics.ncols());
];
let galsol = FaerLu::new(system_matrix).solve(&rhs);
let sigma_coeffs = galsol.view_range(..sigma_len, 0).into_owned();
let u_coeffs = galsol
.view_range(sigma_len..sigma_len + u_len, 0)
.into_owned();
let p_coeffs = galsol.view_range(sigma_len + u_len.., 0).into_owned();
let sigma = if grade > 0 {
Cochain::new(grade - 1, complex.inclusion(grade - 1) * sigma_coeffs)
} else {
Cochain::new(0, sigma_coeffs)
};
let u = Cochain::new(grade, complex.inclusion(grade) * u_coeffs);
let p = Cochain::new(grade, p_coeffs);
Ok((sigma, u, p))
}
pub fn solve_harmonics<C: HilbertComplex>(
complex: &C,
grade: ExteriorGrade,
) -> Result<Matrix, EigenError> {
let homology_dim = complex.harmonic_dim(grade);
if homology_dim == 0 {
let nwhitneys = complex.ndofs(grade);
return Ok(Matrix::zeros(nwhitneys, 0));
}
let (eigenvals, _, harmonics) = solve_evp(complex, grade, homology_dim)?;
assert!(eigenvals.iter().all(|&eigenval| eigenval <= 1e-12));
Ok(harmonics)
}
pub fn solve_evp<C: HilbertComplex>(
complex: &C,
grade: ExteriorGrade,
neigenvalues: usize,
) -> Result<(Vector, Matrix, Matrix), EigenError> {
let galmats = MixedGalMats::compute(complex, grade);
let lhs = galmats.mixed_hodge_laplacian();
let sigma_len = galmats.sigma_len();
let u_len = galmats.u_len();
let mut rhs = CooMatrix::zeros(sigma_len + u_len, sigma_len + u_len);
for (mut r, mut c, &v) in galmats.mass_u.triplet_iter() {
r += sigma_len;
c += sigma_len;
rhs.push(r, c, v);
}
let (eigenvals, eigenvectors) =
sparse_shift_invert_eigen(&(&lhs).into(), &(&rhs).into(), 0.0, neigenvalues)?;
let eigen_sigmas = eigenvectors.rows(0, sigma_len).into_owned();
let eigen_us = eigenvectors.rows(sigma_len, u_len).into_owned();
Ok((eigenvals, eigen_sigmas, eigen_us))
}
pub struct HodgeBlocks {
pub n_sigma: usize,
pub n_u: usize,
pub n_omega: usize,
pub mass_sigma: CsrMatrix,
pub mass_u: CsrMatrix,
pub mass_omega: CsrMatrix,
pub dif_dn: CsrMatrix,
pub dif_up: CsrMatrix,
}
impl HodgeBlocks {
pub fn compute<C: HilbertComplex>(complex: &C, grade: ExteriorGrade) -> Self {
assert!(grade <= complex.dim());
let empty = |r: usize, c: usize| CsrMatrix::from(&CooMatrix::zeros(r, c));
let n_u = complex.ndofs(grade);
let mass_u = CsrMatrix::from(&complex.mass(grade));
let (n_sigma, mass_sigma, dif_dn) = if grade > 0 {
let n_sigma = complex.ndofs(grade - 1);
(
n_sigma,
CsrMatrix::from(&complex.mass(grade - 1)),
complex.dif(grade - 1),
)
} else {
(0, empty(0, 0), empty(n_u, 0))
};
let (n_omega, mass_omega, dif_up) = if grade < complex.dim() {
let n_omega = complex.ndofs(grade + 1);
(
n_omega,
CsrMatrix::from(&complex.mass(grade + 1)),
complex.dif(grade),
)
} else {
(0, empty(0, 0), empty(0, n_u))
};
Self {
n_sigma,
n_u,
n_omega,
mass_sigma,
mass_u,
mass_omega,
dif_dn,
dif_up,
}
}
pub fn codif_dn(&self) -> CsrMatrix {
self.dif_dn.transpose() * &self.mass_u
}
pub fn dif_sigma(&self) -> CsrMatrix {
&self.mass_u * &self.dif_dn
}
pub fn codif_up(&self) -> CsrMatrix {
self.dif_up.transpose() * &self.mass_omega
}
pub fn dif_omega(&self) -> CsrMatrix {
&self.mass_omega * &self.dif_up
}
pub fn stiff(&self) -> CsrMatrix {
self.dif_up.transpose() * &self.mass_omega * &self.dif_up
}
}
pub struct MixedGalMats {
mass_sigma: GalMat,
dif_sigma: GalMat,
codif_u: GalMat,
codifdif_u: GalMat,
mass_u: GalMat,
}
impl MixedGalMats {
pub fn compute<C: HilbertComplex>(complex: &C, grade: ExteriorGrade) -> Self {
assert!(grade <= complex.dim());
let mass_u = complex.mass(grade);
let mass_u_csr = CsrMatrix::from(&mass_u);
let (mass_sigma, dif_sigma, codif_u) = if grade > 0 {
let mass_sigma = complex.mass(grade - 1);
let exdif_sigma = complex.dif(grade - 1);
let dif_sigma = &mass_u_csr * &exdif_sigma;
let dif_sigma = CooMatrix::from(&dif_sigma);
let codif_u = &exdif_sigma.transpose() * &mass_u_csr;
let codif_u = CooMatrix::from(&codif_u);
(mass_sigma, dif_sigma, codif_u)
} else {
let u_len = mass_u.nrows();
(
GalMat::new(0, 0),
GalMat::new(u_len, 0),
GalMat::new(0, u_len),
)
};
let codifdif_u = if grade < complex.dim() {
complex.codif_dif(grade)
} else {
GalMat::new(mass_u.nrows(), mass_u.nrows())
};
Self {
mass_sigma,
dif_sigma,
codif_u,
codifdif_u,
mass_u,
}
}
pub fn sigma_len(&self) -> usize {
self.mass_sigma.nrows()
}
pub fn u_len(&self) -> usize {
self.mass_u.nrows()
}
pub fn mixed_hodge_laplacian(&self) -> CooMatrix {
let Self {
mass_sigma,
dif_sigma,
codif_u,
codifdif_u,
..
} = self;
let codif_u = codif_u.clone();
CooMatrix::block(&[&[mass_sigma, &(codif_u.neg())], &[dif_sigma, codifdif_u]])
}
}