weavatrix-graph 0.6.1

Deterministic, evidence-carrying graph primitives for repository intelligence
Documentation
use weavatrix_graph::{
    DijkstraWorkspace, EdgeEndpoints, NodeIndex, Topology, bellman_ford_measure, dijkstra_iter,
    dijkstra_measure, dijkstra_measure_filtered,
};

fn topology() -> Topology {
    Topology::try_from_edges(
        4,
        [(0, 1), (0, 2), (2, 1), (1, 3), (2, 3)].map(|(source, target)| {
            EdgeEndpoints::new(NodeIndex::new(source), NodeIndex::new(target))
        }),
    )
    .unwrap()
}

#[test]
fn generic_bellman_ford_supports_signed_integer_and_float_measures() {
    let graph = Topology::try_from_edges(
        4,
        [(0, 1), (0, 2), (2, 1), (1, 3)].map(|(source, target)| {
            EdgeEndpoints::new(NodeIndex::new(source), NodeIndex::new(target))
        }),
    )
    .unwrap();
    let signed = bellman_ford_measure(&graph, NodeIndex::new(0), |edge| {
        [4_i32, 5, -3, 2][edge.index()]
    })
    .unwrap()
    .unwrap();
    assert_eq!(signed.distance_to(NodeIndex::new(3)), Some(4));

    let float = bellman_ford_measure(&graph, NodeIndex::new(0), |edge| {
        [4.0_f64, 5.0, -3.5, 2.0][edge.index()]
    })
    .unwrap()
    .unwrap();
    assert_eq!(float.distance_to(NodeIndex::new(3)), Some(3.5));
}

#[test]
fn generic_dijkstra_supports_integer_and_float_measures() {
    let graph = topology();
    let integer = dijkstra_measure(&graph, NodeIndex::new(0), NodeIndex::new(3), |edge| {
        [10_u32, 2, 1, 2, 20][edge.index()]
    })
    .unwrap()
    .unwrap();
    assert_eq!(integer.total_cost(), 5_u32);

    let float = dijkstra_measure(&graph, NodeIndex::new(0), NodeIndex::new(3), |edge| {
        [10.0_f64, 2.5, 1.0, 2.0, 20.0][edge.index()]
    })
    .unwrap()
    .unwrap();
    assert!((float.total_cost() - 5.5).abs() < f64::EPSILON);
}

#[test]
fn lazy_dijkstra_reuses_storage_and_exposes_predecessors() {
    let graph = topology();
    let mut workspace = DijkstraWorkspace::new();
    let settled = dijkstra_iter(&graph, NodeIndex::new(0), &mut workspace, |edge| {
        [10_u16, 2, 1, 2, 20][edge.index()]
    })
    .take(3)
    .collect::<Result<Vec<_>, _>>()
    .unwrap();
    assert_eq!(
        settled,
        [
            (NodeIndex::new(0), 0),
            (NodeIndex::new(2), 2),
            (NodeIndex::new(1), 3)
        ]
    );
    assert_eq!(
        workspace.predecessor_at(NodeIndex::new(1).index()),
        Some(NodeIndex::new(2))
    );
}

#[test]
fn generic_dijkstra_rejects_negative_non_finite_and_overflowing_weights() {
    let graph = topology();
    for result in [
        dijkstra_measure(&graph, NodeIndex::new(0), NodeIndex::new(3), |edge| {
            if edge.index() == 0 { -1_i32 } else { 1 }
        })
        .map(|_| ()),
        dijkstra_measure(&graph, NodeIndex::new(0), NodeIndex::new(3), |edge| {
            if edge.index() == 0 { f64::NAN } else { 1.0 }
        })
        .map(|_| ()),
    ] {
        assert!(result.is_err());
    }

    let overflow = Topology::try_from_edges(
        3,
        [(0, 1), (1, 2)].map(|(source, target)| {
            EdgeEndpoints::new(NodeIndex::new(source), NodeIndex::new(target))
        }),
    )
    .unwrap();
    assert!(
        dijkstra_measure(&overflow, NodeIndex::new(0), NodeIndex::new(2), |edge| [
            u8::MAX,
            1
        ][edge.index()])
        .is_err()
    );

    let filtered = dijkstra_measure_filtered(
        &graph,
        NodeIndex::new(0),
        NodeIndex::new(3),
        weavatrix_graph::Direction::Outgoing,
        |edge| (edge.index() != 2).then_some(1_u8),
    )
    .unwrap()
    .unwrap();
    assert_eq!(filtered.total_cost(), 2);
}