use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::collections::HashMap;
use std::collections::HashSet;
use num_bigint::BigInt;
use num_traits::One;
use num_traits::Zero;
use serde::Deserialize;
use serde::Serialize;
use sprs_rssn::CsMat;
use sprs_rssn::TriMat;
use crate::numerical::sparse::rank;
use crate::symbolic::core::Expr;
use crate::symbolic::matrix;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Simplex(pub BTreeSet<usize>);
impl Simplex {
#[must_use]
pub fn new(vertices: &[usize]) -> Self {
Self(vertices.iter().copied().collect())
}
#[must_use]
pub fn dimension(&self) -> usize {
self.0.len().saturating_sub(1)
}
#[must_use]
pub fn boundary(&self) -> (Vec<Self>, Vec<f64>) {
let mut faces = Vec::new();
let mut coeffs = Vec::new();
let vertices: Vec<_> = self.0.iter().copied().collect();
if self.dimension() == 0 {
return (faces, coeffs);
}
for i in 0..vertices.len() {
let face_vertices: BTreeSet<_> = vertices
.iter()
.enumerate()
.filter(|(j, _)| *j != i)
.map(|(_, &v)| v)
.collect();
faces.push(Self(face_vertices));
coeffs.push(if i % 2 == 0 { 1.0 } else { -1.0 });
}
(faces, coeffs)
}
#[must_use]
pub fn symbolic_boundary(&self) -> (Vec<Self>, Vec<Expr>) {
let mut faces = Vec::new();
let mut coeffs = Vec::new();
let vertices: Vec<_> = self.0.iter().copied().collect();
if self.dimension() == 0 {
return (faces, coeffs);
}
for i in 0..vertices.len() {
let face_vertices: BTreeSet<_> = vertices
.iter()
.enumerate()
.filter(|(j, _)| *j != i)
.map(|(_, &v)| v)
.collect();
faces.push(Self(face_vertices));
coeffs.push(if i % 2 == 0 {
Expr::BigInt(BigInt::one())
} else {
Expr::BigInt(BigInt::from(-1))
});
}
(faces, coeffs)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Chain {
pub terms: HashMap<Simplex, f64>,
pub dimension: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SymbolicChain {
pub terms: HashMap<Simplex, Expr>,
pub dimension: usize,
}
pub type Cochain = Chain;
pub type SymbolicCochain = SymbolicChain;
impl Chain {
#[must_use]
pub fn new(dimension: usize) -> Self {
Self {
terms: HashMap::new(),
dimension,
}
}
pub fn add_term(
&mut self,
simplex: Simplex,
coeff: f64,
) -> Result<(), String> {
if simplex.dimension() != self.dimension {
return Err("Cannot add simplex \
of wrong dimension \
to chain."
.to_string());
}
*self.terms.entry(simplex).or_insert(0.0) += coeff;
Ok(())
}
}
impl SymbolicChain {
#[must_use]
pub fn new(dimension: usize) -> Self {
Self {
terms: HashMap::new(),
dimension,
}
}
pub fn add_term(
&mut self,
simplex: Simplex,
coeff: Expr,
) -> Result<(), String> {
if simplex.dimension() != self.dimension {
return Err("Cannot add simplex \
of wrong dimension \
to chain."
.to_string());
}
let entry = self
.terms
.entry(simplex)
.or_insert(Expr::BigInt(BigInt::zero()));
*entry = Expr::new_add(entry.clone(), coeff);
Ok(())
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SimplicialComplex {
simplices: HashSet<Simplex>,
simplices_by_dim: BTreeMap<usize, Vec<Simplex>>,
}
pub(crate) fn add_faces(
complex: &mut SimplicialComplex,
s: &Simplex,
) {
if complex.simplices.insert(s.clone()) {
let dim = s.dimension();
complex
.simplices_by_dim
.entry(dim)
.or_default()
.push(s.clone());
if dim > 0 {
let (boundary_faces, _) = s.boundary();
for face in boundary_faces {
add_faces(complex, &face);
}
}
}
}
impl SimplicialComplex {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn add_simplex(
&mut self,
vertices: &[usize],
) {
let simplex = Simplex::new(vertices);
add_faces(self, &simplex);
}
#[must_use]
pub fn dimension(&self) -> Option<usize> {
self.simplices_by_dim.keys().max().copied()
}
#[must_use]
pub fn get_simplices_by_dim(
&self,
dim: usize,
) -> Option<&Vec<Simplex>> {
self.simplices_by_dim.get(&dim)
}
#[must_use]
pub fn get_boundary_matrix(
&self,
k: usize,
) -> Option<CsMat<f64>> {
if k == 0 {
return None;
}
let k_simplices = self.get_simplices_by_dim(k)?;
let k_minus_1_simplices = self.get_simplices_by_dim(k - 1)?;
let k_minus_1_map: HashMap<&Simplex, usize> = k_minus_1_simplices
.iter()
.enumerate()
.map(|(i, s)| (s, i))
.collect();
let mut triplets = Vec::new();
for (j, simplex_k) in k_simplices.iter().enumerate() {
let (boundary_faces, coeffs) = simplex_k.boundary();
for (i, face) in boundary_faces.iter().enumerate() {
if let Some(&row_idx) = k_minus_1_map.get(face) {
triplets.push((row_idx, j, coeffs[i]));
}
}
}
Some(csr_from_triplets(
k_minus_1_simplices.len(),
k_simplices.len(),
&triplets,
))
}
#[must_use]
pub fn get_symbolic_boundary_matrix(
&self,
k: usize,
) -> Option<Expr> {
if k == 0 {
return None;
}
let k_simplices = self.get_simplices_by_dim(k)?;
let k_minus_1_simplices = self.get_simplices_by_dim(k - 1)?;
let k_minus_1_map: HashMap<&Simplex, usize> = k_minus_1_simplices
.iter()
.enumerate()
.map(|(i, s)| (s, i))
.collect();
let rows = k_minus_1_simplices.len();
let cols = k_simplices.len();
let mut matrix = vec![vec![Expr::BigInt(BigInt::zero()); cols]; rows];
for (j, simplex_k) in k_simplices.iter().enumerate() {
let (boundary_faces, coeffs) = simplex_k.symbolic_boundary();
for (i, face) in boundary_faces.iter().enumerate() {
if let Some(&row_idx) = k_minus_1_map.get(face) {
matrix[row_idx][j] = coeffs[i].clone();
}
}
}
Some(Expr::Matrix(matrix))
}
#[must_use]
pub fn apply_boundary_operator(
&self,
chain: &Chain,
) -> Option<Chain> {
let k = chain.dimension;
if k == 0 {
return Some(Chain::new(0));
}
let boundary_matrix = self.get_boundary_matrix(k)?;
let k_simplices = self.get_simplices_by_dim(k)?;
let k_minus_1_simplices = self.get_simplices_by_dim(k - 1)?;
let mut input_vec = vec![0.0; k_simplices.len()];
for (i, simplex) in k_simplices.iter().enumerate() {
if let Some(&coeff) = chain.terms.get(simplex) {
input_vec[i] = coeff;
}
}
let output_vec =
crate::numerical::sparse::sp_mat_vec_mul(&boundary_matrix, &input_vec).ok()?;
let mut result_chain = Chain::new(k - 1);
for (i, &coeff) in output_vec.iter().enumerate() {
if coeff.abs() > 1e-9 {
result_chain
.add_term(k_minus_1_simplices[i].clone(), coeff)
.ok()?;
}
}
Some(result_chain)
}
#[must_use]
pub fn apply_symbolic_boundary_operator(
&self,
chain: &SymbolicChain,
) -> Option<SymbolicChain> {
let k = chain.dimension;
if k == 0 {
return Some(SymbolicChain::new(0));
}
let boundary_matrix = self.get_symbolic_boundary_matrix(k)?;
let k_simplices = self.get_simplices_by_dim(k)?;
let k_minus_1_simplices = self.get_simplices_by_dim(k - 1)?;
let mut input_vec = vec![vec![Expr::BigInt(BigInt::zero())]; k_simplices.len()];
for (i, simplex) in k_simplices.iter().enumerate() {
if let Some(coeff) = chain.terms.get(simplex) {
input_vec[i][0] = coeff.clone();
}
}
let input_matrix = Expr::Matrix(input_vec);
let output_matrix_expr = matrix::mul_matrices(&boundary_matrix, &input_matrix);
let output_vec = if let Expr::Matrix(rows) = output_matrix_expr {
rows
} else {
return None;
};
let mut result_chain = SymbolicChain::new(k - 1);
for (i, row) in output_vec.iter().enumerate() {
let coeff = crate::symbolic::simplify_dag::simplify(&row[0]);
if !crate::symbolic::simplify::is_zero(&coeff) {
result_chain
.add_term(k_minus_1_simplices[i].clone(), coeff)
.ok()?;
}
}
Some(result_chain)
}
#[must_use]
pub fn compute_euler_characteristic(&self) -> isize {
let mut ch = 0;
for (dim, simplices) in &self.simplices_by_dim {
let term = simplices.len() as isize;
if dim % 2 == 0 {
ch += term;
} else {
ch -= term;
}
}
ch
}
}
pub struct ChainComplex {
pub complex: SimplicialComplex,
pub boundary_operators: BTreeMap<usize, CsMat<f64>>,
pub coboundary_operators: BTreeMap<usize, CsMat<f64>>,
}
impl ChainComplex {
#[must_use]
pub fn new(complex: SimplicialComplex) -> Self {
let mut boundary_operators = BTreeMap::new();
let mut coboundary_operators = BTreeMap::new();
if let Some(max_dim) = complex.dimension() {
for k in 1..=max_dim {
if let Some(matrix) = complex.get_boundary_matrix(k) {
coboundary_operators.insert(k - 1, matrix.transpose_view().to_owned());
boundary_operators.insert(k, matrix);
}
}
}
Self {
complex,
boundary_operators,
coboundary_operators,
}
}
#[must_use]
pub fn verify_boundary_property(&self) -> bool {
if let Some(max_dim) = self.complex.dimension() {
for k in 1..max_dim {
if let (Some(d_k), Some(d_k_plus_1)) = (
self.boundary_operators.get(&k),
self.boundary_operators.get(&(k + 1)),
) {
if (d_k * d_k_plus_1).nnz() != 0 {
return false;
}
}
}
}
true
}
#[must_use]
pub fn verify_coboundary_property(&self) -> bool {
if let Some(max_dim) = self.complex.dimension() {
for k in 1..max_dim {
if let (Some(d_k), Some(d_k_minus_1)) = (
self.coboundary_operators.get(&k),
self.coboundary_operators.get(&(k - 1)),
) {
if (d_k * d_k_minus_1).nnz() != 0 {
return false;
}
}
}
}
true
}
pub fn compute_homology_betti_number(
&self,
k: usize,
) -> Option<usize> {
let num_k_simplices = self.complex.get_simplices_by_dim(k)?.len();
let rank_dk = if k == 0 {
0
} else {
self.boundary_operators.get(&k).map_or(0, rank)
};
let rank_dk_plus_1 = self.boundary_operators.get(&(k + 1)).map_or(0, rank);
let dim_ker_k = num_k_simplices.saturating_sub(rank_dk);
let dim_im_k_plus_1 = rank_dk_plus_1;
Some(dim_ker_k.saturating_sub(dim_im_k_plus_1))
}
pub fn compute_cohomology_betti_number(
&self,
k: usize,
) -> Option<usize> {
let num_k_simplices = self.complex.get_simplices_by_dim(k)?.len();
let rank_dk_t = self.coboundary_operators.get(&k).map_or(0, rank);
let rank_dk_minus_1_t = if k == 0 {
0
} else {
self.coboundary_operators.get(&(k - 1)).map_or(0, rank)
};
let dim_ker_dk = num_k_simplices.saturating_sub(rank_dk_t);
let dim_im_dk_minus_1 = rank_dk_minus_1_t;
Some(dim_ker_dk.saturating_sub(dim_im_dk_minus_1))
}
}
pub struct Filtration {
pub steps: Vec<(f64, SimplicialComplex)>,
}
#[must_use]
pub fn create_grid_complex(
width: usize,
height: usize,
) -> SimplicialComplex {
let mut complex = SimplicialComplex::new();
for i in 0..height {
for j in 0..width {
let v0 = i * (width + 1) + j;
let v1 = v0 + 1;
let v2 = (i + 1) * (width + 1) + j;
let v3 = v2 + 1;
complex.add_simplex(&[v0, v1, v2]);
complex.add_simplex(&[v1, v3, v2]);
}
}
complex
}
#[must_use]
pub fn create_torus_complex(
m: usize,
n: usize,
) -> SimplicialComplex {
let mut complex = SimplicialComplex::new();
for i in 0..m {
for j in 0..n {
let v0 = i * n + j;
let v1 = i * n + (j + 1) % n;
let v2 = ((i + 1) % m) * n + j;
let v3 = ((i + 1) % m) * n + (j + 1) % n;
complex.add_simplex(&[v0, v1, v2]);
complex.add_simplex(&[v1, v3, v2]);
}
}
complex
}
#[must_use]
pub fn vietoris_rips_filtration(
points: &[Vec<f64>],
max_epsilon: f64,
steps: usize,
) -> Filtration {
let mut filtration = Filtration { steps: Vec::new() };
let num_points = points.len();
for step in 0..=steps {
let epsilon = max_epsilon * (step as f64 / steps as f64);
let mut complex = SimplicialComplex::new();
for i in 0..num_points {
complex.add_simplex(&[i]);
}
for i in 0..num_points {
for j in (i + 1)..num_points {
let dist_sq = points[i]
.iter()
.zip(&points[j])
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>();
if dist_sq.sqrt() <= epsilon {
complex.add_simplex(&[i, j]);
}
}
}
filtration.steps.push((epsilon, complex));
}
filtration
}
pub(crate) fn csr_from_triplets(
rows: usize,
cols: usize,
triplets: &[(usize, usize, f64)],
) -> CsMat<f64> {
let mut mat = TriMat::new((rows, cols));
for &(r, c, v) in triplets {
mat.add_triplet(r, c, v);
}
mat.to_csr()
}