diff --git a/src/testing.rs b/src/testing.rs index 227af35..64c099c 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -1,7 +1,4 @@ //! Test fixture and test macros for graph topology and algorithm implementations. -pub(crate) mod bfs_testing; -pub(crate) mod dfs_testing; -pub(crate) mod dijkstra_testing; pub mod fixtures; pub(crate) mod graph_topology_testing; diff --git a/src/testing/bfs_testing.rs b/src/testing/bfs_testing.rs deleted file mode 100644 index 2d5bf23..0000000 --- a/src/testing/bfs_testing.rs +++ /dev/null @@ -1,261 +0,0 @@ -#[doc(hidden)] -#[macro_export] -macro_rules! bfs_tests { - ($T:ty) => { - #[test] - fn bfs_single_vertex() { - let (graph, v): ($T, _) = $crate::testing::fixtures::MakeTestGraph::single_vertex(); - let result = $crate::algorithms::bfs(&graph, v); - assert_eq!( - result.distances[v], - Some(0), - "unexpected distance of source vertex" - ); - assert_eq!( - result.predecessors[v], None, - "unexpected predecessor of source vertex" - ); - } - - #[test] - fn bfs_disconnected() { - let (graph, vertices): ($T, _) = - $crate::testing::fixtures::MakeTestGraph::disconnected(); - let result = $crate::algorithms::bfs(&graph, vertices[0]); - assert_eq!( - result.distances[vertices[0]], - Some(0), - "unexpected distance of source vertex" - ); - assert_eq!( - result.predecessors[vertices[0]], None, - "unexpected predecessor of source vertex" - ); - for &v in &vertices[1..3] { - assert_eq!( - result.distances[v], None, - "disconnected vertex {v:?} should have no distance" - ); - assert_eq!( - result.predecessors[v], None, - "disconnected vertex {v:?} should have no predecessor" - ); - } - } - - #[test] - fn bfs() { - let (graph, vertices, _, _): ($T, _, _, _) = - $crate::testing::fixtures::MakeTestGraph::standard(); - let result = $crate::algorithms::bfs(&graph, vertices[0]); - assert_bfs_distances(&result.distances, &vertices); - assert_bfs_predecessors(&result.predecessors, &vertices); - } - - #[test] - fn bfs_distances_single_vertex() { - let (graph, v): ($T, _) = $crate::testing::fixtures::MakeTestGraph::single_vertex(); - let distances = $crate::algorithms::bfs_distances(&graph, v); - assert_eq!( - distances[v], - Some(0), - "unexpected distance of source vertex" - ); - } - - #[test] - fn bfs_distances_disconnected() { - let (graph, vertices): ($T, _) = - $crate::testing::fixtures::MakeTestGraph::disconnected(); - let distances = $crate::algorithms::bfs_distances(&graph, vertices[0]); - assert_eq!( - distances[vertices[0]], - Some(0), - "unexpected distance of source vertex" - ); - for &v in &vertices[1..3] { - assert_eq!( - distances[v], None, - "disconnected vertex {v:?} should have no distance" - ); - } - } - - #[test] - fn bfs_distances() { - let (graph, vertices, _, _): ($T, _, _, _) = - $crate::testing::fixtures::MakeTestGraph::standard(); - let distances = $crate::algorithms::bfs_distances(&graph, vertices[0]); - assert_bfs_distances(&distances, &vertices); - } - - #[test] - fn bfs_find_source() { - let (graph, v): ($T, _) = $crate::testing::fixtures::MakeTestGraph::single_vertex(); - assert_eq!( - $crate::algorithms::bfs_find(&graph, v, v), - Some(0), - "source should be found at distance 0" - ); - } - - #[test] - fn bfs_find_disconnected() { - let (graph, vertices): ($T, _) = - $crate::testing::fixtures::MakeTestGraph::disconnected(); - assert_eq!( - $crate::algorithms::bfs_find(&graph, vertices[0], vertices[1]), - None, - "disconnected target should not be found" - ); - } - - #[test] - fn bfs_find() { - let (graph, vertices, _, _): ($T, _, _, _) = - $crate::testing::fixtures::MakeTestGraph::standard(); - let expected_distances = [ - Some(0), - Some(1), - Some(2), - Some(2), - Some(2), - Some(3), - Some(3), - Some(3), - Some(3), - Some(4), - ]; - for i in 0..10 { - assert_eq!( - $crate::algorithms::bfs_find(&graph, vertices[0], vertices[i]), - expected_distances[i], - "unexpected distance from {:?} to {:?}", - vertices[0], - vertices[i] - ); - } - } - - #[test] - fn bfs_find_where_source_matches() { - let (graph, v): ($T, _) = $crate::testing::fixtures::MakeTestGraph::single_vertex(); - assert_eq!( - $crate::algorithms::bfs_find_where(&graph, v, |u| u == v), - Some((v, 0)), - "source should match with distance 0" - ); - } - - #[test] - fn bfs_find_where_disconnected() { - let (graph, vertices): ($T, _) = - $crate::testing::fixtures::MakeTestGraph::disconnected(); - assert_eq!( - $crate::algorithms::bfs_find_where(&graph, vertices[0], |v| v == vertices[1]), - None, - "disconnected vertex {:?} should not be found", - vertices[1] - ); - } - - #[test] - fn bfs_find_where_no_match() { - let (graph, vertices, _, _): ($T, _, _, _) = - $crate::testing::fixtures::MakeTestGraph::standard(); - assert_eq!( - $crate::algorithms::bfs_find_where(&graph, vertices[0], |_| false), - None, - "no vertex should match an always-false predicate" - ); - } - - #[test] - fn bfs_find_where_nearest() { - let (graph, vertices, _, _): ($T, _, _, _) = - $crate::testing::fixtures::MakeTestGraph::standard(); - // vertices[5], vertices[6], vertices[7], vertices[8] are all at distance 3 from vertices[0]. - // vertices[9] is at distance 4. Predicate matches vertices[7], vertices[8], vertices[9]. - // BFS must return one of the distance-3 ones, not vertices[9]. - let result = $crate::algorithms::bfs_find_where(&graph, vertices[0], |v| { - v == vertices[7] || v == vertices[8] || v == vertices[9] - }); - assert!(result.is_some(), "expected a match"); - let (found, distance) = result.unwrap(); - assert_eq!( - distance, 3, - "unexpected distance to nearest matching vertex {found:?}", - ); - assert!( - found == vertices[7] || found == vertices[8], - "unexpected nearest match vertex {found:?}, should be {:?} or {:?}", - vertices[7], - vertices[8] - ); - } - - // TODO: Move out of macro. - fn assert_bfs_distances( - distances: &$crate::maps::ElementMap< - <$T as $crate::traits::GraphTopology>::Vertex, - Option, - >, - vertices: &[<$T as $crate::traits::GraphTopology>::Vertex], - ) { - let expected = [ - Some(0), - Some(1), - Some(2), - Some(2), - Some(2), - Some(3), - Some(3), - Some(3), - Some(3), - Some(4), - ]; - for i in 0..10 { - assert_eq!( - distances[vertices[i]], expected[i], - "unexpected BFS distance from {:?} to {:?}", - vertices[0], vertices[i] - ); - } - } - - // TODO: Move out of macro. - fn assert_bfs_predecessors( - predecessors: &$crate::maps::ElementMap< - <$T as $crate::traits::GraphTopology>::Vertex, - Option<<$T as $crate::traits::GraphTopology>::Vertex>, - >, - vertices: &[<$T as $crate::traits::GraphTopology>::Vertex], - ) { - assert_eq!( - predecessors[vertices[0]], None, - "source should have no predecessor" - ); - // Each non-source vertex's predecessor must be adjacent and at distance one less. - let expected_predecessors = [ - vec![None], - vec![Some(vertices[0])], - vec![Some(vertices[1])], - vec![Some(vertices[1])], - vec![Some(vertices[1])], - vec![Some(vertices[2])], - vec![Some(vertices[2]), Some(vertices[3])], - vec![Some(vertices[4])], - vec![Some(vertices[4])], - vec![Some(vertices[5]), Some(vertices[6]), Some(vertices[7])], - ]; - for i in 1..10 { - assert!( - expected_predecessors[i].contains(&predecessors[vertices[i]]), - "unexpected predecessor {:?} of {:?}", - predecessors[vertices[i]], - vertices[i] - ); - } - } - }; -} diff --git a/src/testing/dfs_testing.rs b/src/testing/dfs_testing.rs deleted file mode 100644 index e57ad56..0000000 --- a/src/testing/dfs_testing.rs +++ /dev/null @@ -1,308 +0,0 @@ -#[doc(hidden)] -#[macro_export] -macro_rules! dfs_tests { - ($T:ty) => { - #[test] - fn dfs_single_vertex() { - let (graph, v): ($T, _) = $crate::testing::fixtures::MakeTestGraph::single_vertex(); - let result = $crate::algorithms::dfs(&graph, v); - assert!(result.visited[v], "source vertex should be visited"); - assert_eq!( - result.predecessors[v], None, - "unexpected predecessor of source vertex" - ); - } - - #[test] - fn dfs_disconnected() { - let (graph, vertices): ($T, _) = $crate::testing::fixtures::MakeTestGraph::disconnected(); - let result = $crate::algorithms::dfs(&graph, vertices[0]); - assert!( - result.visited[vertices[0]], - "source vertex should be visited" - ); - assert_eq!( - result.predecessors[vertices[0]], None, - "unexpected predecessor of source vertex" - ); - for &v in &vertices[1..3] { - assert!( - !result.visited[v], - "disconnected vertex {v:?} should not be visited" - ); - assert_eq!( - result.predecessors[v], None, - "disconnected vertex {v:?} should have no predecessor" - ); - } - } - - #[test] - fn dfs() { - let (graph, vertices, _, _): ($T, _, _, _) = $crate::testing::fixtures::MakeTestGraph::standard(); - let result = $crate::algorithms::dfs(&graph, vertices[0]); - assert_dfs_visited(&result.visited, &vertices); - assert_dfs_predecessors(&graph, &result.visited, &result.predecessors, &vertices); - } - - #[test] - fn dfs_visited_single_vertex() { - let (graph, v): ($T, _) = $crate::testing::fixtures::MakeTestGraph::single_vertex(); - let visited = $crate::algorithms::dfs_visited(&graph, v); - assert!(visited[v], "source vertex should be visited"); - } - - #[test] - fn dfs_visited_disconnected() { - let (graph, vertices): ($T, _) = $crate::testing::fixtures::MakeTestGraph::disconnected(); - let visited = $crate::algorithms::dfs_visited(&graph, vertices[0]); - assert!(visited[vertices[0]], "source vertex should be visited"); - for &v in &vertices[1..3] { - assert!( - !visited[v], - "disconnected vertex {v:?} should not be visited" - ); - } - } - - #[test] - fn dfs_visited() { - let (graph, vertices, _, _): ($T, _, _, _) = $crate::testing::fixtures::MakeTestGraph::standard(); - let visited = $crate::algorithms::dfs_visited(&graph, vertices[0]); - assert_dfs_visited(&visited, &vertices); - } - - #[test] - fn dfs_find_source() { - let (graph, v): ($T, _) = $crate::testing::fixtures::MakeTestGraph::single_vertex(); - assert!( - $crate::algorithms::dfs_find(&graph, v, v), - "source should find itself" - ); - } - - #[test] - fn dfs_find_disconnected() { - let (graph, vertices): ($T, _) = $crate::testing::fixtures::MakeTestGraph::disconnected(); - assert!( - !$crate::algorithms::dfs_find(&graph, vertices[0], vertices[1]), - "disconnected target should not be found" - ); - } - - #[test] - fn dfs_find() { - let (graph, vertices, _, _): ($T, _, _, _) = $crate::testing::fixtures::MakeTestGraph::standard(); - for i in 0..10 { - assert!( - $crate::algorithms::dfs_find(&graph, vertices[0], vertices[i]), - "vertex {:?} should be reachable from {:?}", - vertices[i], - vertices[0] - ); - } - } - - #[test] - fn dfs_find_where_source_matches() { - let (graph, v): ($T, _) = $crate::testing::fixtures::MakeTestGraph::single_vertex(); - assert_eq!( - $crate::algorithms::dfs_find_where(&graph, v, |u| u == v), - Some(v), - "source should find itself" - ); - } - - #[test] - fn dfs_find_where_disconnected() { - let (graph, vertices): ($T, _) = $crate::testing::fixtures::MakeTestGraph::disconnected(); - assert_eq!( - $crate::algorithms::dfs_find_where(&graph, vertices[0], |v| v == vertices[1]), - None, - "disconnected vertex {:?} should not be found", - vertices[1] - ); - } - - #[test] - fn dfs_find_where_no_match() { - let (graph, vertices, _, _): ($T, _, _, _) = $crate::testing::fixtures::MakeTestGraph::standard(); - assert_eq!( - $crate::algorithms::dfs_find_where(&graph, vertices[0], |_| false), - None, - "no vertex should match an always-false predicate" - ); - } - - #[test] - fn dfs_find_where_adjacent() { - let (graph, vertices, _, _): ($T, _, _, _) = $crate::testing::fixtures::MakeTestGraph::standard(); - assert_eq!( - $crate::algorithms::dfs_find_where(&graph, vertices[0], |v| v == vertices[1]), - Some(vertices[1]), - "expected to find adjacent vertex" - ); - } - - #[test] - fn dfs_find_where() { - let (graph, vertices, _, _): ($T, _, _, _) = $crate::testing::fixtures::MakeTestGraph::standard(); - assert_eq!( - $crate::algorithms::dfs_find_where(&graph, vertices[0], |v| v == vertices[9]), - Some(vertices[9]), - "expected to find connected vertex" - ); - } - - // TODO: Move out of macro. - fn assert_dfs_visited( - visited: &$crate::maps::ElementMap<<$T as $crate::traits::GraphTopology>::Vertex, bool>, - vertices: &[<$T as $crate::traits::GraphTopology>::Vertex], - ) { - for i in 0..10 { - assert!( - visited[vertices[i]], - "vertex {:?} should be visited", - vertices[i] - ); - } - } - - // TODO: Move out of macro. - fn assert_dfs_predecessors( - graph: &$T, - visited: &$crate::maps::ElementMap<<$T as $crate::traits::GraphTopology>::Vertex, bool>, - predecessors: &$crate::maps::ElementMap< - <$T as $crate::traits::GraphTopology>::Vertex, - Option<<$T as $crate::traits::GraphTopology>::Vertex>, - >, - vertices: &[<$T as $crate::traits::GraphTopology>::Vertex], - ) { - use $crate::traits::GraphTopology; - assert_eq!( - predecessors[vertices[0]], None, - "source should have no predecessor" - ); - for i in 1..10 { - let v = vertices[i]; - let p = predecessors[v].expect(&format!("vertex {v:?} should have a predecessor")); - assert!( - visited[p], - "predecessor {p:?} of vertex {v:?} should be visited" - ); - assert!( - graph.are_adjacent(v, p), - "predecessor {p:?} of vertex {v:?} should be adjacent" - ); - } - } - - #[test] - fn dfs_find_path_source_equals_target() { - let (graph, v): ($T, _) = $crate::testing::fixtures::MakeTestGraph::single_vertex(); - assert_eq!( - $crate::algorithms::dfs_find_path(&graph, v, v), - Some(vec![]), - "path from source to itself should be empty" - ); - } - - #[test] - fn dfs_find_path_disconnected() { - let (graph, vertices): ($T, _) = $crate::testing::fixtures::MakeTestGraph::disconnected(); - assert_eq!( - $crate::algorithms::dfs_find_path(&graph, vertices[0], vertices[1]), - None, - "no path should exist to disconnected vertex" - ); - } - - #[test] - fn dfs_find_path_adjacent() { - let (graph, vertices, e): ($T, _, _) = $crate::testing::fixtures::MakeTestGraph::single_edge(); - let path = $crate::algorithms::dfs_find_path(&graph, vertices[0], vertices[1]) - .expect("path should exist between adjacent vertices"); - assert_eq!(path.len(), 1, "unexpected path length between adjacent vertices"); - assert_eq!(path[0], e, "path should use the connecting edge"); - } - - #[test] - fn dfs_find_path() { - let (graph, vertices, _, _): ($T, _, _, _) = $crate::testing::fixtures::MakeTestGraph::standard(); - let path = $crate::algorithms::dfs_find_path(&graph, vertices[0], vertices[9]) - .expect(&format!( - "path should exist between connected vertices {:?} and {:?}", - vertices[0], vertices[9] - )); - assert_valid_path(&graph, &path, vertices[0], vertices[9]); - } - - #[test] - fn dfs_find_path_where_source_matches() { - let (graph, v): ($T, _) = $crate::testing::fixtures::MakeTestGraph::single_vertex(); - assert_eq!( - $crate::algorithms::dfs_find_path_where(&graph, v, |u| u == v), - Some(vec![]), - "path from source to itself should be empty" - ); - } - - #[test] - fn dfs_find_path_where_disconnected() { - let (graph, vertices): ($T, _) = $crate::testing::fixtures::MakeTestGraph::disconnected(); - assert_eq!( - $crate::algorithms::dfs_find_path_where(&graph, vertices[0], |v| v == vertices[1]), - None, - "no path should exist to disconnected vertex" - ); - } - - #[test] - fn dfs_find_path_where_no_match() { - let (graph, vertices, _, _): ($T, _, _, _) = $crate::testing::fixtures::MakeTestGraph::standard(); - assert_eq!( - $crate::algorithms::dfs_find_path_where(&graph, vertices[0], |_| false), - None, - "no path should exist when predicate never matches" - ); - } - - #[test] - fn dfs_find_path_where() { - let (graph, vertices, _, _): ($T, _, _, _) = $crate::testing::fixtures::MakeTestGraph::standard(); - let path = - $crate::algorithms::dfs_find_path_where(&graph, vertices[0], |v| v == vertices[9]) - .expect(&format!( - "path should exist between connected vertices {:?} and {:?}", - vertices[0], vertices[9] - )); - assert_valid_path(&graph, &path, vertices[0], vertices[9]); - } - - // TODO: Move out of macro. - fn assert_valid_path( - graph: &$T, - path: &[<$T as $crate::traits::GraphTopology>::Edge], - source: <$T as $crate::traits::GraphTopology>::Vertex, - target: <$T as $crate::traits::GraphTopology>::Vertex, - ) { - use $crate::traits::GraphTopology; - assert!(!path.is_empty(), "path should be non-empty"); - // Walks the path: tracks current vertex, confirm each edge is incident to it. - let mut current = source; - for (i, &e) in path.iter().enumerate() { - let (v1, v2) = graph.incident_vertices(e); - assert_ne!(v1, v2, "path should not contain loop edge {e:?}"); - assert!( - v1 == current || v2 == current, - "path edge {e:?} (index {i}, from {v1:?} to {v2:?}) is not incident to current path vertex {current:?}" - ); - current = if v1 == current { v2 } else { v1 }; - } - assert_eq!( - current, target, - "path should end at target {target:?}, but ended at {current:?}" - ); - } - }; -} diff --git a/src/testing/dijkstra_testing.rs b/src/testing/dijkstra_testing.rs deleted file mode 100644 index fc3d333..0000000 --- a/src/testing/dijkstra_testing.rs +++ /dev/null @@ -1,331 +0,0 @@ -#[doc(hidden)] -#[macro_export] -macro_rules! dijkstra_tests { - ($T:ty) => { - #[test] - fn dijkstra_single_vertex() { - let (graph, v): ($T, _) = $crate::testing::fixtures::MakeTestGraph::single_vertex(); - let result = $crate::algorithms::dijkstra(&graph, v, |_| { - panic!("unexpected call of weight functor") - }); - assert_single_vertex(&result, v); - } - - #[test] - fn dijkstra_disconnected() { - let (graph, vertices): ($T, _) = - $crate::testing::fixtures::MakeTestGraph::disconnected(); - let result = $crate::algorithms::dijkstra(&graph, vertices[0], |_| { - panic!("unexpected call of weight functor") - }); - assert_single_vertex(&result, vertices[0]); - assert_disconnected(&result, &vertices[1..3]); - } - - #[test] - fn dijkstra_zero_weight_loop() { - let (graph, vertices, _, weights) = make_two_edge_path_with_zero_weight_loops(); - let result = $crate::algorithms::dijkstra(&graph, vertices[0], |e| weights[e]); - assert_single_vertex(&result, vertices[0]); - for i in 1..3 { - assert_eq!( - result.predecessors[vertices[i]], - Some(vertices[i - 1]), - "unexpected predecessor of vertex {:?}", - vertices[i] - ); - assert_eq!( - result.distances[vertices[i]], - Some(i.try_into().unwrap()), - "unexpected distance of vertex {:?}", - vertices[i] - ); - } - } - - #[test] - fn dijkstra() { - let (graph, vertices, edges, _, weights) = make_test_graph_weighted(); - let result = $crate::algorithms::dijkstra(&graph, vertices[0], |e| weights[e]); - assert_test_graph(&result, &vertices); - } - - #[test] - fn dijkstra_distances() { - let (graph, vertices, edges, _, weights) = make_test_graph_weighted(); - let distances = - $crate::algorithms::dijkstra_distances(&graph, vertices[0], |e| weights[e]); - assert_distances_test_graph(&distances, &vertices); - } - - #[test] - fn dijkstra_unweighted_single_vertex() { - let (graph, v): ($T, _) = $crate::testing::fixtures::MakeTestGraph::single_vertex(); - let result = $crate::algorithms::dijkstra_unweighted(&graph, v); - assert_single_vertex(&result, v) - } - - #[test] - fn dijkstra_unweighted_disconnected() { - let (graph, vertices): ($T, _) = - $crate::testing::fixtures::MakeTestGraph::disconnected(); - let result = $crate::algorithms::dijkstra_unweighted(&graph, vertices[0]); - assert_single_vertex(&result, vertices[0]); - assert_disconnected(&result, &vertices[1..3]); - } - - #[test] - fn dijkstra_unweighted() { - let (graph, vertices, _, _): ($T, _, _, _) = - $crate::testing::fixtures::MakeTestGraph::standard(); - let result = $crate::algorithms::dijkstra_unweighted(&graph, vertices[0]); - assert_unweighted_test_graph(&result, &vertices); - } - - #[test] - fn dijkstra_distances_unweighted_single_vertex() { - let (graph, v): ($T, _) = $crate::testing::fixtures::MakeTestGraph::single_vertex(); - let distances = $crate::algorithms::dijkstra_distances_unweighted(&graph, v); - assert_distances_single_vertex(&distances, v); - } - - #[test] - fn dijkstra_distances_unweighted_disconnected() { - let (graph, vertices): ($T, _) = - $crate::testing::fixtures::MakeTestGraph::disconnected(); - let distances = $crate::algorithms::dijkstra_distances_unweighted(&graph, vertices[0]); - assert_distances_single_vertex(&distances, vertices[0]); - assert_distances_disconnected(&distances, &vertices[1..3]); - } - - #[test] - fn dijkstra_distances_unweighted() { - let (graph, vertices, _, _): ($T, _, _, _) = - $crate::testing::fixtures::MakeTestGraph::standard(); - let distances = $crate::algorithms::dijkstra_distances_unweighted(&graph, vertices[0]); - assert_distances_unweighted_test_graph(&distances, &vertices); - } - - // TODO: Move out of macro. - fn assert_single_vertex( - result: &$crate::algorithms::DijkstraResult< - <$T as $crate::traits::GraphTopology>::Vertex, - >, - v: <$T as $crate::traits::GraphTopology>::Vertex, - ) { - assert_distances_single_vertex(&result.distances, v); - assert_eq!( - result.predecessors[v], None, - "unexpected predecessor of source vertex", - ); - } - - // TODO: Move out of macro. - fn assert_distances_single_vertex( - distances: &$crate::maps::ElementMap< - <$T as $crate::traits::GraphTopology>::Vertex, - Option, - >, - v: <$T as $crate::traits::GraphTopology>::Vertex, - ) { - assert_eq!( - distances[v], - Some(0), - "unexpected distance of source vertex" - ); - } - - // TODO: Move out of macro. - fn assert_disconnected( - result: &$crate::algorithms::DijkstraResult< - <$T as $crate::traits::GraphTopology>::Vertex, - >, - vertices: &[<$T as $crate::traits::GraphTopology>::Vertex], - ) { - assert_distances_disconnected(&result.distances, &vertices); - for &v in vertices { - assert_eq!( - result.predecessors[v], None, - "unexpected predecessor of disconnected vertex {v:?}", - ); - } - } - - // TODO: Move out of macro. - fn assert_distances_disconnected( - distances: &$crate::maps::ElementMap< - <$T as $crate::traits::GraphTopology>::Vertex, - Option, - >, - vertices: &[<$T as $crate::traits::GraphTopology>::Vertex], - ) { - for &v in vertices { - assert_eq!( - distances[v], None, - "unexpected distance of disconnected vertex {v:?}", - ); - } - } - - // TODO: Move out of macro. - fn assert_test_graph( - result: &$crate::algorithms::DijkstraResult< - <$T as $crate::traits::GraphTopology>::Vertex, - >, - vertices: &[<$T as $crate::traits::GraphTopology>::Vertex], - ) { - assert_distances_test_graph(&result.distances, &vertices); - let expected_predecessors_from_v0 = [ - vec![None], - vec![Some(vertices[0])], - vec![Some(vertices[4])], - vec![Some(vertices[1])], - vec![Some(vertices[7])], - vec![Some(vertices[9])], - vec![Some(vertices[3])], - vec![Some(vertices[9])], - vec![Some(vertices[7])], - vec![Some(vertices[6])], - ]; - for i in 0..10 { - assert!( - expected_predecessors_from_v0[i].contains(&result.predecessors[vertices[i]]), - "unexpected predecessor {:?} of {:?}", - result.predecessors[vertices[i]], - vertices[i] - ); - } - } - - // TODO: Move out of macro. - fn assert_distances_test_graph( - distances: &$crate::maps::ElementMap< - <$T as $crate::traits::GraphTopology>::Vertex, - Option, - >, - vertices: &[<$T as $crate::traits::GraphTopology>::Vertex], - ) { - let expected_distances_from_v0 = [ - Some(0), - Some(1), - Some(65), - Some(4), - Some(58), - Some(43), - Some(14), - Some(46), - Some(145), - Some(29), - ]; - for i in 0..10 { - assert_eq!( - distances[vertices[i]], expected_distances_from_v0[i], - "unexpected distance from {:?} to {:?}", - vertices[0], vertices[i] - ); - } - } - - // TODO: Move out of macro. - fn assert_unweighted_test_graph( - result: &$crate::algorithms::DijkstraResult< - <$T as $crate::traits::GraphTopology>::Vertex, - >, - vertices: &[<$T as $crate::traits::GraphTopology>::Vertex], - ) { - assert_distances_unweighted_test_graph(&result.distances, &vertices); - let expected_predecessors_from_v0 = [ - vec![None], - vec![Some(vertices[0])], - vec![Some(vertices[1])], - vec![Some(vertices[1])], - vec![Some(vertices[1])], - vec![Some(vertices[2])], - vec![Some(vertices[2]), Some(vertices[3])], - vec![Some(vertices[4])], - vec![Some(vertices[4])], - vec![Some(vertices[5]), Some(vertices[6]), Some(vertices[7])], - ]; - for i in 0..10 { - assert!( - expected_predecessors_from_v0[i].contains(&result.predecessors[vertices[i]]), - "unexpected predecessor {:?} of {:?}", - result.predecessors[vertices[i]], - vertices[i] - ); - } - } - - // TODO: Move out of macro. - fn assert_distances_unweighted_test_graph( - distances: &$crate::maps::ElementMap< - <$T as $crate::traits::GraphTopology>::Vertex, - Option, - >, - vertices: &[<$T as $crate::traits::GraphTopology>::Vertex], - ) { - let expected_distances_from_v0 = [ - Some(0), - Some(1), - Some(2), - Some(2), - Some(2), - Some(3), - Some(3), - Some(3), - Some(3), - Some(4), - ]; - for i in 0..10 { - assert_eq!( - distances[vertices[i]], expected_distances_from_v0[i], - "unexpected distance from {:?} to {:?}", - vertices[0], vertices[i] - ); - } - } - - // TODO: Move out of macro. - fn make_test_graph_weighted() -> ( - $T, - [<$T as $crate::traits::GraphTopology>::Vertex; 10], - [( - <$T as $crate::traits::GraphTopology>::Edge, - <$T as $crate::traits::GraphTopology>::Vertex, - <$T as $crate::traits::GraphTopology>::Vertex, - ); 18], - [Vec< - $crate::traits::Incidence< - <$T as $crate::traits::GraphTopology>::Vertex, - <$T as $crate::traits::GraphTopology>::Edge, - >, - >; 10], - $crate::maps::ElementMap<<$T as $crate::traits::GraphTopology>::Edge, u32>, - ) { - use $crate::traits::GraphTopology; - let (graph, vertices, edges, incidences): ($T, _, _, _) = - $crate::testing::fixtures::MakeTestGraph::standard(); - let mut weights = graph.edge_map(99); - for i in [1, 3, 7, 10, 12, 14, 15, 17] { - weights[edges[i].0] = i.try_into().unwrap(); - } - (graph, vertices, edges, incidences, weights) - } - - // TODO: Move out of macro. - fn make_two_edge_path_with_zero_weight_loops() -> ( - $T, - [<$T as $crate::traits::GraphTopology>::Vertex; 3], - [<$T as $crate::traits::GraphTopology>::Edge; 4], - $crate::maps::ElementMap<<$T as $crate::traits::GraphTopology>::Edge, u32>, - ) { - use $crate::traits::GraphTopology; - let (graph, vertices, edges): ($T, _, _) = - $crate::testing::fixtures::MakeTestGraph::two_edge_path_with_loops(); - let mut weights = graph.edge_map(1); - weights[edges[2]] = 0; - weights[edges[3]] = 0; - (graph, vertices, edges, weights) - } - }; -} diff --git a/tests/bfs.rs b/tests/bfs.rs index babd76a..913234d 100644 --- a/tests/bfs.rs +++ b/tests/bfs.rs @@ -1,11 +1,307 @@ -mod append_graph_tests { +use std::fmt::Debug; + +use grapherity::algorithms; +use grapherity::maps::ElementMap; +use grapherity::testing::fixtures::MakeTestGraph; +use grapherity::traits::GraphTopology; + +macro_rules! bfs_tests { + ($T:ty) => { + bfs_tests!(@wrap $T, + bfs_single_vertex, + bfs_disconnected, + bfs, + bfs_distances_single_vertex, + bfs_distances_disconnected, + bfs_distances, + bfs_find_source, + bfs_find_disconnected, + bfs_find, + bfs_find_where_source_matches, + bfs_find_where_disconnected, + bfs_find_where_no_match, + bfs_find_where_nearest, + ); + }; + + (@wrap $T:ty, $($name:ident),* $(,)?) => { + $( + #[test] + fn $name() { + super::$name::<$T>(); + } + )* + }; +} + +mod append_graph { use grapherity::models::AppendGraph; - grapherity::bfs_tests!(AppendGraph); + bfs_tests!(AppendGraph); } -mod graph_tests { +mod graph { use grapherity::models::Graph; - grapherity::bfs_tests!(Graph); + bfs_tests!(Graph); +} + +fn bfs_single_vertex() +where + G::Vertex: Debug, +{ + let (graph, v) = G::single_vertex(); + let result = algorithms::bfs(&graph, v); + assert_eq!( + result.distances[v], + Some(0), + "unexpected distance of source vertex" + ); + assert_eq!( + result.predecessors[v], None, + "unexpected predecessor of source vertex" + ); +} + +fn bfs_disconnected() +where + G::Vertex: Debug, +{ + let (graph, vertices) = G::disconnected(); + let result = algorithms::bfs(&graph, vertices[0]); + assert_eq!( + result.distances[vertices[0]], + Some(0), + "unexpected distance of source vertex" + ); + assert_eq!( + result.predecessors[vertices[0]], None, + "unexpected predecessor of source vertex" + ); + for &v in &vertices[1..3] { + assert_eq!( + result.distances[v], None, + "disconnected vertex {v:?} should have no distance" + ); + assert_eq!( + result.predecessors[v], None, + "disconnected vertex {v:?} should have no predecessor" + ); + } +} + +fn bfs() +where + G::Vertex: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + let result = algorithms::bfs(&graph, vertices[0]); + assert_bfs_distances::(&result.distances, &vertices); + assert_bfs_predecessors::(&result.predecessors, &vertices); +} + +fn bfs_distances_single_vertex() { + let (graph, v) = G::single_vertex(); + let distances = algorithms::bfs_distances(&graph, v); + assert_eq!( + distances[v], + Some(0), + "unexpected distance of source vertex" + ); +} + +fn bfs_distances_disconnected() +where + G::Vertex: Debug, +{ + let (graph, vertices) = G::disconnected(); + let distances = algorithms::bfs_distances(&graph, vertices[0]); + assert_eq!( + distances[vertices[0]], + Some(0), + "unexpected distance of source vertex" + ); + for &v in &vertices[1..3] { + assert_eq!( + distances[v], None, + "disconnected vertex {v:?} should have no distance" + ); + } +} + +fn bfs_distances() +where + G::Vertex: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + let distances = algorithms::bfs_distances(&graph, vertices[0]); + assert_bfs_distances::(&distances, &vertices); +} + +fn bfs_find_source() { + let (graph, v) = G::single_vertex(); + assert_eq!( + algorithms::bfs_find(&graph, v, v), + Some(0), + "source should be found at distance 0" + ); +} + +fn bfs_find_disconnected() { + let (graph, vertices) = G::disconnected(); + assert_eq!( + algorithms::bfs_find(&graph, vertices[0], vertices[1]), + None, + "disconnected target should not be found" + ); +} + +fn bfs_find() +where + G::Vertex: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + let expected_distances = [ + Some(0), + Some(1), + Some(2), + Some(2), + Some(2), + Some(3), + Some(3), + Some(3), + Some(3), + Some(4), + ]; + for i in 0..10 { + assert_eq!( + algorithms::bfs_find(&graph, vertices[0], vertices[i]), + expected_distances[i], + "unexpected distance from {:?} to {:?}", + vertices[0], + vertices[i] + ); + } +} + +fn bfs_find_where_source_matches() +where + G::Vertex: Debug, +{ + let (graph, v) = G::single_vertex(); + assert_eq!( + algorithms::bfs_find_where(&graph, v, |u| u == v), + Some((v, 0)), + "source should match with distance 0" + ); +} + +fn bfs_find_where_disconnected() +where + G::Vertex: Debug, +{ + let (graph, vertices) = G::disconnected(); + assert_eq!( + algorithms::bfs_find_where(&graph, vertices[0], |v| v == vertices[1]), + None, + "disconnected vertex {:?} should not be found", + vertices[1] + ); +} + +fn bfs_find_where_no_match() +where + G::Vertex: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + assert_eq!( + algorithms::bfs_find_where(&graph, vertices[0], |_| false), + None, + "no vertex should match an always-false predicate" + ); +} + +fn bfs_find_where_nearest() +where + G::Vertex: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + // vertices[5], vertices[6], vertices[7], vertices[8] are all at distance 3 from vertices[0]. + // vertices[9] is at distance 4. Predicate matches vertices[7], vertices[8], vertices[9]. + // BFS must return one of the distance-3 ones, not vertices[9]. + let result = algorithms::bfs_find_where(&graph, vertices[0], |v| { + v == vertices[7] || v == vertices[8] || v == vertices[9] + }); + assert!(result.is_some(), "expected a match"); + let (found, distance) = result.unwrap(); + assert_eq!( + distance, 3, + "unexpected distance to nearest matching vertex {found:?}", + ); + assert!( + found == vertices[7] || found == vertices[8], + "unexpected nearest match vertex {found:?}, should be {:?} or {:?}", + vertices[7], + vertices[8] + ); +} + +fn assert_bfs_distances( + distances: &ElementMap>, + vertices: &[G::Vertex], +) where + G::Vertex: Debug, +{ + let expected = [ + Some(0), + Some(1), + Some(2), + Some(2), + Some(2), + Some(3), + Some(3), + Some(3), + Some(3), + Some(4), + ]; + for i in 0..10 { + assert_eq!( + distances[vertices[i]], expected[i], + "unexpected distance from {:?} to {:?}", + vertices[0], vertices[i] + ); + } +} + +fn assert_bfs_predecessors( + predecessors: &ElementMap>, + vertices: &[G::Vertex], +) where + G::Vertex: Debug, +{ + assert_eq!( + predecessors[vertices[0]], None, + "source should have no predecessor" + ); + // Each non-source vertex's predecessor must be adjacent and at distance one less. + let expected_predecessors = [ + vec![None], + vec![Some(vertices[0])], + vec![Some(vertices[1])], + vec![Some(vertices[1])], + vec![Some(vertices[1])], + vec![Some(vertices[2])], + vec![Some(vertices[2]), Some(vertices[3])], + vec![Some(vertices[4])], + vec![Some(vertices[4])], + vec![Some(vertices[5]), Some(vertices[6]), Some(vertices[7])], + ]; + for i in 1..10 { + assert!( + expected_predecessors[i].contains(&predecessors[vertices[i]]), + "unexpected predecessor {:?} of {:?}", + predecessors[vertices[i]], + vertices[i] + ); + } } diff --git a/tests/dfs.rs b/tests/dfs.rs index fcbbc3b..4999cc9 100644 --- a/tests/dfs.rs +++ b/tests/dfs.rs @@ -1,11 +1,398 @@ -mod append_graph_tests { +use std::fmt::Debug; + +use grapherity::algorithms; +use grapherity::maps::ElementMap; +use grapherity::testing::fixtures::MakeTestGraph; +use grapherity::traits::GraphTopology; + +macro_rules! dfs_tests { + ($T:ty) => { + dfs_tests!(@wrap $T, + dfs_single_vertex, + dfs_disconnected, + dfs, + dfs_visited_single_vertex, + dfs_visited_disconnected, + dfs_visited, + dfs_find_source, + dfs_find_disconnected, + dfs_find, + dfs_find_where_source_matches, + dfs_find_where_disconnected, + dfs_find_where_no_match, + dfs_find_where_adjacent, + dfs_find_where, + dfs_find_path_source_equals_target, + dfs_find_path_disconnected, + dfs_find_path_adjacent, + dfs_find_path, + dfs_find_path_where_source_matches, + dfs_find_path_where_disconnected, + dfs_find_path_where_no_match, + dfs_find_path_where, + ); + }; + + (@wrap $T:ty, $($name:ident),* $(,)?) => { + $( + #[test] + fn $name() { + super::$name::<$T>(); + } + )* + }; +} + +mod append_graph { use grapherity::models::AppendGraph; - grapherity::dfs_tests!(AppendGraph); + dfs_tests!(AppendGraph); } -mod graph_tests { +mod graph { use grapherity::models::Graph; - grapherity::dfs_tests!(Graph); + dfs_tests!(Graph); +} + +fn dfs_single_vertex() +where + G::Vertex: Debug, +{ + let (graph, v) = G::single_vertex(); + let result = algorithms::dfs(&graph, v); + assert!(result.visited[v], "source vertex should be visited"); + assert_eq!( + result.predecessors[v], None, + "unexpected predecessor of source vertex" + ); +} + +fn dfs_disconnected() +where + G::Vertex: Debug, +{ + let (graph, vertices) = G::disconnected(); + let result = algorithms::dfs(&graph, vertices[0]); + assert!( + result.visited[vertices[0]], + "source vertex should be visited" + ); + assert_eq!( + result.predecessors[vertices[0]], None, + "unexpected predecessor of source vertex" + ); + for &v in &vertices[1..3] { + assert!( + !result.visited[v], + "disconnected vertex {v:?} should not be visited" + ); + assert_eq!( + result.predecessors[v], None, + "disconnected vertex {v:?} should have no predecessor" + ); + } +} + +fn dfs() +where + G::Vertex: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + let result = algorithms::dfs(&graph, vertices[0]); + assert_dfs_visited::(&result.visited, &vertices); + assert_dfs_predecessors(&graph, &result.visited, &result.predecessors, &vertices); +} + +fn dfs_visited_single_vertex() { + let (graph, v) = G::single_vertex(); + let visited = algorithms::dfs_visited(&graph, v); + assert!(visited[v], "source vertex should be visited"); +} + +fn dfs_visited_disconnected() +where + G::Vertex: Debug, +{ + let (graph, vertices) = G::disconnected(); + let visited = algorithms::dfs_visited(&graph, vertices[0]); + assert!(visited[vertices[0]], "source vertex should be visited"); + for &v in &vertices[1..3] { + assert!( + !visited[v], + "disconnected vertex {v:?} should not be visited" + ); + } +} + +fn dfs_visited() +where + G::Vertex: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + let visited = algorithms::dfs_visited(&graph, vertices[0]); + assert_dfs_visited::(&visited, &vertices); +} + +fn dfs_find_source() { + let (graph, v) = G::single_vertex(); + assert!( + algorithms::dfs_find(&graph, v, v), + "source should find itself" + ); +} + +fn dfs_find_disconnected() { + let (graph, vertices) = G::disconnected(); + assert!( + !algorithms::dfs_find(&graph, vertices[0], vertices[1]), + "disconnected target should not be found" + ); +} + +fn dfs_find() +where + G::Vertex: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + for i in 0..10 { + assert!( + algorithms::dfs_find(&graph, vertices[0], vertices[i]), + "vertex {:?} should be reachable from {:?}", + vertices[i], + vertices[0] + ); + } +} + +fn dfs_find_where_source_matches() +where + G::Vertex: Debug, +{ + let (graph, v) = G::single_vertex(); + assert_eq!( + algorithms::dfs_find_where(&graph, v, |u| u == v), + Some(v), + "source should find itself" + ); +} + +fn dfs_find_where_disconnected() +where + G::Vertex: Debug, +{ + let (graph, vertices) = G::disconnected(); + assert_eq!( + algorithms::dfs_find_where(&graph, vertices[0], |v| v == vertices[1]), + None, + "disconnected vertex {:?} should not be found", + vertices[1] + ); +} + +fn dfs_find_where_no_match() +where + G::Vertex: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + assert_eq!( + algorithms::dfs_find_where(&graph, vertices[0], |_| false), + None, + "no vertex should match an always-false predicate" + ); +} + +fn dfs_find_where_adjacent() +where + G::Vertex: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + assert_eq!( + algorithms::dfs_find_where(&graph, vertices[0], |v| v == vertices[1]), + Some(vertices[1]), + "expected to find adjacent vertex" + ); +} + +fn dfs_find_where() +where + G::Vertex: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + assert_eq!( + algorithms::dfs_find_where(&graph, vertices[0], |v| v == vertices[9]), + Some(vertices[9]), + "expected to find connected vertex" + ); +} + +fn dfs_find_path_source_equals_target() +where + G::Edge: Debug, +{ + let (graph, v) = G::single_vertex(); + assert_eq!( + algorithms::dfs_find_path(&graph, v, v), + Some(vec![]), + "path from source to itself should be empty" + ); +} + +fn dfs_find_path_disconnected() +where + G::Edge: Debug, +{ + let (graph, vertices) = G::disconnected(); + assert_eq!( + algorithms::dfs_find_path(&graph, vertices[0], vertices[1]), + None, + "no path should exist to disconnected vertex" + ); +} + +fn dfs_find_path_adjacent() +where + G::Edge: Debug, +{ + let (graph, vertices, e) = G::single_edge(); + let path = algorithms::dfs_find_path(&graph, vertices[0], vertices[1]) + .expect("path should exist between adjacent vertices"); + assert_eq!( + path.len(), + 1, + "unexpected path length between adjacent vertices" + ); + assert_eq!(path[0], e, "path should use the connecting edge"); +} + +fn dfs_find_path() +where + G::Vertex: Debug, + G::Edge: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + let path = algorithms::dfs_find_path(&graph, vertices[0], vertices[9]).expect(&format!( + "path should exist between connected vertices {:?} and {:?}", + vertices[0], vertices[9] + )); + assert_valid_path(&graph, &path, vertices[0], vertices[9]); +} + +fn dfs_find_path_where_source_matches() +where + G::Edge: Debug, +{ + let (graph, v) = G::single_vertex(); + assert_eq!( + algorithms::dfs_find_path_where(&graph, v, |u| u == v), + Some(vec![]), + "path from source to itself should be empty" + ); +} + +fn dfs_find_path_where_disconnected() +where + G::Edge: Debug, +{ + let (graph, vertices) = G::disconnected(); + assert_eq!( + algorithms::dfs_find_path_where(&graph, vertices[0], |v| v == vertices[1]), + None, + "no path should exist to disconnected vertex" + ); +} + +fn dfs_find_path_where_no_match() +where + G::Edge: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + assert_eq!( + algorithms::dfs_find_path_where(&graph, vertices[0], |_| false), + None, + "no path should exist when predicate never matches" + ); +} + +fn dfs_find_path_where() +where + G::Vertex: Debug, + G::Edge: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + let path = algorithms::dfs_find_path_where(&graph, vertices[0], |v| v == vertices[9]).expect( + &format!( + "path should exist between connected vertices {:?} and {:?}", + vertices[0], vertices[9] + ), + ); + assert_valid_path(&graph, &path, vertices[0], vertices[9]); +} + +fn assert_dfs_visited( + visited: &ElementMap, + vertices: &[G::Vertex], +) where + G::Vertex: Debug, +{ + for i in 0..10 { + assert!( + visited[vertices[i]], + "vertex {:?} should be visited", + vertices[i] + ); + } +} + +fn assert_dfs_predecessors( + graph: &G, + visited: &ElementMap, + predecessors: &ElementMap>, + vertices: &[G::Vertex], +) where + G::Vertex: Debug, +{ + assert_eq!( + predecessors[vertices[0]], None, + "source should have no predecessor" + ); + for i in 1..10 { + let v = vertices[i]; + let p = predecessors[v].expect(&format!("vertex {v:?} should have a predecessor")); + assert!( + visited[p], + "predecessor {p:?} of vertex {v:?} should be visited" + ); + assert!( + graph.are_adjacent(v, p), + "predecessor {p:?} of vertex {v:?} should be adjacent" + ); + } +} + +fn assert_valid_path( + graph: &G, + path: &[G::Edge], + source: G::Vertex, + target: G::Vertex, +) where + G::Vertex: Debug, + G::Edge: Debug, +{ + assert!(!path.is_empty(), "path should be non-empty"); + // Walks the path: tracks current vertex, confirm each edge is incident to it. + let mut current = source; + for (i, &e) in path.iter().enumerate() { + let (v1, v2) = graph.incident_vertices(e); + assert_ne!(v1, v2, "path should not contain loop edge {e:?}"); + assert!( + v1 == current || v2 == current, + "path edge {e:?} (idx {i}, {v1:?} to {v2:?}) not incident to vertex {current:?}" + ); + current = if v1 == current { v2 } else { v1 }; + } + assert_eq!( + current, target, + "path should end at target {target:?}, but ended at {current:?}" + ); } diff --git a/tests/dijkstra.rs b/tests/dijkstra.rs index e655000..0945aa9 100644 --- a/tests/dijkstra.rs +++ b/tests/dijkstra.rs @@ -1,11 +1,348 @@ -mod append_graph_tests { +use std::fmt::Debug; +use std::hash::Hash; + +use grapherity::algorithms; +use grapherity::algorithms::DijkstraResult; +use grapherity::maps::ElementMap; +use grapherity::testing::fixtures::MakeTestGraph; +use grapherity::traits::{GraphTopology, Incidence}; + +macro_rules! dijkstra_tests { + ($T:ty) => { + dijkstra_tests!(@wrap $T, + dijkstra_single_vertex, + dijkstra_disconnected, + dijkstra_zero_weight_loop, + dijkstra, + dijkstra_distances, + dijkstra_unweighted_single_vertex, + dijkstra_unweighted_disconnected, + dijkstra_unweighted, + dijkstra_distances_unweighted_single_vertex, + dijkstra_distances_unweighted_disconnected, + dijkstra_distances_unweighted, + ); + }; + + (@wrap $T:ty, $($name:ident),* $(,)?) => { + $( + #[test] + fn $name() { + super::$name::<$T>(); + } + )* + }; +} + +mod append_graph { use grapherity::models::AppendGraph; - grapherity::dijkstra_tests!(AppendGraph); + dijkstra_tests!(AppendGraph); } -mod graph_tests { +mod graph { use grapherity::models::Graph; - grapherity::dijkstra_tests!(Graph); + dijkstra_tests!(Graph); +} + +fn dijkstra_single_vertex() +where + G::Vertex: Debug + Hash, +{ + let (graph, v) = G::single_vertex(); + let result = algorithms::dijkstra(&graph, v, |_| panic!("unexpected call of weight functor")); + assert_single_vertex::(&result, v); +} + +fn dijkstra_disconnected() +where + G::Vertex: Debug + Hash, +{ + let (graph, vertices) = G::disconnected(); + let result = algorithms::dijkstra(&graph, vertices[0], |_| { + panic!("unexpected call of weight functor") + }); + assert_single_vertex::(&result, vertices[0]); + assert_disconnected::(&result, &vertices[1..3]); +} + +fn dijkstra_zero_weight_loop() +where + G::Vertex: Debug + Hash, +{ + let (graph, vertices, _, weights) = make_two_edge_path_with_zero_weight_loops::(); + let result = algorithms::dijkstra(&graph, vertices[0], |e| weights[e]); + assert_single_vertex::(&result, vertices[0]); + for i in 1..3 { + assert_eq!( + result.predecessors[vertices[i]], + Some(vertices[i - 1]), + "unexpected predecessor of vertex {:?}", + vertices[i] + ); + assert_eq!( + result.distances[vertices[i]], + Some(i.try_into().unwrap()), + "unexpected distance of vertex {:?}", + vertices[i] + ); + } +} + +fn dijkstra() +where + G::Vertex: Debug + Hash, +{ + let (graph, vertices, _, _, weights) = make_test_graph_weighted::(); + let result = algorithms::dijkstra(&graph, vertices[0], |e| weights[e]); + assert_test_graph::(&result, &vertices); +} + +fn dijkstra_distances() +where + G::Vertex: Debug + Hash, +{ + let (graph, vertices, _, _, weights) = make_test_graph_weighted::(); + let distances = algorithms::dijkstra_distances(&graph, vertices[0], |e| weights[e]); + assert_distances_test_graph::(&distances, &vertices); +} + +fn dijkstra_unweighted_single_vertex() +where + G::Vertex: Debug + Hash, +{ + let (graph, v) = G::single_vertex(); + let result = algorithms::dijkstra_unweighted(&graph, v); + assert_single_vertex::(&result, v) +} + +fn dijkstra_unweighted_disconnected() +where + G::Vertex: Debug + Hash, +{ + let (graph, vertices) = G::disconnected(); + let result = algorithms::dijkstra_unweighted(&graph, vertices[0]); + assert_single_vertex::(&result, vertices[0]); + assert_disconnected::(&result, &vertices[1..3]); +} + +fn dijkstra_unweighted() +where + G::Vertex: Debug + Hash, +{ + let (graph, vertices, _, _) = G::standard(); + let result = algorithms::dijkstra_unweighted(&graph, vertices[0]); + assert_unweighted_test_graph::(&result, &vertices); +} + +fn dijkstra_distances_unweighted_single_vertex() +where + G::Vertex: Hash, +{ + let (graph, v) = G::single_vertex(); + let distances = algorithms::dijkstra_distances_unweighted(&graph, v); + assert_distances_single_vertex::(&distances, v); +} + +fn dijkstra_distances_unweighted_disconnected() +where + G::Vertex: Debug + Hash, +{ + let (graph, vertices) = G::disconnected(); + let distances = algorithms::dijkstra_distances_unweighted(&graph, vertices[0]); + assert_distances_single_vertex::(&distances, vertices[0]); + assert_distances_disconnected::(&distances, &vertices[1..3]); +} + +fn dijkstra_distances_unweighted() +where + G::Vertex: Debug + Hash, +{ + let (graph, vertices, _, _) = G::standard(); + let distances = algorithms::dijkstra_distances_unweighted(&graph, vertices[0]); + assert_distances_unweighted_test_graph::(&distances, &vertices); +} + +fn assert_single_vertex(result: &DijkstraResult, v: G::Vertex) +where + G::Vertex: Debug, +{ + assert_distances_single_vertex::(&result.distances, v); + assert_eq!( + result.predecessors[v], None, + "unexpected predecessor of source vertex", + ); +} + +fn assert_distances_single_vertex( + distances: &ElementMap>, + v: G::Vertex, +) { + assert_eq!( + distances[v], + Some(0), + "unexpected distance of source vertex" + ); +} + +fn assert_disconnected(result: &DijkstraResult, vertices: &[G::Vertex]) +where + G::Vertex: Debug, +{ + assert_distances_disconnected::(&result.distances, &vertices); + for &v in vertices { + assert_eq!( + result.predecessors[v], None, + "unexpected predecessor of disconnected vertex {v:?}", + ); + } +} + +fn assert_distances_disconnected( + distances: &ElementMap>, + vertices: &[G::Vertex], +) where + G::Vertex: Debug, +{ + for &v in vertices { + assert_eq!( + distances[v], None, + "unexpected distance of disconnected vertex {v:?}", + ); + } +} + +fn assert_test_graph(result: &DijkstraResult, vertices: &[G::Vertex]) +where + G::Vertex: Debug, +{ + assert_distances_test_graph::(&result.distances, &vertices); + let expected_predecessors_from_v0 = [ + vec![None], + vec![Some(vertices[0])], + vec![Some(vertices[4])], + vec![Some(vertices[1])], + vec![Some(vertices[7])], + vec![Some(vertices[9])], + vec![Some(vertices[3])], + vec![Some(vertices[9])], + vec![Some(vertices[7])], + vec![Some(vertices[6])], + ]; + for i in 0..10 { + assert!( + expected_predecessors_from_v0[i].contains(&result.predecessors[vertices[i]]), + "unexpected predecessor {:?} of {:?}", + result.predecessors[vertices[i]], + vertices[i] + ); + } +} + +fn assert_distances_test_graph( + distances: &ElementMap>, + vertices: &[G::Vertex], +) where + G::Vertex: Debug, +{ + let expected_distances_from_v0 = [ + Some(0), + Some(1), + Some(65), + Some(4), + Some(58), + Some(43), + Some(14), + Some(46), + Some(145), + Some(29), + ]; + for i in 0..10 { + assert_eq!( + distances[vertices[i]], expected_distances_from_v0[i], + "unexpected distance from {:?} to {:?}", + vertices[0], vertices[i] + ); + } +} + +fn assert_unweighted_test_graph( + result: &DijkstraResult, + vertices: &[G::Vertex], +) where + G::Vertex: Debug, +{ + assert_distances_unweighted_test_graph::(&result.distances, &vertices); + let expected_predecessors_from_v0 = [ + vec![None], + vec![Some(vertices[0])], + vec![Some(vertices[1])], + vec![Some(vertices[1])], + vec![Some(vertices[1])], + vec![Some(vertices[2])], + vec![Some(vertices[2]), Some(vertices[3])], + vec![Some(vertices[4])], + vec![Some(vertices[4])], + vec![Some(vertices[5]), Some(vertices[6]), Some(vertices[7])], + ]; + for i in 0..10 { + assert!( + expected_predecessors_from_v0[i].contains(&result.predecessors[vertices[i]]), + "unexpected predecessor {:?} of {:?}", + result.predecessors[vertices[i]], + vertices[i] + ); + } +} + +fn assert_distances_unweighted_test_graph( + distances: &ElementMap>, + vertices: &[G::Vertex], +) where + G::Vertex: Debug, +{ + let expected_distances_from_v0 = [ + Some(0), + Some(1), + Some(2), + Some(2), + Some(2), + Some(3), + Some(3), + Some(3), + Some(3), + Some(4), + ]; + for i in 0..10 { + assert_eq!( + distances[vertices[i]], expected_distances_from_v0[i], + "unexpected distance from {:?} to {:?}", + vertices[0], vertices[i] + ); + } +} + +fn make_test_graph_weighted() -> ( + G, + [G::Vertex; 10], + [(G::Edge, G::Vertex, G::Vertex); 18], + [Vec>; 10], + ElementMap, +) { + let (graph, vertices, edges, incidences) = G::standard(); + let mut weights = graph.edge_map(99); + for i in [1, 3, 7, 10, 12, 14, 15, 17] { + weights[edges[i].0] = i.try_into().unwrap(); + } + (graph, vertices, edges, incidences, weights) +} + +fn make_two_edge_path_with_zero_weight_loops() +-> (G, [G::Vertex; 3], [G::Edge; 4], ElementMap) { + let (graph, vertices, edges) = G::two_edge_path_with_loops(); + let mut weights = graph.edge_map(1); + weights[edges[2]] = 0; + weights[edges[3]] = 0; + (graph, vertices, edges, weights) }