diff --git a/include/graaflib/algorithm/shortest_path.tpp b/include/graaflib/algorithm/shortest_path.tpp index 26f0ed16..6a99409e 100644 --- a/include/graaflib/algorithm/shortest_path.tpp +++ b/include/graaflib/algorithm/shortest_path.tpp @@ -207,7 +207,16 @@ bellman_ford_shortest_paths(const graph& graph, } } } - + // Negative cycle detection by doing an additional pass in the graph + for (const auto& [edge_id, edge] : graph.get_edges()) { + const auto [u, v]{edge_id}; + WEIGHT_T weight = get_weight(edge); + if (shortest_paths[u].total_weight != std::numeric_limits::max() && shortest_paths[u].total_weight + weight < shortest_paths[v].total_weight) { + std::ostringstream error_msg; + error_msg << "Negative cycle detected in the graph."; + throw std::invalid_argument{error_msg.str()}; + } + } return shortest_paths; } diff --git a/test/graaflib/algorithm/shortest_path_test.cpp b/test/graaflib/algorithm/shortest_path_test.cpp index b3a5ec10..0253e329 100644 --- a/test/graaflib/algorithm/shortest_path_test.cpp +++ b/test/graaflib/algorithm/shortest_path_test.cpp @@ -626,7 +626,67 @@ TYPED_TEST(BellmanFordShortestPathsTest, expected_path_map[vertex_id_5] = path5; ASSERT_EQ(path_map, expected_path_map); } +template +struct BellmanFordShortestPathsSignedTypesTest : public testing::Test { + using graph_t = typename T::first_type; + using edge_t = typename T::second_type; +}; +using undirected_weighted_graph_signed_types = testing::Types< + /** + * Primitive edge type directed graph + */ + std::pair, int>, + std::pair, float>, + std::pair, long double>, + + /** + * Non primitive weighted edge type directed graph + */ + std::pair>,my_weighted_edge>, + std::pair>,my_weighted_edge>, + std::pair>,my_weighted_edge>>; +TYPED_TEST_SUITE(BellmanFordShortestPathsSignedTypesTest,undirected_weighted_graph_signed_types); + +TYPED_TEST(BellmanFordShortestPathsSignedTypesTest, BellmanFordNegativecycleTest) { + // Bellman Ford can handle negative edge weights only in directed graphs. + // GIVEN + using graph_t = typename TestFixture::graph_t; + using edge_t = typename TestFixture::edge_t; + using weight_t = decltype(get_weight(std::declval())); + + graph_t graph{}; + // Adding vertices + const auto vertex_id_1{graph.add_vertex(10)}; + const auto vertex_id_2{graph.add_vertex(20)}; + const auto vertex_id_3{graph.add_vertex(30)}; + const auto vertex_id_4{graph.add_vertex(40)}; + + // Adding Edges + + // Negative cycle exists between the vertices 2,3 and 4. + graph.add_edge(vertex_id_2, vertex_id_1, edge_t{static_cast(1)}); + graph.add_edge(vertex_id_2, vertex_id_3, edge_t{static_cast(-6)}); + graph.add_edge(vertex_id_3, vertex_id_4, edge_t{static_cast(-1)}); + graph.add_edge(vertex_id_4, vertex_id_2, edge_t{static_cast(-2)}); + + ASSERT_THROW( + { + try { + [[maybe_unused]] const auto path{bellman_ford_shortest_paths(graph, vertex_id_3)}; + // If the above line doesn't throw an exception, fail the test + FAIL() + << "Expected std::invalid_argument exception, but no exception was thrown"; + } + catch (const std::invalid_argument &ex) { + // Verify that the exception message contains the expected err or message. + EXPECT_STREQ("Negative cycle detected in the graph.",ex.what()); + throw; + } + }, + std::invalid_argument); + +} template struct AStarShortestPathTest : public testing::Test { using graph_t = typename T::first_type;