holos-tda 0.9.0

Vietoris-Rips persistence and checked degree-Rips modules
Documentation
use sha2::{Digest, Sha256};

use crate::{CohomologyLimits, KineticFiltration, SparseDistanceMatrix, cohomology_space};

use super::*;
use holos_tda_check::{ProofLimits, VerifiedSynthesisSource, verify_synthesis};

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

fn state(
    graph: &SparseDistanceMatrix,
    scenario: u64,
    step: u64,
    target_basis: usize,
) -> SynthesisState {
    let space = cohomology_space(graph, 1, 1.0, 3, CohomologyLimits::default()).unwrap();
    let target = space
        .subspace_from_coordinates(&[vec![(target_basis, 1)]])
        .unwrap();
    SynthesisState::from_subspace(scenario, step, graph, 1.0, &space, &target, 0).unwrap()
}

fn problem() -> (TopologicalSpecification, Vec<SynthesisAction>) {
    let graph = two_cycles();
    let specification = TopologicalSpecification::new(
        8,
        1,
        1.0,
        3,
        vec![state(&graph, 0, 0, 0), state(&graph, 1, 0, 1)],
    );
    let mut actions = vec![
        SynthesisAction::new(0, 2, 4, vec![0]),
        SynthesisAction::new(1, 3, 7, vec![0]),
        SynthesisAction::new(4, 6, 5, vec![1]),
        SynthesisAction::new(5, 7, 9, vec![1]),
    ];
    actions.sort();
    (specification, actions)
}

#[test]
fn temporal_subspace_plan_has_a_checked_optimality_tree() {
    let (specification, actions) = problem();
    let limits = SynthesisLimits::default();
    let artifact = SynthesisArtifact::build(specification, actions, 2, limits).unwrap();
    assert_eq!(artifact.status(), SynthesisStatus::Optimal);
    assert_eq!(artifact.upper_bound_cost(), Some(9));
    assert_eq!(artifact.lower_bound_cost(), Some(9));
    assert_eq!(artifact.before_ranks(), [1, 1]);
    assert_eq!(artifact.after_ranks(), [0, 0]);
    assert!(artifact.proof_nodes() > 0);
    assert!(artifact.proof_topology_checks() > 0);
    let bytes = artifact.encode(limits).unwrap();
    let decoded = SynthesisArtifact::decode(&bytes, limits).unwrap();
    assert_eq!(decoded, artifact);
    let checked = verify_synthesis(&bytes, ProofLimits::default()).unwrap();
    assert_eq!(
        checked.status,
        holos_tda_check::VerifiedSynthesisStatus::Optimal
    );
    assert_eq!(checked.total_cost, Some(9));
    assert_eq!(
        checked.proof_topology_checks,
        artifact.proof_topology_checks()
    );
    assert!(checked.proof_topology_checks < artifact.producer_oracle_calls());
}

#[test]
fn edit_bound_has_a_complete_infeasibility_proof() {
    let (specification, actions) = problem();
    let limits = SynthesisLimits::default();
    let artifact = SynthesisArtifact::build(specification, actions, 1, limits).unwrap();
    assert_eq!(artifact.status(), SynthesisStatus::Infeasible);
    assert!(artifact.upper_bound_cost().is_none());
    artifact.verify(limits).unwrap();
    verify_synthesis(&artifact.encode(limits).unwrap(), ProofLimits::default()).unwrap();
}

#[test]
fn bounded_search_keeps_only_a_checked_gap() {
    let (specification, actions) = problem();
    let limits = SynthesisLimits::default()
        .with_max_oracle_calls(4)
        .with_max_search_nodes(2);
    let artifact = SynthesisArtifact::build(specification, actions, 2, limits).unwrap();
    assert_eq!(artifact.status(), SynthesisStatus::SearchIncomplete);
    assert_eq!(artifact.proof_nodes(), 0);
    artifact.verify(limits).unwrap();
}

#[test]
fn mutations_and_truncations_are_rejected() {
    let (specification, actions) = problem();
    let limits = SynthesisLimits::default();
    let artifact = SynthesisArtifact::build(specification, actions, 2, limits).unwrap();
    let bytes = artifact.encode(limits).unwrap();
    for end in 0..bytes.len() {
        assert!(SynthesisArtifact::decode(&bytes[..end], limits).is_err());
        assert!(verify_synthesis(&bytes[..end], ProofLimits::default()).is_err());
    }
    let mut changed = bytes;
    changed[48] ^= 1;
    assert!(SynthesisArtifact::decode(&changed, limits).is_err());
}

#[test]
fn kinetic_compiler_covers_endpoints_events_and_open_cells() {
    let filtration = KineticFiltration::new(
        4,
        vec![
            crate::KineticEdge {
                u: 0,
                v: 1,
                intercept: 1.0,
                velocity: 0.0,
            },
            crate::KineticEdge {
                u: 1,
                v: 2,
                intercept: 1.0,
                velocity: 0.0,
            },
            crate::KineticEdge {
                u: 2,
                v: 3,
                intercept: 1.0,
                velocity: 0.0,
            },
            crate::KineticEdge {
                u: 0,
                v: 3,
                intercept: 1.0,
                velocity: 0.0,
            },
            crate::KineticEdge {
                u: 0,
                v: 2,
                intercept: 2.0,
                velocity: -1.0,
            },
        ],
        0.0,
        1.5,
        crate::KineticLimits::default(),
    )
    .unwrap();
    let critical = filtration.critical_graphs(1.0).unwrap();
    assert!(matches!(
        critical[0].kind,
        crate::KineticGraphStateKind::Start
    ));
    assert!(matches!(
        critical.last().unwrap().kind,
        crate::KineticGraphStateKind::End
    ));
    assert!(
        critical
            .iter()
            .any(|state| matches!(state.kind, crate::KineticGraphStateKind::Event(_)))
    );
    let specification = TopologicalSpecification::from_kinetic_rank_ceiling(
        &filtration,
        7,
        1,
        1.0,
        3,
        0,
        CohomologyLimits::default(),
    )
    .unwrap();
    assert!(!specification.states().is_empty());
    let action = SynthesisAction::throughout(1, 3, 2, &specification);
    let artifact =
        SynthesisArtifact::build(specification, vec![action], 1, SynthesisLimits::default())
            .unwrap();
    assert_eq!(artifact.status(), SynthesisStatus::Optimal);
    assert_eq!(artifact.upper_bound_cost(), Some(2));
    let limits = SynthesisLimits::default();
    let mut bytes = artifact.encode(limits).unwrap();
    let checked = verify_synthesis(&bytes, ProofLimits::default()).unwrap();
    assert_eq!(checked.source, VerifiedSynthesisSource::Affine);

    let velocity = (-1.0f64).to_bits().to_be_bytes();
    let position = bytes
        .windows(velocity.len())
        .position(|window| window == velocity)
        .unwrap();
    bytes[position..position + velocity.len()].copy_from_slice(&0.0f64.to_bits().to_be_bytes());
    let payload_end = bytes.len() - 32;
    let mut hash = Sha256::new();
    hash.update(b"holos-synthesis-artifact-v1");
    hash.update(&bytes[..payload_end]);
    let digest: [u8; 32] = hash.finalize().into();
    bytes[payload_end..].copy_from_slice(&digest);
    assert!(SynthesisArtifact::decode(&bytes, limits).is_err());
    assert!(verify_synthesis(&bytes, ProofLimits::default()).is_err());
}

#[test]
fn incidence_components_partition_states_and_actions() {
    let (specification, actions) = problem();
    let components = specification.components(&actions).unwrap();
    assert_eq!(components.len(), 2);
    assert_eq!(components[0].states(), [0]);
    assert_eq!(components[0].actions(), [0, 1]);
    assert_eq!(components[1].states(), [1]);
    assert_eq!(components[1].actions(), [2, 3]);

    let bridge = SynthesisAction::new(0, 6, 20, vec![0, 1]);
    let mut connected = actions;
    connected.push(bridge);
    connected.sort();
    assert_eq!(specification.components(&connected).unwrap().len(), 1);
}

#[test]
fn overlapping_obligations_require_a_recursive_optimality_proof() {
    let graph = SparseDistanceMatrix::from_triplets(
        4,
        &[(0, 1, 1.0), (1, 2, 1.0), (2, 3, 1.0), (0, 3, 1.0)],
    )
    .unwrap();
    let specification = TopologicalSpecification::new(
        4,
        1,
        1.0,
        3,
        (0..3).map(|step| state(&graph, 0, step, 0)).collect(),
    );
    let mut actions = vec![
        SynthesisAction::new(0, 2, 1, vec![0, 1]),
        SynthesisAction::new(0, 2, 1, vec![0, 2]),
        SynthesisAction::new(0, 2, 1, vec![1, 2]),
    ];
    actions.sort();
    let limits = SynthesisLimits::default();
    let artifact = SynthesisArtifact::build(specification, actions, 2, limits).unwrap();
    assert_eq!(artifact.status(), SynthesisStatus::Optimal);
    assert_eq!(artifact.upper_bound_cost(), Some(2));
    assert_eq!(artifact.lower_bound_cost(), Some(2));
    assert_eq!(artifact.selected().len(), 2);
    assert!(artifact.proof_nodes() > 1);
    assert!(artifact.proof_topology_checks() > 1);
    verify_synthesis(&artifact.encode(limits).unwrap(), ProofLimits::default()).unwrap();
}

#[test]
fn empty_specification_has_a_zero_cost_proof() {
    let specification = TopologicalSpecification::new(4, 1, 1.0, 3, Vec::new());
    let limits = SynthesisLimits::default();
    let artifact = SynthesisArtifact::build(specification, Vec::new(), 5, limits).unwrap();
    assert_eq!(artifact.status(), SynthesisStatus::Optimal);
    assert_eq!(artifact.upper_bound_cost(), Some(0));
    assert_eq!(artifact.lower_bound_cost(), Some(0));
    assert!(artifact.selected().is_empty());
    verify_synthesis(&artifact.encode(limits).unwrap(), ProofLimits::default()).unwrap();
}