Files
ortools-clone/ortools/graph/shortest_paths_test.cc
Corentin Le Molgat 40c2136bd4 graph: remove all dag_*
2025-03-03 18:02:26 +01:00

453 lines
20 KiB
C++

// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "ortools/graph/shortest_paths.h"
#include <algorithm>
#include <random>
#include <vector>
#include "absl/base/macros.h"
#include "absl/log/check.h"
#include "absl/random/random.h"
#include "gtest/gtest.h"
#include "ortools/graph/graph.h"
#include "ortools/graph/strongly_connected_components.h"
namespace operations_research {
template <class GraphType>
void CheckPathDataPair(
const GenericPathContainer<GraphType>& container,
const GenericPathContainer<GraphType>& distance_container,
PathDistance expected_distance,
typename GraphType::NodeIndex expected_predecessor,
typename GraphType::NodeIndex tail, typename GraphType::NodeIndex head) {
EXPECT_EQ(expected_distance, container.GetDistance(tail, head));
EXPECT_EQ(expected_distance, distance_container.GetDistance(tail, head));
EXPECT_EQ(expected_predecessor,
container.GetPenultimateNodeInPath(tail, head));
EXPECT_DEATH(distance_container.GetPenultimateNodeInPath(tail, head),
"Path not stored.");
// Checking path between tail and head.
std::vector<typename GraphType::NodeIndex> paths;
container.GetPath(tail, head, &paths);
if (tail == head) {
EXPECT_GE(1, paths.size());
if (!paths.empty()) {
EXPECT_EQ(tail, paths.back());
}
} else if (!paths.empty()) {
EXPECT_EQ(tail, paths[0]);
typename GraphType::NodeIndex current = head;
for (int i = paths.size() - 1; i >= 0; --i) {
EXPECT_EQ(current, paths[i]);
current = container.GetPenultimateNodeInPath(tail, current);
}
}
EXPECT_DEATH(distance_container.GetPath(tail, head, &paths),
"Path not stored.");
}
template <class GraphType>
void CheckPathDataRow(const GraphType& graph,
const GenericPathContainer<GraphType>& container,
const GenericPathContainer<GraphType>& distance_container,
const typename GraphType::NodeIndex expected_paths[],
const PathDistance expected_distances[],
typename GraphType::NodeIndex tail) {
int index = tail * graph.num_nodes();
for (const typename GraphType::NodeIndex head : graph.AllNodes()) {
CheckPathDataPair(container, distance_container, expected_distances[index],
expected_paths[index], tail, head);
++index;
}
}
template <class GraphType>
void CheckPathDataRowFromGraph(
const GraphType& graph, const GenericPathContainer<GraphType>& container,
const GenericPathContainer<GraphType>& distance_container,
const typename GraphType::NodeIndex expected_paths[],
const PathDistance expected_distances[],
typename GraphType::NodeIndex tail) {
int index = tail * graph.num_nodes();
for (typename GraphType::NodeIndex head : graph.AllNodes()) {
CheckPathDataPair(container, distance_container, expected_distances[index],
expected_paths[index], tail, head);
++index;
}
}
template <class GraphType>
void CheckPathData(const GraphType& graph,
const GenericPathContainer<GraphType>& container,
const GenericPathContainer<GraphType>& distance_container,
const typename GraphType::NodeIndex expected_paths[],
const PathDistance expected_distances[]) {
for (const typename GraphType::NodeIndex tail : graph.AllNodes()) {
CheckPathDataRow(graph, container, distance_container, expected_paths,
expected_distances, tail);
}
}
template <class GraphType>
void CheckPathDataFromGraph(
const GraphType& graph, const GenericPathContainer<GraphType>& container,
const GenericPathContainer<GraphType>& distance_container,
const typename GraphType::NodeIndex expected_paths[],
const PathDistance expected_distances[]) {
for (typename GraphType::NodeIndex tail : graph.AllNodes()) {
CheckPathDataRowFromGraph(graph, container, distance_container,
expected_paths, expected_distances, tail);
}
}
#define BUILD_CONTAINERS() \
auto container = \
GenericPathContainer<GraphType>::BuildInMemoryCompactPathContainer(); \
auto distance_container = \
GenericPathContainer<GraphType>::BuildPathDistanceContainer()
template <class GraphType>
void TestShortestPathsFromGraph(
const GraphType& graph, const std::vector<PathDistance>& lengths,
const typename GraphType::NodeIndex expected_paths[],
const PathDistance expected_distances[]) {
const int kThreads = 32;
const typename GraphType::NodeIndex source = 0;
std::vector<typename GraphType::NodeIndex> some_nodes;
int index = 0;
std::mt19937 randomizer(12345);
for (const typename GraphType::NodeIndex node : graph.AllNodes()) {
if (absl::Bernoulli(randomizer, 1.0 / 2)) {
some_nodes.push_back(node);
}
++index;
}
// All-pair shortest paths.
{
BUILD_CONTAINERS();
ComputeAllToAllShortestPathsWithMultipleThreads(graph, lengths, kThreads,
&container);
ComputeAllToAllShortestPathsWithMultipleThreads(graph, lengths, kThreads,
&distance_container);
CheckPathDataFromGraph(graph, container, distance_container, expected_paths,
expected_distances);
}
// One-to-all shortest paths.
{
BUILD_CONTAINERS();
ComputeOneToAllShortestPaths(graph, lengths, source, &container);
ComputeOneToAllShortestPaths(graph, lengths, source, &distance_container);
CheckPathDataRowFromGraph(graph, container, distance_container,
expected_paths, expected_distances, source);
}
// Many-to-all shortest paths.
{
BUILD_CONTAINERS();
ComputeManyToAllShortestPathsWithMultipleThreads(graph, lengths, some_nodes,
kThreads, &container);
ComputeManyToAllShortestPathsWithMultipleThreads(
graph, lengths, some_nodes, kThreads, &distance_container);
for (int i = 0; i < some_nodes.size(); ++i) {
CheckPathDataRowFromGraph(graph, container, distance_container,
expected_paths, expected_distances,
some_nodes[i]);
}
}
// Many-to-all shortest paths with duplicates.
{
BUILD_CONTAINERS();
std::vector<typename GraphType::NodeIndex> sources(3, source);
ComputeManyToAllShortestPathsWithMultipleThreads(graph, lengths, sources,
kThreads, &container);
ComputeManyToAllShortestPathsWithMultipleThreads(
graph, lengths, sources, kThreads, &distance_container);
for (int i = 0; i < sources.size(); ++i) {
CheckPathDataRowFromGraph(graph, container, distance_container,
expected_paths, expected_distances, sources[i]);
}
}
// One-to-many shortest paths.
{
BUILD_CONTAINERS();
ComputeOneToManyShortestPaths(graph, lengths, source, some_nodes,
&container);
ComputeOneToManyShortestPaths(graph, lengths, source, some_nodes,
&distance_container);
index = source * graph.num_nodes();
for (int i = 0; i < some_nodes.size(); ++i) {
CheckPathDataPair(container, distance_container,
expected_distances[index + some_nodes[i]],
expected_paths[index + some_nodes[i]], source,
some_nodes[i]);
}
} // Many-to-many shortest paths.
{
BUILD_CONTAINERS();
ComputeManyToManyShortestPathsWithMultipleThreads(
graph, lengths, some_nodes, some_nodes, kThreads, &container);
ComputeManyToManyShortestPathsWithMultipleThreads(
graph, lengths, some_nodes, some_nodes, kThreads, &distance_container);
for (int i = 0; i < some_nodes.size(); ++i) {
index = some_nodes[i] * graph.num_nodes();
for (int j = 0; j < some_nodes.size(); ++j) {
CheckPathDataPair(container, distance_container,
expected_distances[index + some_nodes[j]],
expected_paths[index + some_nodes[j]], some_nodes[i],
some_nodes[j]);
}
}
}
}
#undef BUILD_CONTAINERS
template <class GraphType>
void TestShortestPathsFromGraph(
int num_nodes, int num_arcs, const typename GraphType::NodeIndex arcs[][2],
const PathDistance arc_lengths[],
const typename GraphType::NodeIndex expected_paths[],
const PathDistance expected_distances[]) {
GraphType graph(num_nodes, num_arcs);
std::vector<PathDistance> lengths(num_arcs);
for (int i = 0; i < num_arcs; ++i) {
lengths[graph.AddArc(arcs[i][0], arcs[i][1])] = arc_lengths[i];
}
std::vector<typename GraphType::ArcIndex> permutation;
graph.Build(&permutation);
util::Permute(permutation, &lengths);
TestShortestPathsFromGraph(graph, lengths, expected_paths,
expected_distances);
}
// Series of shortest paths tests on small graphs.
template <typename GraphType>
class GraphShortestPathsDeathTest : public testing::Test {};
template <typename GraphType>
class GraphShortestPathsTest : public testing::Test {};
typedef testing::Types<::util::ListGraph<>, ::util::StaticGraph<>,
::util::ReverseArcListGraph<>,
::util::ReverseArcStaticGraph<>>
GraphTypesForShortestPathsTesting;
TYPED_TEST_SUITE(GraphShortestPathsDeathTest,
GraphTypesForShortestPathsTesting);
TYPED_TEST_SUITE(GraphShortestPathsTest, GraphTypesForShortestPathsTesting);
// Test on an empty graph.
TYPED_TEST(GraphShortestPathsDeathTest, ShortestPathsEmptyGraph) {
const auto kExpectedPaths = nullptr;
const auto kExpectedDistances = nullptr;
TypeParam graph;
std::vector<PathDistance> lengths;
TestShortestPathsFromGraph(graph, lengths, kExpectedPaths,
kExpectedDistances);
}
// Test on a disconnected graph (set of nodes pointing to themselves).
TYPED_TEST(GraphShortestPathsDeathTest, ShortestPathsAllDisconnected) {
const typename TypeParam::NodeIndex kUnconnected = TypeParam::kNilNode;
const int kNodes = 3;
const typename TypeParam::NodeIndex kArcs[][2] = {{0, 0}, {1, 1}, {2, 2}};
const PathDistance kArcLengths[] = {0, 0, 0};
const int kExpectedPaths[] = {0, kUnconnected, kUnconnected, kUnconnected,
1, kUnconnected, kUnconnected, kUnconnected,
2};
const PathDistance kExpectedDistances[] = {0,
kDisconnectedPathDistance,
kDisconnectedPathDistance,
kDisconnectedPathDistance,
0,
kDisconnectedPathDistance,
kDisconnectedPathDistance,
kDisconnectedPathDistance,
0};
TestShortestPathsFromGraph<TypeParam>(kNodes, ABSL_ARRAYSIZE(kArcLengths),
kArcs, kArcLengths, kExpectedPaths,
kExpectedDistances);
}
// 1 1 1
// -> 0 ---> 2 ------> 4 <--- 1
// | | | |
// | |4 1| |
// | | 1 | 3|
// |1 ---> 3 ---> 5 <- |
// | || |
// --------------- ----------
//
TYPED_TEST(GraphShortestPathsDeathTest, ShortestPaths1) {
const int kNodes = 6;
const typename TypeParam::NodeIndex kArcs[][2] = {
{0, 2}, {0, 3}, {1, 4}, {2, 4}, {3, 5}, {4, 5}, {5, 0}, {5, 1}};
const PathDistance kArcLengths[] = {1, 4, 1, 1, 1, 1, 1, 3};
const int kExpectedPaths[] = {5, 5, 0, 0, 2, 4, 5, 5, 0, 0, 1, 4,
5, 5, 0, 0, 2, 4, 5, 5, 0, 0, 2, 3,
5, 5, 0, 0, 2, 4, 5, 5, 0, 0, 2, 4};
const PathDistance kExpectedDistances[] = {
4, 6, 1, 4, 2, 3, 3, 5, 4, 7, 1, 2, 3, 5, 4, 7, 1, 2,
2, 4, 3, 6, 4, 1, 2, 4, 3, 6, 4, 1, 1, 3, 2, 5, 3, 4};
TestShortestPathsFromGraph<TypeParam>(kNodes, ABSL_ARRAYSIZE(kArcLengths),
kArcs, kArcLengths, kExpectedPaths,
kExpectedDistances);
}
// 0
// ---
// | | 1 4
// -> 0 -----> 1 -----> 4 --
// || | ^ |
// ||3 |1 1| |
// || | | |
// | ------> 2 ------- |
// | |
// | 1 |
// ----------------------
TYPED_TEST(GraphShortestPathsDeathTest, ShortestPaths2) {
const int kNodes = 4;
const typename TypeParam::NodeIndex kArcs[][2] = {
{0, 1}, {0, 0}, {0, 2}, {1, 2}, {1, 3}, {2, 3}, {3, 0}};
const PathDistance kArcLengths[] = {1, 0, 3, 1, 4, 1, 1};
const int kExpectedPaths[] = {0, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2};
const PathDistance kExpectedDistances[] = {0, 1, 2, 3, 3, 4, 1, 2,
2, 3, 4, 1, 1, 2, 3, 4};
TestShortestPathsFromGraph<TypeParam>(kNodes, ABSL_ARRAYSIZE(kArcLengths),
kArcs, kArcLengths, kExpectedPaths,
kExpectedDistances);
}
TYPED_TEST(GraphShortestPathsDeathTest, MismatchedData) {
TypeParam graph(2, 2);
graph.AddArc(0, 1);
graph.AddArc(1, 0);
std::vector<PathDistance> lengths = {0};
auto container =
GenericPathContainer<TypeParam>::BuildInMemoryCompactPathContainer();
EXPECT_DEATH(ComputeAllToAllShortestPathsWithMultipleThreads(graph, lengths,
1, &container),
"Number of arcs in graph must match arc length vector size");
}
// Test the case where some sources are not strongly connected to themselves.
TYPED_TEST(GraphShortestPathsDeathTest, SourceNotConnectedToItself) {
const typename TypeParam::NodeIndex kUnconnected = TypeParam::kNilNode;
const int kNodes = 3;
const typename TypeParam::NodeIndex kArcs[][2] = {{1, 2}, {2, 2}};
const PathDistance kArcLengths[] = {1, 0};
const int kExpectedPaths[] = {kUnconnected, kUnconnected, kUnconnected,
kUnconnected, kUnconnected, 1,
kUnconnected, kUnconnected, 2};
const PathDistance kExpectedDistances[] = {kDisconnectedPathDistance,
kDisconnectedPathDistance,
kDisconnectedPathDistance,
kDisconnectedPathDistance,
kDisconnectedPathDistance,
1,
kDisconnectedPathDistance,
kDisconnectedPathDistance,
0};
TestShortestPathsFromGraph<TypeParam>(kNodes, ABSL_ARRAYSIZE(kArcLengths),
kArcs, kArcLengths, kExpectedPaths,
kExpectedDistances);
}
// Test the case where the graph is a multigraph, a graph with parallel arcs
// (arcs which have the same end nodes).
TYPED_TEST(GraphShortestPathsDeathTest, Multigraph) {
const int kNodes = 4;
const typename TypeParam::NodeIndex kArcs[][2] = {
{0, 1}, {0, 1}, {0, 2}, {0, 2}, {1, 3}, {2, 3}, {1, 3}, {2, 3}, {3, 0}};
const PathDistance kArcLengths[] = {2, 3, 1, 2, 2, 2, 1, 1, 1};
const int kExpectedPaths[] = {3, 0, 0, 2, 3, 0, 0, 1, 3, 0, 0, 2, 3, 0, 0, 2};
const PathDistance kExpectedDistances[] = {3, 2, 1, 2, 2, 4, 3, 1,
2, 4, 3, 1, 1, 3, 2, 3};
TestShortestPathsFromGraph<TypeParam>(kNodes, ABSL_ARRAYSIZE(kArcLengths),
kArcs, kArcLengths, kExpectedPaths,
kExpectedDistances);
}
// Large test on a random strongly connected graph with 10,000,000 nodes and
// 50,000,000 arcs.
// Shortest paths are computed between 10 randomly chosen nodes.
TYPED_TEST(GraphShortestPathsTest, DISABLED_LargeRandomShortestPaths) {
const int kSize = 10000000;
const int kDegree = 4;
const int max_distance = 50;
const PathDistance kConnectionArcLength = 300;
std::mt19937 randomizer(12345);
TypeParam graph(kSize, kSize + kSize * kDegree);
std::vector<PathDistance> lengths;
for (int i = 0; i < kSize; ++i) {
const typename TypeParam::NodeIndex tail(
absl::Uniform(randomizer, 0, kSize));
for (int j = 0; j < kDegree; ++j) {
const typename TypeParam::NodeIndex head(
absl::Uniform(randomizer, 0, kSize));
const PathDistance length =
1 + absl::Uniform(randomizer, 0, max_distance);
graph.AddArc(tail, head);
lengths.push_back(length);
}
}
typename TypeParam::NodeIndex prev_index = TypeParam::kNilNode;
typename TypeParam::NodeIndex first_index = TypeParam::kNilNode;
for (const typename TypeParam::NodeIndex node_index : graph.AllNodes()) {
if (prev_index != TypeParam::kNilNode) {
graph.AddArc(prev_index, node_index);
lengths.push_back(kConnectionArcLength);
} else {
first_index = node_index;
}
prev_index = node_index;
}
graph.AddArc(prev_index, first_index);
lengths.push_back(kConnectionArcLength);
std::vector<typename TypeParam::ArcIndex> permutation;
graph.Build(&permutation);
util::Permute(permutation, &lengths);
std::vector<std::vector<typename TypeParam::NodeIndex>> components;
::FindStronglyConnectedComponents(graph.num_nodes(), graph, &components);
CHECK_EQ(1, components.size());
CHECK_EQ(kSize, components[0].size());
const int kSourceSize = 10;
const int source_size = std::min(graph.num_nodes(), kSourceSize);
std::vector<typename TypeParam::NodeIndex> sources(source_size, 0);
for (int i = 0; i < source_size; ++i) {
sources[i] = absl::Uniform(randomizer, 0, graph.num_nodes());
}
const int kThreads = 10;
auto container =
GenericPathContainer<TypeParam>::BuildInMemoryCompactPathContainer();
ComputeManyToManyShortestPathsWithMultipleThreads(
graph, lengths, sources, sources, kThreads, &container);
auto distance_container =
GenericPathContainer<TypeParam>::BuildPathDistanceContainer();
ComputeManyToManyShortestPathsWithMultipleThreads(
graph, lengths, sources, sources, kThreads, &distance_container);
for (int tail = 0; tail < sources.size(); ++tail) {
for (int head = 0; head < sources.size(); ++head) {
EXPECT_NE(TypeParam::kNilNode, container.GetPenultimateNodeInPath(
sources[tail], sources[head]));
EXPECT_NE(kDisconnectedPathDistance,
container.GetDistance(sources[tail], sources[head]));
EXPECT_NE(kDisconnectedPathDistance,
distance_container.GetDistance(sources[tail], sources[head]));
}
}
}
} // namespace operations_research