weavatrix-graph 0.6.3

Deterministic, evidence-carrying graph core for Weavatrix repository intelligence
Documentation
use petgraph::algo::{
    dijkstra as pet_dijkstra, dinics, has_path_connecting, is_cyclic_directed, kosaraju_scc,
    toposort,
};
use petgraph::graph::DiGraph;
use weavatrix_graph::{
    EdgeEndpoints, NodeIndex, Topology, dijkstra, has_cycle, maximum_flow, reachable,
    shortest_path, strongly_connected_components, topological_sort,
};

#[test]
fn algorithms_match_petgraph_across_deterministic_random_graphs() {
    let mut seed = 0x9e37_79b9_u64;
    for node_count in [1_usize, 2, 7, 32, 97] {
        for density in [1_u64, 5, 17] {
            let mut pairs = Vec::new();
            for source in 0..node_count {
                for target in 0..node_count {
                    seed = seed.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
                    if seed % 101 < density {
                        pairs.push((source, target));
                    }
                }
            }
            compare_case(node_count, &pairs);
        }
    }
}

fn compare_case(node_count: usize, pairs: &[(usize, usize)]) {
    let ours = Topology::try_from_edges(
        node_count,
        pairs.iter().map(|&(source, target)| {
            EdgeEndpoints::new(
                NodeIndex::new(u32::try_from(source).unwrap()),
                NodeIndex::new(u32::try_from(target).unwrap()),
            )
        }),
    )
    .unwrap();
    let mut pet = DiGraph::<(), u64>::with_capacity(node_count, pairs.len());
    let pet_nodes = (0..node_count)
        .map(|_| pet.add_node(()))
        .collect::<Vec<_>>();
    for &(source, target) in pairs {
        let weight = u64::try_from((source * 31 + target * 17) % 23 + 1).unwrap();
        pet.add_edge(pet_nodes[source], pet_nodes[target], weight);
    }

    assert_eq!(has_cycle(&ours), is_cyclic_directed(&pet));
    assert_eq!(
        topological_sort(&ours).is_some(),
        toposort(&pet, None).is_ok()
    );
    assert_eq!(normalize_ours(&ours), normalize_pet(&pet));
    if node_count > 1 {
        let ours_flow = maximum_flow(
            &ours,
            NodeIndex::new(0),
            NodeIndex::new(u32::try_from(node_count - 1).unwrap()),
            |edge| {
                let (source, target) = pairs[edge.index()];
                u64::try_from((source * 31 + target * 17) % 23 + 1).unwrap()
            },
        )
        .unwrap()
        .unwrap()
        .value();
        let pet_flow = dinics(&pet, pet_nodes[0], pet_nodes[node_count - 1]).0;
        assert_eq!(ours_flow, pet_flow);
    }

    for source in 0..node_count {
        let expected_costs = pet_dijkstra(&pet, pet_nodes[source], None, |edge| *edge.weight());
        for target in 0..node_count {
            let ours_source = NodeIndex::new(u32::try_from(source).unwrap());
            let ours_target = NodeIndex::new(u32::try_from(target).unwrap());
            let expected = has_path_connecting(&pet, pet_nodes[source], pet_nodes[target], None);
            assert_eq!(reachable(&ours, ours_source, ours_target), expected);
            assert_eq!(
                shortest_path(&ours, ours_source, ours_target).is_some(),
                expected
            );
            let ours_cost = dijkstra(&ours, ours_source, ours_target, |edge| {
                let (edge_source, edge_target) = pairs[edge.index()];
                u64::try_from((edge_source * 31 + edge_target * 17) % 23 + 1).unwrap()
            })
            .map(|path| path.total_cost());
            assert_eq!(ours_cost, expected_costs.get(&pet_nodes[target]).copied());
        }
    }
}

fn normalize_ours(graph: &Topology) -> Vec<Vec<usize>> {
    let mut components = strongly_connected_components(graph)
        .into_iter()
        .map(|component| {
            component
                .into_iter()
                .map(NodeIndex::index)
                .collect::<Vec<_>>()
        })
        .collect::<Vec<_>>();
    normalize(&mut components);
    components
}

fn normalize_pet(graph: &DiGraph<(), u64>) -> Vec<Vec<usize>> {
    let mut components = kosaraju_scc(graph)
        .into_iter()
        .map(|component| {
            component
                .into_iter()
                .map(petgraph::graph::NodeIndex::index)
                .collect::<Vec<_>>()
        })
        .collect::<Vec<_>>();
    normalize(&mut components);
    components
}

fn normalize(components: &mut [Vec<usize>]) {
    for component in &mut *components {
        component.sort_unstable();
    }
    components.sort_unstable();
}