holos-tda 0.9.0

Vietoris-Rips persistence and checked degree-Rips modules
Documentation
use super::algebra::{Vector, nullspace, rank};
use super::{ZigzagDirection, ZigzagMap, ZigzagModule};
use crate::Result;

pub(super) fn generalized_rank(module: &ZigzagModule, start: usize, end: usize) -> Result<usize> {
    if start == end {
        return Ok(module.dimensions[start]);
    }
    let modulus = module.modulus as u64;
    let offsets = node_offsets(&module.dimensions, start, end);
    let ambient = offsets.last().copied().unwrap_or(0) + module.dimensions[end];
    let (mut relations, equations) = compatibility_system(module, start, end, &offsets);
    let relation_rank = rank(relations.clone(), modulus);
    let limit = nullspace(equations, ambient, modulus);
    relations.extend(first_node_images(limit, module.dimensions[start]));
    Ok(rank(relations, modulus) - relation_rank)
}

fn node_offsets(dimensions: &[usize], start: usize, end: usize) -> Vec<usize> {
    let mut offsets = vec![0usize; end - start + 1];
    for position in 1..offsets.len() {
        offsets[position] = offsets[position - 1] + dimensions[start + position - 1];
    }
    offsets
}

fn compatibility_system(
    module: &ZigzagModule,
    start: usize,
    end: usize,
    offsets: &[usize],
) -> (Vec<Vector>, Vec<Vector>) {
    let mut relations = Vec::new();
    let mut equations = Vec::new();
    for position in start..end {
        let map = &module.maps[position];
        let left_offset = offsets[position - start];
        let right_offset = offsets[position + 1 - start];
        let shape = oriented_offsets(module, position, left_offset, right_offset);
        relations.extend(map_relations(map, shape.0, shape.1, module.modulus));
        equations.extend(map_equations(
            map,
            shape.0,
            shape.1,
            shape.2,
            module.modulus,
        ));
    }
    (relations, equations)
}

fn oriented_offsets(
    module: &ZigzagModule,
    position: usize,
    left_offset: usize,
    right_offset: usize,
) -> (usize, usize, usize) {
    match module.maps[position].direction {
        ZigzagDirection::Forward => (left_offset, right_offset, module.dimensions[position + 1]),
        ZigzagDirection::Backward => (right_offset, left_offset, module.dimensions[position]),
    }
}

fn map_relations(
    map: &ZigzagMap,
    source_offset: usize,
    target_offset: usize,
    modulus: u32,
) -> Vec<Vector> {
    map.columns
        .iter()
        .enumerate()
        .map(|(column, terms)| {
            let mut relation = Vector::default();
            relation.insert(source_offset + column, 1);
            for term in terms {
                relation.insert(
                    target_offset + term.target,
                    (u64::from(modulus) - u64::from(term.coefficient)) as u32,
                );
            }
            relation
        })
        .collect()
}

fn map_equations(
    map: &ZigzagMap,
    source_offset: usize,
    target_offset: usize,
    target_dimension: usize,
    modulus: u32,
) -> Vec<Vector> {
    (0..target_dimension)
        .map(|target| map_equation(map, source_offset, target_offset, target, modulus))
        .collect()
}

fn map_equation(
    map: &ZigzagMap,
    source_offset: usize,
    target_offset: usize,
    target: usize,
    modulus: u32,
) -> Vector {
    let mut equation = Vector::default();
    equation.insert(target_offset + target, 1);
    for (column, terms) in map.columns.iter().enumerate() {
        if let Ok(term_position) = terms.binary_search_by_key(&target, |term| term.target) {
            let coefficient = terms[term_position].coefficient;
            equation.insert(
                source_offset + column,
                (u64::from(modulus) - u64::from(coefficient)) as u32,
            );
        }
    }
    equation
}

fn first_node_images(limit: Vec<Vector>, first_dimension: usize) -> impl Iterator<Item = Vector> {
    limit.into_iter().filter_map(move |section| {
        let mut image = Vector::default();
        for (&position, &coefficient) in section.0.range(..first_dimension) {
            image.insert(position, coefficient);
        }
        (!image.is_zero()).then_some(image)
    })
}