#![deny(unsafe_code, nonstandard_style)]
#![allow(missing_docs)]
mod common;
use common::{
Hyperedge,
Vertex,
};
use hypergraph::{
HyperedgeIndex,
Hypergraph,
VertexIndex,
};
fn build_graph<'a>() -> (
Hypergraph<Vertex<'a>, Hyperedge<'a>>,
VertexIndex,
VertexIndex,
VertexIndex,
VertexIndex,
HyperedgeIndex,
HyperedgeIndex,
HyperedgeIndex,
) {
let mut g = Hypergraph::<Vertex, Hyperedge>::new();
let a = g.add_vertex(Vertex::new("a")).unwrap();
let b = g.add_vertex(Vertex::new("b")).unwrap();
let c = g.add_vertex(Vertex::new("c")).unwrap();
let d = g.add_vertex(Vertex::new("d")).unwrap();
let ab = g
.add_hyperedge(vec![a, b], Hyperedge::new("a-b", 1))
.unwrap();
let bc = g
.add_hyperedge(vec![b, c], Hyperedge::new("b-c", 2))
.unwrap();
let ac = g
.add_hyperedge(vec![a, c], Hyperedge::new("a-c", 5))
.unwrap();
(g, a, b, c, d, ab, bc, ac)
}
#[test]
fn is_empty_on_new_graph() {
let g = Hypergraph::<Vertex, Hyperedge>::new();
assert!(g.is_empty());
}
#[test]
fn is_empty_after_adding_vertex() {
let mut g = Hypergraph::<Vertex, Hyperedge>::new();
g.add_vertex(Vertex::new("x")).unwrap();
assert!(!g.is_empty());
}
#[test]
fn is_empty_after_clear() {
let (mut g, ..) = build_graph();
g.clear();
assert!(g.is_empty());
}
#[test]
fn clone_produces_independent_copy() {
let (g, a, _, _, _, _, _, _) = build_graph();
let mut g2 = g.clone();
assert_eq!(g.count_vertices(), g2.count_vertices());
assert_eq!(g.count_hyperedges(), g2.count_hyperedges());
g2.remove_vertex(a).unwrap();
assert_eq!(g.count_vertices(), 4);
assert_eq!(g2.count_vertices(), 3);
}
#[test]
fn vertices_iter_yields_all() {
let (g, a, b, c, d, ..) = build_graph();
let mut collected: Vec<VertexIndex> = g.vertices_iter().map(|(idx, _)| idx).collect();
collected.sort();
assert_eq!(collected, vec![a, b, c, d]);
}
#[test]
fn hyperedges_iter_yields_all() {
let (g, _, _, _, _, ab, bc, ac) = build_graph();
let mut collected: Vec<HyperedgeIndex> = g.hyperedges_iter().map(|(idx, _)| idx).collect();
collected.sort();
assert_eq!(collected, vec![ab, bc, ac]);
}
#[test]
fn ref_into_iterator() {
let (g, ..) = build_graph();
let count = (&g).into_iter().count();
assert_eq!(count, g.count_hyperedges());
}
#[test]
fn bfs_from_source() {
let (g, a, b, c, _, ..) = build_graph();
let bfs = g.get_bfs(a).unwrap();
assert_eq!(bfs[0], a);
assert!(bfs.contains(&b));
assert!(bfs.contains(&c));
}
#[test]
fn bfs_isolated_vertex() {
let (g, _, _, _, d, ..) = build_graph();
assert_eq!(g.get_bfs(d).unwrap(), vec![d]);
}
#[test]
fn bfs_invalid_vertex() {
let (g, ..) = build_graph();
assert!(g.get_bfs(VertexIndex(999)).is_err());
}
#[test]
fn dfs_from_source() {
let (g, a, b, c, _, ..) = build_graph();
let dfs = g.get_dfs(a).unwrap();
assert_eq!(dfs[0], a);
assert!(dfs.contains(&b));
assert!(dfs.contains(&c));
}
#[test]
fn dfs_isolated_vertex() {
let (g, _, _, _, d, ..) = build_graph();
assert_eq!(g.get_dfs(d).unwrap(), vec![d]);
}
#[test]
fn dfs_invalid_vertex() {
let (g, ..) = build_graph();
assert!(g.get_dfs(VertexIndex(999)).is_err());
}
#[test]
fn is_reachable_self() {
let (g, a, ..) = build_graph();
assert_eq!(g.is_reachable(a, a).unwrap(), true);
}
#[test]
fn is_reachable_direct_edge() {
let (g, a, b, ..) = build_graph();
assert_eq!(g.is_reachable(a, b).unwrap(), true);
}
#[test]
fn is_reachable_transitive() {
let (g, a, _, c, ..) = build_graph();
assert_eq!(g.is_reachable(a, c).unwrap(), true);
}
#[test]
fn is_reachable_false() {
let (g, a, _, _, d, ..) = build_graph();
assert_eq!(g.is_reachable(d, a).unwrap(), false);
}
#[test]
fn is_reachable_invalid_vertex() {
let (g, a, ..) = build_graph();
assert!(g.is_reachable(a, VertexIndex(999)).is_err());
assert!(g.is_reachable(VertexIndex(999), a).is_err());
}
#[test]
fn topological_sort_dag() {
let (g, a, b, c, d, ..) = build_graph();
let order = g.topological_sort().unwrap();
let pos = |v: VertexIndex| order.iter().position(|&x| x == v).unwrap();
assert!(pos(a) < pos(b), "a must precede b");
assert!(pos(a) < pos(c), "a must precede c");
assert!(pos(b) < pos(c), "b must precede c");
assert!(order.contains(&d));
assert_eq!(order.len(), 4);
}
#[test]
fn topological_sort_cyclic() {
let mut g = Hypergraph::<Vertex, Hyperedge>::new();
let a = g.add_vertex(Vertex::new("a")).unwrap();
let b = g.add_vertex(Vertex::new("b")).unwrap();
g.add_hyperedge(vec![a, b], Hyperedge::new("a-b", 1))
.unwrap();
g.add_hyperedge(vec![b, a], Hyperedge::new("b-a", 1))
.unwrap();
assert!(g.topological_sort().is_err());
}
#[test]
fn dijkstra_with_cost_finds_shortest() {
let (g, a, b, c, _, ab, bc, _ac) = build_graph();
let (cost, path) = g.get_dijkstra_connections_with_cost(a, c).unwrap();
assert_eq!(cost, 3);
assert_eq!(path, vec![(a, None), (b, Some(ab)), (c, Some(bc))]);
}
#[test]
fn dijkstra_with_cost_matches_without_cost() {
let (g, a, _, c, ..) = build_graph();
let (_, path_with) = g.get_dijkstra_connections_with_cost(a, c).unwrap();
let path_without = g.get_dijkstra_connections(a, c).unwrap();
assert_eq!(path_with, path_without);
}
#[test]
fn dijkstra_with_cost_no_path() {
let (g, _, _, _, d, ..) = build_graph();
let a = VertexIndex(0);
let (cost, path) = g.get_dijkstra_connections_with_cost(d, a).unwrap();
assert_eq!(cost, 0);
assert!(path.is_empty());
}
#[test]
fn contains_vertex_present() {
let (g, ..) = build_graph();
assert!(g.contains_vertex(Vertex::new("a")));
assert!(g.contains_vertex(Vertex::new("b")));
}
#[test]
fn contains_vertex_absent() {
let (g, ..) = build_graph();
assert!(!g.contains_vertex(Vertex::new("z")));
}
#[test]
fn get_vertex_index_roundtrip() {
let (g, a, b, c, d, ..) = build_graph();
assert_eq!(g.get_vertex_index(Vertex::new("a")), vec![a]);
assert_eq!(g.get_vertex_index(Vertex::new("b")), vec![b]);
assert_eq!(g.get_vertex_index(Vertex::new("c")), vec![c]);
assert_eq!(g.get_vertex_index(Vertex::new("d")), vec![d]);
}
#[test]
fn get_vertex_index_absent() {
let (g, ..) = build_graph();
assert!(g.get_vertex_index(Vertex::new("z")).is_empty());
}
#[test]
fn get_vertex_index_after_remove() {
let (mut g, a, ..) = build_graph();
g.remove_vertex(a).unwrap();
assert!(g.get_vertex_index(Vertex::new("a")).is_empty());
}
#[test]
fn get_vertex_index_duplicate_weights() {
let mut g = Hypergraph::<Vertex, Hyperedge>::new();
let x1 = g.add_vertex(Vertex::new("x")).unwrap();
let x2 = g.add_vertex(Vertex::new("x")).unwrap();
assert_ne!(x1, x2);
let mut found = g.get_vertex_index(Vertex::new("x"));
found.sort();
assert_eq!(found, vec![x1, x2]);
}
#[test]
fn is_acyclic_dag() {
let (g, ..) = build_graph();
assert!(g.is_acyclic());
}
#[test]
fn is_acyclic_with_cycle() {
let mut g = Hypergraph::<Vertex, Hyperedge>::new();
let a = g.add_vertex(Vertex::new("a")).unwrap();
let b = g.add_vertex(Vertex::new("b")).unwrap();
g.add_hyperedge(vec![a, b], Hyperedge::new("a-b", 1))
.unwrap();
g.add_hyperedge(vec![b, a], Hyperedge::new("b-a", 1))
.unwrap();
assert!(!g.is_acyclic());
}
#[test]
fn is_acyclic_empty() {
let g = Hypergraph::<Vertex, Hyperedge>::new();
assert!(g.is_acyclic());
}
#[test]
fn display_contains_vertex_weights() {
let (g, ..) = build_graph();
let s = g.to_string();
assert!(s.contains("a"), "missing vertex a in: {s}");
assert!(s.contains("b"), "missing vertex b in: {s}");
assert!(s.contains("c"), "missing vertex c in: {s}");
assert!(s.contains("d"), "missing vertex d in: {s}");
}
#[test]
fn display_contains_hyperedge_weights() {
let (g, ..) = build_graph();
let s = g.to_string();
assert!(s.contains("a-b"), "missing hyperedge a-b in: {s}");
assert!(s.contains("b-c"), "missing hyperedge b-c in: {s}");
}
#[test]
fn display_empty_graph() {
let g = Hypergraph::<Vertex, Hyperedge>::new();
let s = g.to_string();
assert!(s.contains("vertices: []"), "unexpected: {s}");
assert!(s.contains("hyperedges: []"), "unexpected: {s}");
}
#[test]
fn connected_components_single() {
let (g, a, b, c, d, ..) = build_graph();
let mut components = g.connected_components().unwrap();
for comp in &mut components {
comp.sort();
}
components.sort_by_key(|c| c[0]);
assert_eq!(components.len(), 2, "expected 2 components");
assert!(components[0].contains(&a));
assert!(components[0].contains(&b));
assert!(components[0].contains(&c));
assert_eq!(components[1], vec![d]);
}
#[test]
fn connected_components_all_isolated() {
let mut g = Hypergraph::<Vertex, Hyperedge>::new();
let a = g.add_vertex(Vertex::new("a")).unwrap();
let b = g.add_vertex(Vertex::new("b")).unwrap();
let components = g.connected_components().unwrap();
assert_eq!(components.len(), 2);
assert!(components.iter().any(|c| c == &vec![a]));
assert!(components.iter().any(|c| c == &vec![b]));
}
#[test]
fn connected_components_empty_graph() {
let g = Hypergraph::<Vertex, Hyperedge>::new();
assert_eq!(
g.connected_components().unwrap(),
Vec::<Vec<VertexIndex>>::new()
);
}
#[test]
fn dijkstra_from_includes_source() {
let (g, a, ..) = build_graph();
let dists = g.get_dijkstra_from(a).unwrap();
assert_eq!(dists[&a], 0);
}
#[test]
fn dijkstra_from_correct_distances() {
let (g, a, b, c, ..) = build_graph();
let dists = g.get_dijkstra_from(a).unwrap();
assert_eq!(dists[&a], 0);
assert_eq!(dists[&b], 1);
assert_eq!(dists[&c], 3);
}
#[test]
fn dijkstra_from_excludes_unreachable() {
let (g, _, _, _, d, ..) = build_graph();
let dists = g.get_dijkstra_from(d).unwrap();
assert_eq!(dists.len(), 1);
assert_eq!(dists[&d], 0);
}
#[test]
fn dijkstra_from_consistent_with_point_to_point() {
let (g, a, _, c, ..) = build_graph();
let (cost, _) = g.get_dijkstra_connections_with_cost(a, c).unwrap();
let dists = g.get_dijkstra_from(a).unwrap();
assert_eq!(dists[&c], cost);
}
#[test]
fn dijkstra_from_invalid_vertex() {
let (g, ..) = build_graph();
assert!(g.get_dijkstra_from(VertexIndex(999)).is_err());
}
#[test]
fn find_hyperedges_by_weight_single_match() {
let (g, _, _, _, _, ab, _, _) = build_graph();
assert_eq!(
g.find_hyperedges_by_weight(Hyperedge::new("a-b", 1)),
vec![ab]
);
}
#[test]
fn find_hyperedges_by_weight_no_match() {
let (g, ..) = build_graph();
assert!(
g.find_hyperedges_by_weight(Hyperedge::new("zzz", 99))
.is_empty()
);
}
#[test]
fn find_hyperedges_by_weight_duplicate_weights() {
let mut g = Hypergraph::<Vertex, Hyperedge>::new();
let a = g.add_vertex(Vertex::new("a")).unwrap();
let b = g.add_vertex(Vertex::new("b")).unwrap();
let c = g.add_vertex(Vertex::new("c")).unwrap();
let shared = Hyperedge::new("shared", 7);
let he1 = g.add_hyperedge(vec![a, b], shared).unwrap();
let he2 = g.add_hyperedge(vec![b, c], shared).unwrap();
let mut found = g.find_hyperedges_by_weight(shared);
found.sort();
assert_eq!(found, vec![he1, he2]);
}
#[test]
fn scc_dag_all_singletons() {
let (g, a, b, c, d, ..) = build_graph();
let sccs = g.strongly_connected_components().unwrap();
assert_eq!(sccs.len(), 4);
for v in [a, b, c, d] {
assert!(
sccs.iter().any(|s| s == &vec![v]),
"expected singleton SCC for {:?}",
v
);
}
}
#[test]
fn scc_cycle_single_component() {
let mut g = Hypergraph::<Vertex, Hyperedge>::new();
let a = g.add_vertex(Vertex::new("a")).unwrap();
let b = g.add_vertex(Vertex::new("b")).unwrap();
let c = g.add_vertex(Vertex::new("c")).unwrap();
g.add_hyperedge(vec![a, b], Hyperedge::new("a-b", 1))
.unwrap();
g.add_hyperedge(vec![b, c], Hyperedge::new("b-c", 1))
.unwrap();
g.add_hyperedge(vec![c, a], Hyperedge::new("c-a", 1))
.unwrap();
let sccs = g.strongly_connected_components().unwrap();
assert_eq!(sccs.len(), 1);
let mut scc = sccs.into_iter().next().unwrap();
scc.sort();
assert_eq!(scc, vec![a, b, c]);
}
#[test]
fn scc_mixed_structure() {
let mut g = Hypergraph::<Vertex, Hyperedge>::new();
let a = g.add_vertex(Vertex::new("a")).unwrap();
let b = g.add_vertex(Vertex::new("b")).unwrap();
let c = g.add_vertex(Vertex::new("c")).unwrap();
g.add_hyperedge(vec![a, b], Hyperedge::new("a-b", 1))
.unwrap();
g.add_hyperedge(vec![b, a], Hyperedge::new("b-a", 1))
.unwrap();
g.add_hyperedge(vec![c, a], Hyperedge::new("c-a", 1))
.unwrap();
let sccs = g.strongly_connected_components().unwrap();
assert_eq!(sccs.len(), 2);
assert!(
sccs.iter().any(|s| s == &vec![c]),
"c should be a singleton SCC"
);
assert!(
sccs.iter().any(|s| {
let mut s = s.clone();
s.sort();
s == vec![a, b]
}),
"a and b should form one SCC"
);
}
#[test]
fn scc_empty_graph() {
let g = Hypergraph::<Vertex, Hyperedge>::new();
assert_eq!(
g.strongly_connected_components().unwrap(),
Vec::<Vec<VertexIndex>>::new()
);
}
#[test]
fn subgraph_preserves_internal_edges() {
let (g, a, b, _, _, ab, _, _) = build_graph();
let sub = g.subgraph(&[a, b]).unwrap();
assert_eq!(sub.count_vertices(), 2);
assert_eq!(sub.count_hyperedges(), 1);
let (he_idx, he_weight) = sub.hyperedges_iter().next().unwrap();
assert_eq!(*he_weight, Hyperedge::new("a-b", 1));
let _ = (he_idx, ab); }
#[test]
fn subgraph_drops_crossing_edges() {
let (g, _, b, _, d, ..) = build_graph();
let sub = g.subgraph(&[b, d]).unwrap();
assert_eq!(sub.count_vertices(), 2);
assert_eq!(sub.count_hyperedges(), 0);
}
#[test]
fn subgraph_empty_vertex_list() {
let (g, ..) = build_graph();
let sub = g.subgraph(&[]).unwrap();
assert!(sub.is_empty());
}
#[test]
fn subgraph_full_vertex_set() {
let (g, a, b, c, d, ..) = build_graph();
let sub = g.subgraph(&[a, b, c, d]).unwrap();
assert_eq!(sub.count_vertices(), g.count_vertices());
assert_eq!(sub.count_hyperedges(), g.count_hyperedges());
}
#[test]
fn subgraph_deduplicates_vertices() {
let (g, a, b, ..) = build_graph();
let sub = g.subgraph(&[a, b, a, b]).unwrap();
assert_eq!(sub.count_vertices(), 2);
}
#[test]
fn subgraph_invalid_vertex() {
let (g, ..) = build_graph();
assert!(g.subgraph(&[VertexIndex(999)]).is_err());
}
#[test]
fn retain_vertices_keep_all() {
let (mut g, ..) = build_graph();
g.retain_vertices(|_, _| true).unwrap();
assert_eq!(g.count_vertices(), 4);
assert_eq!(g.count_hyperedges(), 3);
}
#[test]
fn retain_vertices_remove_all() {
let (mut g, ..) = build_graph();
g.retain_vertices(|_, _| false).unwrap();
assert!(g.is_empty());
}
#[test]
fn retain_vertices_by_index() {
let (mut g, a, b, ..) = build_graph();
g.retain_vertices(|idx, _| idx.0 < 2).unwrap();
assert_eq!(g.count_vertices(), 2);
assert!(g.contains_vertex(Vertex::new("a")));
assert!(g.contains_vertex(Vertex::new("b")));
assert_eq!(g.count_hyperedges(), 3);
let ab_idx = g
.find_hyperedges_by_weight(Hyperedge::new("a-b", 1))
.into_iter()
.next()
.unwrap();
let ab_verts = g.get_hyperedge_vertices(ab_idx).unwrap();
assert!(ab_verts.contains(&a));
assert!(ab_verts.contains(&b));
}
#[test]
fn retain_vertices_strips_vertex_from_hyperedges() {
let (mut g, ..) = build_graph();
g.retain_vertices(|_, weight| *weight != Vertex::new("c"))
.unwrap();
assert_eq!(g.count_vertices(), 3);
assert_eq!(g.count_hyperedges(), 3);
}
#[test]
fn retain_hyperedges_keep_all() {
let (mut g, ..) = build_graph();
g.retain_hyperedges(|_, _| true).unwrap();
assert_eq!(g.count_hyperedges(), 3);
assert_eq!(g.count_vertices(), 4);
}
#[test]
fn retain_hyperedges_remove_all() {
let (mut g, ..) = build_graph();
g.retain_hyperedges(|_, _| false).unwrap();
assert_eq!(g.count_hyperedges(), 0);
assert_eq!(g.count_vertices(), 4); }
#[test]
fn retain_hyperedges_by_cost() {
let (mut g, _, _, _, _, ab, bc, _) = build_graph();
g.retain_hyperedges(|_, w| usize::from(*w) <= 2).unwrap();
assert_eq!(g.count_hyperedges(), 2);
let mut remaining: Vec<_> = g.hyperedges_iter().map(|(idx, _)| idx).collect();
remaining.sort();
assert!(remaining.contains(&ab));
assert!(remaining.contains(&bc));
}
#[test]
fn all_paths_single_route() {
let (g, _, b, c, ..) = build_graph();
let paths = g.get_all_paths(b, c).unwrap();
assert_eq!(paths.len(), 1);
assert_eq!(paths[0], vec![b, c]);
}
#[test]
fn all_paths_multiple_routes() {
let (g, a, b, c, ..) = build_graph();
let mut paths = g.get_all_paths(a, c).unwrap();
paths.sort();
assert_eq!(paths.len(), 2);
assert!(paths.contains(&vec![a, b, c]));
assert!(paths.contains(&vec![a, c]));
}
#[test]
fn all_paths_no_route() {
let (g, a, _, _, d, ..) = build_graph();
assert_eq!(
g.get_all_paths(d, a).unwrap(),
Vec::<Vec<VertexIndex>>::new()
);
}
#[test]
fn all_paths_from_equals_to() {
let (g, a, ..) = build_graph();
assert_eq!(g.get_all_paths(a, a).unwrap(), vec![vec![a]]);
}
#[test]
fn all_paths_no_cycle_infinite_loop() {
let mut g = Hypergraph::<Vertex, Hyperedge>::new();
let a = g.add_vertex(Vertex::new("a")).unwrap();
let b = g.add_vertex(Vertex::new("b")).unwrap();
let c = g.add_vertex(Vertex::new("c")).unwrap();
g.add_hyperedge(vec![a, b], Hyperedge::new("a-b", 1))
.unwrap();
g.add_hyperedge(vec![b, a], Hyperedge::new("b-a", 1))
.unwrap();
g.add_hyperedge(vec![b, c], Hyperedge::new("b-c", 1))
.unwrap();
let paths = g.get_all_paths(a, c).unwrap();
assert_eq!(paths, vec![vec![a, b, c]]);
}
#[test]
fn all_paths_invalid_vertex() {
let (g, a, ..) = build_graph();
assert!(g.get_all_paths(VertexIndex(999), a).is_err());
assert!(g.get_all_paths(a, VertexIndex(999)).is_err());
}