holos-tda 0.9.0

Vietoris-Rips persistence and checked degree-Rips modules
Documentation
use std::collections::{BTreeMap, BTreeSet};

use crate::certificate::{CertificateError, CertificateLimits};
use crate::field::{MODULUS_LIMIT, is_prime};
use crate::{RipsParams, SparseDistanceMatrix};

use super::model::InterfaceCell;

pub(super) fn check_interface_parameters(
    max_dim: usize,
    modulus: u32,
    limits: CertificateLimits,
) -> Result<(), CertificateError> {
    if max_dim > limits.max_dimension || u64::from(modulus) >= MODULUS_LIMIT {
        return Err(CertificateError::new(
            "relative interface exceeds the dimension or modulus limit",
        ));
    }
    if !is_prime(modulus as u64) {
        return Err(CertificateError::new(
            "relative interface modulus is not prime",
        ));
    }
    Ok(())
}
pub(super) fn check_protected_cells(
    input: &[Vec<InterfaceCell>],
    core: &[Vec<InterfaceCell>],
    protected: &BTreeSet<usize>,
) -> Result<(), CertificateError> {
    for (input_dimension, core_dimension) in input.iter().zip(core) {
        let core_map: BTreeMap<_, _> = core_dimension
            .iter()
            .map(|cell| (&cell.vertices, cell))
            .collect();
        for cell in input_dimension {
            if is_protected(&cell.vertices, protected)
                && core_map.get(&cell.vertices).copied() != Some(cell)
            {
                return Err(CertificateError::new(
                    "relative interface does not fix its protected subcomplex",
                ));
            }
        }
    }
    Ok(())
}

pub(super) fn is_protected(cell: &[usize], protected: &BTreeSet<usize>) -> bool {
    !protected.is_empty() && cell.iter().all(|vertex| protected.contains(vertex))
}

pub(super) fn canonical_vertices(vertices: &[usize]) -> Result<Vec<usize>, CertificateError> {
    let mut output = vertices.to_vec();
    output.sort_unstable();
    output.dedup();
    if output.len() != vertices.len() {
        return Err(CertificateError::new(
            "protected vertex list contains a duplicate",
        ));
    }
    Ok(output)
}

pub(super) fn validate_parameters(
    input: &SparseDistanceMatrix,
    labels: &[usize],
    params: &RipsParams,
    protected: &[usize],
    limits: CertificateLimits,
) -> Result<(), CertificateError> {
    if labels.len() != input.len()
        || labels.windows(2).any(|pair| pair[0] >= pair[1])
        || labels.len() > limits.max_vertices
    {
        return Err(CertificateError::new(
            "relative interface labels must be unique, ordered, and bounded",
        ));
    }
    let label_set: BTreeSet<_> = labels.iter().copied().collect();
    if protected.iter().any(|vertex| !label_set.contains(vertex)) {
        return Err(CertificateError::new(
            "protected vertex is outside the interface scope",
        ));
    }
    if params.max_dim > limits.max_dimension
        || u64::from(params.modulus) >= MODULUS_LIMIT
        || !is_prime(params.modulus as u64)
    {
        return Err(CertificateError::new(
            "relative interface dimension or coefficient field is invalid",
        ));
    }
    checked_threshold(params.threshold)?;
    Ok(())
}

fn checked_threshold(threshold: Option<f64>) -> Result<f64, CertificateError> {
    let value = threshold.unwrap_or(f64::INFINITY);
    if value.is_nan() || value < 0.0 {
        return Err(CertificateError::new(
            "relative interface threshold must be non-negative",
        ));
    }
    Ok(value)
}

pub(super) fn enforce_cell_limits(
    cells: &[Vec<InterfaceCell>],
    limits: CertificateLimits,
) -> Result<(), CertificateError> {
    for (dimension, values) in cells.iter().enumerate() {
        enforce_dimension_limit(dimension, values.len(), limits)?;
    }
    Ok(())
}

fn enforce_dimension_limit(
    dimension: usize,
    count: usize,
    limits: CertificateLimits,
) -> Result<(), CertificateError> {
    let limit = match dimension {
        0 => limits.max_vertices,
        1 => limits.max_edges,
        2 => limits.max_triangles,
        _ => limits.max_higher_simplices,
    };
    if count > limit {
        return Err(CertificateError::new(format!(
            "relative dimension {dimension} cell count exceeds the limit {limit}"
        )));
    }
    Ok(())
}

pub(super) fn count_cells(cells: &[Vec<InterfaceCell>]) -> usize {
    cells.iter().map(Vec::len).sum()
}

pub(super) fn cell_order(left: &InterfaceCell, right: &InterfaceCell) -> std::cmp::Ordering {
    left.value
        .total_cmp(&right.value)
        .then_with(|| right.vertices.iter().rev().cmp(left.vertices.iter().rev()))
}