holos-tda 0.9.0

Vietoris-Rips persistence and checked degree-Rips modules
Documentation
use super::api::cohomology_space;
use super::complex::{ActiveComplex, space_from_complex, space_from_complex_nullspace};
use super::digest::active_graph_digest;
use super::forest::forest_cocycles;
use super::model::CohomologyLimits;
use crate::SparseDistanceMatrix;

fn assert_matches_reference(graph: &SparseDistanceMatrix, scale: f64, modulus: u32) {
    let limits = CohomologyLimits::default();
    let active_edges = graph
        .edges()
        .filter(|&(_, _, value)| value <= scale)
        .map(|(u, v, _)| (u, v))
        .collect::<Vec<_>>();
    let digest = active_graph_digest(graph, scale);
    let fast = space_from_complex(
        ActiveComplex::build(graph.len(), 1, &active_edges, limits).unwrap(),
        graph.len(),
        1,
        scale,
        modulus,
        digest,
        limits,
    )
    .unwrap();
    let reference = space_from_complex_nullspace(
        ActiveComplex::build(graph.len(), 1, &active_edges, limits).unwrap(),
        graph.len(),
        1,
        scale,
        modulus,
        digest,
        limits,
    )
    .unwrap();
    let public = cohomology_space(graph, 1, scale, modulus, limits).unwrap();

    assert_eq!(fast.basis_vectors, reference.basis_vectors);
    assert_eq!(fast.coboundaries, reference.coboundaries);
    assert_eq!(fast.simplices, reference.simplices);
    assert_eq!(fast.simplex_counts, reference.simplex_counts);
    assert_eq!(fast.id, reference.id);
    assert_eq!(fast.basis, reference.basis);
    assert_eq!(public.id, reference.id);
    assert_eq!(public.basis, reference.basis);
}

fn graph_from_mask(vertex_count: usize, mask: u64, weight: f64) -> SparseDistanceMatrix {
    let mut bit = 0;
    let mut edges = Vec::new();
    for u in 0..vertex_count {
        for v in (u + 1)..vertex_count {
            if mask & (1 << bit) != 0 {
                edges.push((u, v, weight));
            }
            bit += 1;
        }
    }
    SparseDistanceMatrix::from_triplets(vertex_count, &edges).unwrap()
}

#[test]
fn forest_matches_unrestricted_nullspace_on_all_small_graphs() {
    for vertex_count in 0usize..=5 {
        let pair_count = vertex_count * vertex_count.saturating_sub(1) / 2;
        for mask in 0..(1u64 << pair_count) {
            let graph = graph_from_mask(vertex_count, mask, 1.0);
            for modulus in [2, 3, 5] {
                assert_matches_reference(&graph, 1.0, modulus);
            }
        }
    }
}

fn weighted_fixture() -> SparseDistanceMatrix {
    SparseDistanceMatrix::from_triplets(
        9,
        &[
            (0, 1, 0.0),
            (1, 2, 1.0),
            (2, 3, 1.0),
            (0, 3, 2.0),
            (0, 2, 1.0),
            (3, 4, 0.0),
            (4, 5, 1.0),
            (5, 6, 1.0),
            (3, 6, 2.0),
            (4, 6, 1.0),
            (6, 7, 0.0),
            (7, 8, 0.0),
        ],
    )
    .unwrap()
}

#[test]
fn forest_matches_at_weight_ties_and_zero_scales() {
    let graph = weighted_fixture();
    for scale in [-0.0, 0.0, 1.0, 2.0] {
        for modulus in [2, 3, 5] {
            assert_matches_reference(&graph, scale, modulus);
        }
    }
}

fn larger_fixture() -> SparseDistanceMatrix {
    let vertex_count = 36;
    let edges = (0..vertex_count)
        .flat_map(|u| ((u + 1)..vertex_count).map(move |v| (u, v)))
        .filter(|&(u, v)| {
            v == u + 1
                || (v == u + 5 && u % 2 == 0)
                || (v == u + 7 && u % 3 == 0)
                || (u * 11 + v * 7) % 29 == 0
        })
        .map(|(u, v)| (u, v, 1.0))
        .collect::<Vec<_>>();
    SparseDistanceMatrix::from_triplets(vertex_count, &edges).unwrap()
}

#[test]
fn forest_matches_on_larger_sparse_and_triangle_fixture() {
    let graph = larger_fixture();
    for modulus in [2, 3, 5] {
        assert_matches_reference(&graph, 1.0, modulus);
    }
}

#[test]
fn forest_rejects_malformed_incidence() {
    assert!(forest_cocycles(3, &[vec![0, 0]], &[], 5).is_err());
    assert!(forest_cocycles(3, &[vec![0, 1]], &[vec![0, 1, 2]], 5).is_err());
    assert!(
        forest_cocycles(
            3,
            &[vec![0, 1], vec![0, 2], vec![1, 2]],
            &[vec![0, 2, 1]],
            5
        )
        .is_err()
    );
}

#[test]
fn maximal_dimension_is_checked_before_simplex_loop() {
    let graph = SparseDistanceMatrix::from_triplets(1, &[]).unwrap();
    let limits = CohomologyLimits {
        max_dimension: usize::MAX,
        ..CohomologyLimits::default()
    };

    assert!(cohomology_space(&graph, usize::MAX, 0.0, 2, limits).is_err());
    assert!(cohomology_space(&graph, usize::MAX - 1, 0.0, 2, limits).is_err());
    assert!(cohomology_space(&graph, 0, 0.0, 2, limits).is_ok());
}