use nalgebra::DMatrix;
use nalgebra_sparse::{CooMatrix, CscMatrix};
use cartan_core::Manifold;
use crate::mesh::{FlatMesh, Mesh};
pub struct ExteriorDerivative {
pub d: Vec<CscMatrix<f64>>,
}
impl ExteriorDerivative {
pub fn from_mesh(mesh: &FlatMesh) -> Self {
Self::from_mesh_sparse_generic(mesh)
}
pub fn from_mesh_sparse<M: Manifold>(mesh: &Mesh<M, 3, 2>) -> Self {
Self::from_mesh_sparse_generic(mesh)
}
pub fn from_mesh_sparse_generic<M: Manifold, const K: usize, const B: usize>(
mesh: &Mesh<M, K, B>,
) -> Self {
let nv = mesh.n_vertices();
let nb = mesh.n_boundaries();
let ns = mesh.n_simplices();
let mut tri0 = CooMatrix::new(nb, nv);
for (b, boundary) in mesh.boundaries.iter().enumerate() {
for (k, &v) in boundary.iter().enumerate() {
let sign = if k % 2 == 0 { -1.0 } else { 1.0 };
tri0.push(b, v, sign);
}
}
let d0 = CscMatrix::from(&tri0);
let mut tri1 = CooMatrix::new(ns, nb);
for (s, (local_b, local_s)) in mesh
.simplex_boundary_ids
.iter()
.zip(mesh.boundary_signs.iter())
.enumerate()
{
for k in 0..K {
tri1.push(s, local_b[k], local_s[k]);
}
}
let d1 = CscMatrix::from(&tri1);
Self { d: vec![d0, d1] }
}
pub fn from_graph(n_vertices: usize, edges: &[(usize, usize)]) -> Self {
let mut tri = CooMatrix::new(edges.len(), n_vertices);
for (e, &(i, j)) in edges.iter().enumerate() {
tri.push(e, i, -1.0);
tri.push(e, j, 1.0);
}
Self {
d: vec![CscMatrix::from(&tri)],
}
}
#[deprecated(since = "0.2.0", note = "use from_mesh_sparse or from_mesh instead")]
pub fn from_mesh_generic_dense<M: Manifold>(
mesh: &Mesh<M, 3, 2>,
) -> (DMatrix<f64>, DMatrix<f64>) {
let nv = mesh.n_vertices();
let ne = mesh.n_boundaries();
let nt = mesh.n_simplices();
let mut d0 = DMatrix::<f64>::zeros(ne, nv);
for (e, &[i, j]) in mesh.boundaries.iter().enumerate() {
d0[(e, i)] = -1.0;
d0[(e, j)] = 1.0;
}
let mut d1 = DMatrix::<f64>::zeros(nt, ne);
for (t, (local_e, local_s)) in mesh
.simplex_boundary_ids
.iter()
.zip(mesh.boundary_signs.iter())
.enumerate()
{
for k in 0..3 {
d1[(t, local_e[k])] = local_s[k];
}
}
(d0, d1)
}
pub fn d0(&self) -> &CscMatrix<f64> {
&self.d[0]
}
pub fn d1(&self) -> &CscMatrix<f64> {
&self.d[1]
}
pub fn degree(&self) -> usize {
self.d.len()
}
pub fn check_exactness(&self) -> f64 {
let mut max_err = 0.0f64;
for k in 0..self.d.len().saturating_sub(1) {
let prod = &self.d[k + 1] * &self.d[k];
for &val in prod.values() {
max_err = max_err.max(val.abs());
}
}
max_err
}
}
#[cfg(test)]
mod tests {
#[test]
fn coo_to_csr_sums_duplicate_entries() {
use nalgebra_sparse::{CooMatrix, CsrMatrix};
let mut coo = CooMatrix::<f64>::new(2, 2);
coo.push(0, 0, 1.0);
coo.push(0, 0, 2.0);
coo.push(0, 0, 4.0);
coo.push(1, 1, -3.0);
coo.push(0, 1, 0.5);
let csr = CsrMatrix::from(&coo);
assert_eq!(csr.get_entry(0, 0).unwrap().into_value(), 7.0, "duplicates must sum");
assert_eq!(csr.get_entry(1, 1).unwrap().into_value(), -3.0);
assert_eq!(csr.get_entry(0, 1).unwrap().into_value(), 0.5);
assert_eq!(csr.nnz(), 3, "summed duplicates occupy one entry");
}
#[test]
fn coo_to_csc_sums_duplicate_entries() {
use nalgebra_sparse::{CooMatrix, CscMatrix};
let mut coo = CooMatrix::<f64>::new(2, 2);
coo.push(0, 0, 1.0);
coo.push(0, 0, 2.0);
coo.push(0, 0, 4.0);
coo.push(1, 1, -3.0);
let csc = CscMatrix::from(&coo);
assert_eq!(csc.get_entry(0, 0).unwrap().into_value(), 7.0, "duplicates must sum");
assert_eq!(csc.get_entry(1, 1).unwrap().into_value(), -3.0);
assert_eq!(csc.nnz(), 2, "summed duplicates occupy one entry");
}
}