holos-tda 0.9.0

Vietoris-Rips persistence and checked degree-Rips modules
Documentation
use super::*;

fn column(terms: &[(usize, u32)]) -> Vec<ZigzagTerm> {
    terms
        .iter()
        .map(|&(target, coefficient)| ZigzagTerm {
            target,
            coefficient,
        })
        .collect()
}

#[test]
fn identity_chain_is_one_complete_interval() {
    let module = ZigzagModule::new(
        3,
        vec![1, 1, 1],
        vec![
            ZigzagMap::new(ZigzagDirection::Forward, vec![column(&[(0, 1)])]),
            ZigzagMap::new(ZigzagDirection::Forward, vec![column(&[(0, 1)])]),
        ],
        ZigzagLimits::default(),
    )
    .unwrap();
    let barcode = module.decompose().unwrap();
    assert_eq!(barcode.rank(0, 2), Some(1));
    assert_eq!(barcode.intervals.len(), 1);
    assert_eq!(barcode.intervals[0].start, 0);
    assert_eq!(barcode.intervals[0].end, 2);
    assert_eq!(barcode.intervals[0].multiplicity, 1);
}

#[test]
fn zero_map_splits_two_point_intervals() {
    let module = ZigzagModule::new(
        5,
        vec![1, 1],
        vec![ZigzagMap::new(ZigzagDirection::Forward, vec![vec![]])],
        ZigzagLimits::default(),
    )
    .unwrap();
    let intervals = module.decompose().unwrap().intervals;
    assert_eq!(
        intervals
            .iter()
            .map(|interval| (interval.start, interval.end, interval.multiplicity))
            .collect::<Vec<_>>(),
        vec![(0, 0, 1), (1, 1, 1)]
    );
}

#[test]
fn fork_distinguishes_shared_and_independent_event_classes() {
    let shared = ZigzagModule::new(
        3,
        vec![1, 2, 1],
        vec![
            ZigzagMap::new(ZigzagDirection::Backward, vec![column(&[(0, 1)]), vec![]]),
            ZigzagMap::new(ZigzagDirection::Forward, vec![column(&[(0, 1)]), vec![]]),
        ],
        ZigzagLimits::default(),
    )
    .unwrap()
    .decompose()
    .unwrap();
    assert!(
        shared
            .intervals
            .iter()
            .any(|item| item.start == 0 && item.end == 2)
    );

    let independent = ZigzagModule::new(
        3,
        vec![1, 2, 1],
        vec![
            ZigzagMap::new(ZigzagDirection::Backward, vec![column(&[(0, 1)]), vec![]]),
            ZigzagMap::new(ZigzagDirection::Forward, vec![vec![], column(&[(0, 1)])]),
        ],
        ZigzagLimits::default(),
    )
    .unwrap()
    .decompose()
    .unwrap();
    assert_eq!(independent.rank(0, 2), Some(0));
    assert_eq!(
        independent
            .intervals
            .iter()
            .map(|item| (item.start, item.end, item.multiplicity))
            .collect::<Vec<_>>(),
        vec![(0, 1, 1), (1, 2, 1)]
    );
}

#[test]
fn every_orientation_recovers_direct_sum_multiplicities() {
    let expected = vec![(0, 2, 2), (0, 4, 1), (1, 3, 1), (2, 2, 1), (4, 4, 2)];
    let copies = expected
        .iter()
        .flat_map(|&(start, end, multiplicity)| std::iter::repeat_n((start, end), multiplicity))
        .collect::<Vec<_>>();
    let bases = (0..5)
        .map(|node| {
            copies
                .iter()
                .enumerate()
                .filter_map(|(copy, &(start, end))| (start <= node && node <= end).then_some(copy))
                .collect::<Vec<_>>()
        })
        .collect::<Vec<_>>();
    let dimensions = bases.iter().map(Vec::len).collect::<Vec<_>>();

    for modulus in [2, 3, 5] {
        for directions in 0..16 {
            let maps = (0..4)
                .map(|position| {
                    let direction = if directions & (1 << position) == 0 {
                        ZigzagDirection::Forward
                    } else {
                        ZigzagDirection::Backward
                    };
                    let (source, target) = match direction {
                        ZigzagDirection::Forward => (&bases[position], &bases[position + 1]),
                        ZigzagDirection::Backward => (&bases[position + 1], &bases[position]),
                    };
                    let columns = source
                        .iter()
                        .map(|copy| {
                            target
                                .iter()
                                .position(|candidate| candidate == copy)
                                .map_or_else(Vec::new, |target| column(&[(target, 1)]))
                        })
                        .collect();
                    ZigzagMap::new(direction, columns)
                })
                .collect();
            let actual =
                ZigzagModule::new(modulus, dimensions.clone(), maps, ZigzagLimits::default())
                    .unwrap()
                    .decompose()
                    .unwrap()
                    .intervals
                    .iter()
                    .map(|interval| (interval.start, interval.end, interval.multiplicity))
                    .collect::<Vec<_>>();
            assert_eq!(actual, expected, "modulus {modulus}, mask {directions}");
        }
    }
}