Files
ortools-clone/ortools/graph/dag_shortest_path_test.cc
2025-08-11 09:13:53 +02:00

1236 lines
49 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/dag_shortest_path.h"
#include <algorithm>
#include <cstdint>
#include <limits>
#include <tuple>
#include <utility>
#include <vector>
#include "absl/algorithm/container.h"
#include "absl/log/check.h"
#include "absl/random/random.h"
#include "absl/status/status.h"
#include "absl/types/span.h"
#include "benchmark/benchmark.h"
#include "gtest/gtest.h"
#include "ortools/base/dump_vars.h"
#include "ortools/base/gmock.h"
#include "ortools/base/strong_int.h"
#include "ortools/base/strong_vector.h"
#include "ortools/graph/graph.h"
#include "ortools/graph/graph_io.h"
#include "ortools/util/flat_matrix.h"
namespace operations_research {
namespace {
constexpr double kInf = std::numeric_limits<double>::infinity();
using ::testing::ElementsAre;
using ::testing::FieldsAre;
using ::testing::HasSubstr;
using ::testing::IsEmpty;
using ::testing::status::StatusIs;
TEST(TopologicalOrderIsValidTest, ValidateTopologicalOrder) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
util::ListGraph<> graph(num_nodes, /*arc_capacity=*/2);
graph.AddArc(source, destination);
EXPECT_OK(TopologicalOrderIsValid(graph, {source, destination}));
EXPECT_THAT(TopologicalOrderIsValid(graph, {source}),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("topological_order.size() = 1")));
EXPECT_THAT(TopologicalOrderIsValid(graph, {source, source}),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("0 appears twice")));
EXPECT_THAT(TopologicalOrderIsValid(graph, {destination, source}),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("arc (0, 1) is inconsistent")));
graph.AddArc(source, source);
EXPECT_THAT(TopologicalOrderIsValid(graph, {source, destination}),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("arc (0, 0) is inconsistent")));
}
// -----------------------------------------------------------------------------
// ShortestPathOnDagTest and ShortestPathsOnDagWrapperTest.
// -----------------------------------------------------------------------------
TEST(ShortestPathOnDagTest, EmptyGraph) {
EXPECT_DEATH(ShortestPathsOnDag(/*num_nodes=*/0, /*arcs_with_length=*/{},
/*source=*/0, /*destination=*/0),
"num_nodes > 0");
}
TEST(ShortestPathOnDagTest, NonExistingSourceBecauseNegative) {
EXPECT_DEATH(
ShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, 0.0}},
/*source=*/-1, /*destination=*/1),
"Node must be nonnegative");
}
TEST(ShortestPathOnDagTest, NonExistingSourceBecauseTooLarge) {
EXPECT_DEATH(
ShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, 0.0}},
/*source=*/3, /*destination=*/1),
"num_nodes");
}
TEST(ShortestPathOnDagTest, NonExistingDestinationBecauseNegative) {
EXPECT_DEATH(
ShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, 0.0}},
/*source=*/0, /*destination=*/-1),
"Node must be nonnegative");
}
TEST(ShortestPathOnDagTest, NonExistingDestinationBecauseTooLarge) {
EXPECT_DEATH(
ShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, 0.0}},
/*source=*/0, /*destination=*/3),
"num_nodes");
}
TEST(ShortestPathOnDagTest, Cycle) {
EXPECT_DEATH(
ShortestPathsOnDag(/*num_nodes=*/2,
/*arcs_with_length=*/{{0, 1, 0.0}, {1, 0, 0.0}},
/*source=*/0, /*destination=*/1),
"cycle");
}
TEST(ShortestPathOnDagTest, SimpleGraph) {
const int source = 0;
const int destination = 1;
const int a = 2;
const int b = 3;
const int num_nodes = 4;
const std::vector<ArcWithLength> arcs_with_length = {{source, a, 5.0},
{source, b, 2.0},
{a, destination, 3.0},
{b, destination, 20.0}};
EXPECT_THAT(
ShortestPathsOnDag(num_nodes, arcs_with_length, source, destination),
FieldsAre(/*length=*/8.0, /*arc_path=*/ElementsAre(0, 2),
/*node_path=*/ElementsAre(source, a, destination)));
}
TEST(ShortestPathOnDagTest, GraphsWithNoArcs) {
EXPECT_THAT(ShortestPathsOnDag(/*num_nodes=*/1, /*arcs_with_length=*/{},
/*source=*/0, /*destination=*/0),
FieldsAre(/*length=*/0, /*arc_path=*/IsEmpty(),
/*node_path=*/ElementsAre(0)));
EXPECT_THAT(ShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{},
/*source=*/0, /*destination=*/1),
FieldsAre(/*length=*/kInf, /*arc_path=*/IsEmpty(),
/*node_path=*/IsEmpty()));
}
TEST(ShortestPathOnDagTest, SourceIsDestination) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
const std::vector<ArcWithLength> arcs_with_length = {
{source, destination, 1.0}};
EXPECT_THAT(ShortestPathsOnDag(num_nodes, arcs_with_length, source, source),
FieldsAre(
/*length=*/0.0, /*arc_path=*/IsEmpty(),
/*node_path=*/ElementsAre(source)));
}
TEST(ShortestPathOnDagTest, LargerGraphWithNegativeCost) {
const int source = 0;
const int a = 3;
const int b = 2;
const int c = 1;
const int destination = 4;
const int num_nodes = 5;
const std::vector<ArcWithLength> arcs_with_length = {
{a, c, 5.0}, {source, b, 7.0}, {a, b, 1.0},
{source, a, 3.0}, {c, destination, 5.0}, {b, destination, -2.0}};
EXPECT_THAT(
ShortestPathsOnDag(num_nodes, arcs_with_length, source, destination),
FieldsAre(/*length=*/2.0, /*arc_path=*/ElementsAre(3, 2, 5),
/*node_path=*/ElementsAre(source, a, b, destination)));
}
TEST(ShortestPathOnDagTest, SetsNotConnected) {
const int source = 0;
const int destination = 1;
const int a = 2;
const int num_nodes = 3;
const std::vector<ArcWithLength> arcs_with_length = {{source, a, 1.0}};
EXPECT_THAT(
ShortestPathsOnDag(num_nodes, arcs_with_length, source, destination),
FieldsAre(/*length=*/kInf, /*arc_path=*/IsEmpty(),
/*node_path=*/IsEmpty()));
}
TEST(ShortestPathOnDagTest, TwoConnectedComponents) {
const int a = 0;
const int b = 1;
const int c = 2;
const int d = 3;
const int e = 4;
const int num_nodes = 5;
const std::vector<ArcWithLength> arcs_with_length = {
{a, b, 0.0}, {b, c, 0.0}, {d, e, 0.0}};
EXPECT_THAT(ShortestPathsOnDag(num_nodes, arcs_with_length, /*source=*/a,
/*destination=*/e),
FieldsAre(/*length=*/kInf, /*arc_path=*/IsEmpty(),
/*node_path=*/IsEmpty()));
EXPECT_THAT(ShortestPathsOnDag(num_nodes, arcs_with_length, /*source=*/b,
/*destination=*/d),
FieldsAre(/*length=*/kInf, /*arc_path=*/IsEmpty(),
/*node_path=*/IsEmpty()));
}
TEST(ShortestPathOnDagTest, SetsNotConnectedDueToInfiniteCost) {
const int source = 0;
const int destination = 1;
const int a = 2;
const int num_nodes = 3;
const std::vector<ArcWithLength> arcs_with_length = {{a, destination, 1.0},
{source, a, kInf}};
EXPECT_THAT(
ShortestPathsOnDag(num_nodes, arcs_with_length, source, destination),
FieldsAre(/*length=*/kInf, /*arc_path=*/IsEmpty(),
/*node_path=*/IsEmpty()));
}
TEST(ShortestPathOnDagTest, AvoidInfiniteCost) {
const int source = 0;
const int destination = 1;
const int a = 2;
const int b = 3;
const int num_nodes = 4;
const std::vector<ArcWithLength> arcs_with_length = {{a, destination, 1.0},
{b, destination, 1.0},
{source, a, kInf},
{source, b, 3.0}};
EXPECT_THAT(
ShortestPathsOnDag(num_nodes, arcs_with_length, source, destination),
FieldsAre(/*length=*/4.0, /*arc_path=*/ElementsAre(3, 1),
/*node_path=*/ElementsAre(source, b, destination)));
}
TEST(ShortestPathOnDagTest, SourceNotFirst) {
const int destination = 0;
const int source = 1;
const int num_nodes = 2;
const std::vector<ArcWithLength> arcs_with_length = {
{source, destination, 1.0}};
EXPECT_THAT(
ShortestPathsOnDag(num_nodes, arcs_with_length, source, destination),
FieldsAre(/*length=*/1.0, /*arc_path=*/ElementsAre(0),
/*node_path=*/ElementsAre(source, destination)));
}
TEST(ShortestPathOnDagTest, MultipleArcs) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
const std::vector<ArcWithLength> arcs_with_length = {
{source, destination, 4.0}, {source, destination, 2.0}};
EXPECT_THAT(
ShortestPathsOnDag(num_nodes, arcs_with_length, source, destination),
FieldsAre(/*length=*/2.0, /*arc_path=*/ElementsAre(1),
/*node_path=*/ElementsAre(source, destination)));
}
TEST(ShortestPathOnDagTest, UpdateCost) {
const int source = 0;
const int destination = 1;
const int a = 2;
const int b = 3;
const int num_nodes = 4;
std::vector<ArcWithLength> arcs_with_length = {{source, a, 5.0},
{source, b, 2.0},
{a, destination, 3.0},
{b, destination, 20.0}};
EXPECT_THAT(
ShortestPathsOnDag(num_nodes, arcs_with_length, source, destination),
FieldsAre(/*length=*/8.0, /*arc_path=*/ElementsAre(0, 2),
/*node_path=*/ElementsAre(source, a, destination)));
// Update the length of arc b -> destination from 20.0 to -1.0.
arcs_with_length[3].length = -1.0;
EXPECT_THAT(
ShortestPathsOnDag(num_nodes, arcs_with_length, source, destination),
FieldsAre(/*length=*/1.0, /*arc_path=*/ElementsAre(1, 3),
/*node_path=*/ElementsAre(source, b, destination)));
}
DEFINE_STRONG_INT_TYPE(NodeIndex, int32_t);
DEFINE_STRONG_INT_TYPE(ArcIndex, int32_t);
TEST(ShortestPathsOnDagWrapperTest, StrongIndices) {
const NodeIndex source_1(0);
const NodeIndex source_2(1);
const NodeIndex destination(2);
const NodeIndex num_nodes(3);
util::ListGraph<NodeIndex, ArcIndex> graph(num_nodes,
/*arc_capacity=*/ArcIndex(2));
using ArcLengths = util_intops::StrongVector<ArcIndex, double>;
ArcLengths arc_lengths;
graph.AddArc(source_1, destination);
arc_lengths.push_back(-6.0);
graph.AddArc(source_2, destination);
arc_lengths.push_back(3.0);
const std::vector<NodeIndex> topological_order = {source_2, source_1,
destination};
ShortestPathsOnDagWrapper<util::ListGraph<NodeIndex, ArcIndex>, ArcLengths>
shortest_path_on_dag(&graph, &arc_lengths, topological_order);
shortest_path_on_dag.RunShortestPathOnDag({source_1, source_2});
EXPECT_TRUE(shortest_path_on_dag.IsReachable(destination));
EXPECT_THAT(shortest_path_on_dag.LengthTo(destination), -6.0);
EXPECT_THAT(shortest_path_on_dag.ArcPathTo(destination),
ElementsAre(ArcIndex(0)));
EXPECT_THAT(shortest_path_on_dag.NodePathTo(destination),
ElementsAre(source_1, destination));
}
TEST(ShortestPathsOnDagWrapperTest, MultipleSources) {
const int source_1 = 0;
const int source_2 = 1;
const int destination = 2;
const int num_nodes = 3;
util::ListGraph<> graph(num_nodes, /*arc_capacity=*/2);
std::vector<double> arc_lengths;
graph.AddArc(source_1, destination);
arc_lengths.push_back(-6.0);
graph.AddArc(source_2, destination);
arc_lengths.push_back(3.0);
const std::vector<int> topological_order = {source_2, source_1, destination};
ShortestPathsOnDagWrapper<util::ListGraph<>> shortest_path_on_dag(
&graph, &arc_lengths, topological_order);
shortest_path_on_dag.RunShortestPathOnDag({source_1, source_2});
EXPECT_TRUE(shortest_path_on_dag.IsReachable(destination));
EXPECT_THAT(shortest_path_on_dag.LengthTo(destination), -6.0);
EXPECT_THAT(shortest_path_on_dag.ArcPathTo(destination), ElementsAre(0));
EXPECT_THAT(shortest_path_on_dag.NodePathTo(destination),
ElementsAre(source_1, destination));
}
TEST(ShortestPathsOnDagWrapperTest, ShortestPathGoesThroughMultipleSources) {
const int source_1 = 0;
const int source_2 = 1;
const int destination = 2;
const int num_nodes = 3;
util::ListGraph<> graph(num_nodes, /*arc_capacity=*/2);
std::vector<double> arc_lengths;
graph.AddArc(source_1, source_2);
arc_lengths.push_back(-7.0);
graph.AddArc(source_2, destination);
arc_lengths.push_back(3.0);
const std::vector<int> topological_order = {source_1, source_2, destination};
ShortestPathsOnDagWrapper<util::ListGraph<>> shortest_path_on_dag(
&graph, &arc_lengths, topological_order);
shortest_path_on_dag.RunShortestPathOnDag({source_1, source_2});
EXPECT_TRUE(shortest_path_on_dag.IsReachable(destination));
EXPECT_THAT(shortest_path_on_dag.LengthTo(destination), -4.0);
EXPECT_THAT(shortest_path_on_dag.ArcPathTo(destination), ElementsAre(0, 1));
EXPECT_THAT(shortest_path_on_dag.NodePathTo(destination),
ElementsAre(source_1, source_2, destination));
}
TEST(ShortestPathsOnDagWrapperTest, MultipleDestinations) {
const int source = 0;
const int destination_1 = 1;
const int destination_2 = 2;
const int destination_3 = 3;
const int num_nodes = 4;
util::ListGraph<> graph(num_nodes, /*arc_capacity=*/3);
std::vector<double> arc_lengths;
graph.AddArc(source, destination_1);
arc_lengths.push_back(3.0);
graph.AddArc(source, destination_2);
arc_lengths.push_back(1.0);
graph.AddArc(source, destination_3);
arc_lengths.push_back(2.0);
const std::vector<int> topological_order = {source, destination_3,
destination_1, destination_2};
ShortestPathsOnDagWrapper<util::ListGraph<>> shortest_path_on_dag(
&graph, &arc_lengths, topological_order);
shortest_path_on_dag.RunShortestPathOnDag({source});
EXPECT_TRUE(shortest_path_on_dag.IsReachable(destination_1));
EXPECT_THAT(shortest_path_on_dag.LengthTo(destination_1), 3.0);
EXPECT_THAT(shortest_path_on_dag.ArcPathTo(destination_1), ElementsAre(0));
EXPECT_THAT(shortest_path_on_dag.NodePathTo(destination_1),
ElementsAre(source, destination_1));
EXPECT_TRUE(shortest_path_on_dag.IsReachable(destination_2));
EXPECT_THAT(shortest_path_on_dag.LengthTo(destination_2), 1.0);
EXPECT_THAT(shortest_path_on_dag.ArcPathTo(destination_2), ElementsAre(1));
EXPECT_THAT(shortest_path_on_dag.NodePathTo(destination_2),
ElementsAre(source, destination_2));
EXPECT_TRUE(shortest_path_on_dag.IsReachable(destination_3));
EXPECT_THAT(shortest_path_on_dag.LengthTo(destination_3), 2.0);
EXPECT_THAT(shortest_path_on_dag.ArcPathTo(destination_3), ElementsAre(2));
EXPECT_THAT(shortest_path_on_dag.NodePathTo(destination_3),
ElementsAre(source, destination_3));
}
TEST(ShortestPathsOnDagWrapperTest, UpdateCost) {
const int source = 0;
const int destination = 1;
const int a = 2;
const int b = 3;
const int num_nodes = 4;
util::ListGraph<> graph(num_nodes, /*arc_capacity=*/4);
std::vector<double> arc_lengths;
graph.AddArc(source, a);
arc_lengths.push_back(5.0);
graph.AddArc(source, b);
arc_lengths.push_back(2.0);
graph.AddArc(a, destination);
arc_lengths.push_back(3.0);
graph.AddArc(b, destination);
arc_lengths.push_back(20.0);
const std::vector<int> topological_order = {source, a, b, destination};
ShortestPathsOnDagWrapper<util::ListGraph<>> shortest_path_on_dag(
&graph, &arc_lengths, topological_order);
shortest_path_on_dag.RunShortestPathOnDag({source});
EXPECT_TRUE(shortest_path_on_dag.IsReachable(destination));
EXPECT_THAT(shortest_path_on_dag.LengthTo(destination), 8.0);
EXPECT_THAT(shortest_path_on_dag.ArcPathTo(destination), ElementsAre(0, 2));
EXPECT_THAT(shortest_path_on_dag.NodePathTo(destination),
ElementsAre(source, a, destination));
// Update the length of arc b -> destination from 20.0 to -1.0.
arc_lengths[3] = -1.0;
shortest_path_on_dag.RunShortestPathOnDag({source});
EXPECT_TRUE(shortest_path_on_dag.IsReachable(destination));
EXPECT_THAT(shortest_path_on_dag.LengthTo(destination), 1.0);
EXPECT_THAT(shortest_path_on_dag.ArcPathTo(destination), ElementsAre(1, 3));
EXPECT_THAT(shortest_path_on_dag.NodePathTo(destination),
ElementsAre(source, b, destination));
}
// Builds a random DAG with a given number of nodes and arcs where 0 is always
// the first and num_nodes-1 the last element in the topological order. Note
// that the graph always include at least one arc from 0 to num_nodes-1.
std::pair<util::StaticGraph<>, std::vector<int>> BuildRandomDag(
const int64_t num_nodes, const int64_t num_arcs) {
absl::BitGen bit_gen;
CHECK_GE(num_nodes, 2);
CHECK_GE(num_arcs, 1);
CHECK_LE(num_arcs, (num_nodes * (num_nodes - 1)) / 2);
std::vector<int> topological_order(num_nodes);
topological_order.back() = num_nodes - 1;
absl::Span<int> non_start_end =
absl::MakeSpan(topological_order).subspan(1, num_nodes - 2);
absl::c_iota(non_start_end, 1);
absl::c_shuffle(non_start_end, bit_gen);
int edges_added = 0;
util::StaticGraph<> graph(num_nodes, num_arcs);
graph.AddArc(0, num_nodes - 1);
while (edges_added < num_arcs - 1) {
int start_index = absl::Uniform(bit_gen, 0, num_nodes - 1);
int end_index = absl::Uniform(bit_gen, start_index + 1, num_nodes);
graph.AddArc(topological_order[start_index], topological_order[end_index]);
edges_added++;
}
graph.Build();
return {graph, topological_order};
}
// The length of each arc is drawn uniformly at random within a given interval
// except if the first arc from 0 to num_nodes-1 where it is set to a large
// length.
std::vector<double> GenerateRandomLengths(const util::StaticGraph<>& graph,
const double min_length = 0.0,
const double max_length = 10.0,
const double large_length = 10000.0) {
absl::BitGen bit_gen;
std::vector<double> arc_lengths;
arc_lengths.reserve(graph.num_arcs());
bool large_length_set = false;
for (util::StaticGraph<>::ArcIndex arc = 0; arc < graph.num_arcs(); ++arc) {
if (!large_length_set && graph.Tail(arc) == 0 &&
graph.Head(arc) == graph.num_nodes() - 1) {
arc_lengths.push_back(large_length);
large_length_set = true;
continue;
}
arc_lengths.push_back(static_cast<double>(
absl::Uniform<int>(bit_gen, min_length, max_length)));
}
return arc_lengths;
}
TEST(ShortestPathsOnDagWrapperTest, RandomizedStressTest) {
absl::BitGen bit_gen;
const int kNumTests = 10000;
for (int test = 0; test < kNumTests; ++test) {
const int num_nodes = absl::Uniform(bit_gen, 2, 12);
const int num_arcs = absl::Uniform(
bit_gen, 1, std::min(num_nodes * (num_nodes - 1) / 2, 15));
// Generate a random DAG with random lengths.
const auto [graph, topological_order] = BuildRandomDag(num_nodes, num_arcs);
const std::vector<double> arc_lengths = GenerateRandomLengths(graph);
// Run Floyd-Warshall as a 'reference' shortest path algorithm.
FlatMatrix<double> ref_dist(num_nodes, num_nodes, kInf);
for (int a = 0; a < num_arcs; ++a) {
double& d = ref_dist[graph.Tail(a)][graph.Head(a)];
if (arc_lengths[a] < d) d = arc_lengths[a];
}
for (int node = 0; node < num_nodes; ++node) {
ref_dist[node][node] = 0;
}
for (int k = 0; k < num_nodes; ++k) {
for (int i = 0; i < num_nodes; ++i) {
for (int j = 0; j < num_nodes; ++j) {
const double dist_through_k = ref_dist[i][k] + ref_dist[k][j];
if (dist_through_k < ref_dist[i][j]) ref_dist[i][j] = dist_through_k;
}
}
}
// Now, run some shortest paths and verify that they match. To balance out
// the FW (Floyd-Warshall) which is O(N³), we run more than one shortest
// path per FW.
ShortestPathsOnDagWrapper<util::StaticGraph<>> shortest_path_on_dag(
&graph, &arc_lengths, topological_order);
for (int _ = 0; _ < 20; ++_) {
// Draw sources (*with* repetition) with initial distances.
const int num_sources = absl::Uniform(bit_gen, 1, 5);
std::vector<int> sources(num_sources);
for (int& source : sources) {
source = absl::Uniform(bit_gen, 0, num_nodes);
}
// Precompute the reference minimum distance to each node (using any of
// the sources), and the expected reached nodes: any node whose distance
// is < kInf.
std::vector<double> node_min_dist(num_nodes, kInf);
std::vector<int> expected_reached_nodes;
for (int node = 0; node < num_nodes; ++node) {
double min_dist = kInf;
for (const int source : sources) {
min_dist = std::min(min_dist, ref_dist[source][node]);
}
node_min_dist[node] = min_dist;
if (min_dist < kInf) expected_reached_nodes.push_back(node);
}
shortest_path_on_dag.RunShortestPathOnDag(sources);
for (const int node : expected_reached_nodes) {
EXPECT_TRUE(shortest_path_on_dag.IsReachable(node));
EXPECT_EQ(shortest_path_on_dag.LengthTo(node), node_min_dist[node])
<< node;
}
ASSERT_FALSE(HasFailure())
<< DUMP_VARS(num_nodes, num_arcs, num_sources, sources, arc_lengths)
<< "\n With graph:\n"
<< util::GraphToString(graph, util::PRINT_GRAPH_ARCS);
}
}
}
// Debug tests.
#ifndef NDEBUG
TEST(ShortestPathOnDagTest, MinusInfWeight) {
EXPECT_DEATH(
ShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, -kInf}},
/*source=*/0, /*destination=*/1),
"-inf");
}
TEST(ShortestPathOnDagTest, NaNWeight) {
EXPECT_DEATH(
ShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/
{{0, 1, std::numeric_limits<double>::quiet_NaN()}},
/*source=*/0, /*destination=*/1),
"NaN");
}
TEST(ShortestPathsOnDagWrapperTest, ValidateTopologicalOrder) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
util::ListGraph<> graph(num_nodes, /*arc_capacity=*/1);
std::vector<double> arc_lengths;
graph.AddArc(source, destination);
arc_lengths.push_back(1.0);
const std::vector<int> topological_order = {source};
EXPECT_DEATH(ShortestPathsOnDagWrapper<util::ListGraph<>>(
&graph, &arc_lengths, topological_order),
"Invalid topological order");
}
#endif // NDEBUG
// -----------------------------------------------------------------------------
// ShortestPathsOnDagWrapper benchmarks.
// -----------------------------------------------------------------------------
void BM_RandomDag(benchmark::State& state) {
absl::BitGen bit_gen;
// Generate a fixed random DAG.
const int num_nodes = state.range(0);
const int num_arcs = num_nodes * state.range(1);
const auto [graph, topological_order] = BuildRandomDag(num_nodes, num_arcs);
// Generate 10 scenarios of random arc lengths.
const int num_scenarios = 10;
std::vector<std::vector<double>> arc_lengths_scenarios;
for (int _ = 0; _ < num_scenarios; ++_) {
arc_lengths_scenarios.push_back(GenerateRandomLengths(graph));
}
std::vector<double> arc_lengths = arc_lengths_scenarios.front();
ShortestPathsOnDagWrapper<util::StaticGraph<>> shortest_path_on_dag(
&graph, &arc_lengths, topological_order);
for (auto _ : state) {
// Pick a arc lengths scenario at random.
arc_lengths =
arc_lengths_scenarios[absl::Uniform(bit_gen, 0, num_scenarios)];
shortest_path_on_dag.RunShortestPathOnDag({0});
CHECK(shortest_path_on_dag.IsReachable(num_nodes - 1));
const double minimum_length = shortest_path_on_dag.LengthTo(num_nodes - 1);
CHECK_GE(minimum_length, 0.0);
CHECK_LE(minimum_length, 10000.0);
}
state.SetItemsProcessed(state.iterations() * (num_nodes + num_arcs));
}
BENCHMARK(BM_RandomDag)
->ArgPair(1000, 10)
->ArgPair(1 << 16, 4)
->ArgPair(1 << 16, 16)
->ArgPair(1 << 22, 4)
->ArgPair(1 << 22, 16)
->ArgPair(1 << 26, 4); // Don't go bigger, can't run on work station.
void BM_LineDag(benchmark::State& state) {
const int num_nodes = state.range(0);
const int num_edges = num_nodes - 1;
std::vector<int> topological_order(num_nodes);
util::StaticGraph<> graph(num_nodes, num_edges);
absl::c_iota(topological_order, 0);
for (int i = 0; i < num_nodes - 1; ++i) {
graph.AddArc(i, i + 1);
}
graph.Build();
std::vector<double> arc_lengths(num_edges, 1);
ShortestPathsOnDagWrapper<util::StaticGraph<>> shortest_path_on_dag(
&graph, &arc_lengths, topological_order);
for (auto _ : state) {
shortest_path_on_dag.RunShortestPathOnDag({0});
CHECK(shortest_path_on_dag.IsReachable(num_nodes - 1));
CHECK_EQ(shortest_path_on_dag.LengthTo(num_nodes - 1), num_nodes - 1);
CHECK_EQ(shortest_path_on_dag.ArcPathTo(num_nodes - 1).size(),
num_nodes - 1);
CHECK_EQ(shortest_path_on_dag.NodePathTo(num_nodes - 1).size(), num_nodes);
}
state.SetItemsProcessed(state.iterations() * num_nodes);
}
BENCHMARK(BM_LineDag)->Arg(1 << 16)->Arg(1 << 22)->Arg(1 << 24);
// -----------------------------------------------------------------------------
// KShortestPathOnDagTest and KShortestPathsOnDagWrapperTest.
// -----------------------------------------------------------------------------
TEST(KShortestPathOnDagTest, EmptyGraph) {
EXPECT_DEATH(
KShortestPathsOnDag(/*num_nodes=*/0, /*arcs_with_length=*/{},
/*source=*/0, /*destination=*/0, /*path_count=*/2),
"num_nodes > 0");
}
TEST(KShortestPathOnDagTest, NonExistingSourceBecauseNegative) {
EXPECT_DEATH(
KShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, 0.0}},
/*source=*/-1, /*destination=*/1, /*path_count=*/2),
"Node must be nonnegative");
}
TEST(KShortestPathOnDagTest, NonExistingSourceBecauseTooLarge) {
EXPECT_DEATH(
KShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, 0.0}},
/*source=*/3, /*destination=*/1, /*path_count=*/2),
"num_nodes");
}
TEST(KShortestPathOnDagTest, NonExistingDestinationBecauseNegative) {
EXPECT_DEATH(
KShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, 0.0}},
/*source=*/0, /*destination=*/-1, /*path_count=*/2),
"Node must be nonnegative");
}
TEST(KShortestPathOnDagTest, NonExistingDestinationBecauseTooLarge) {
EXPECT_DEATH(
KShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, 0.0}},
/*source=*/0, /*destination=*/3, /*path_count=*/2),
"num_nodes\\(\\)");
}
TEST(KShortestPathOnDagTest, KEqualsZero) {
EXPECT_DEATH(
KShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, 0.0}},
/*source=*/0, /*destination=*/1, /*path_count=*/0),
"path_count must be greater than 0");
}
TEST(KShortestPathOnDagTest, OnlyHasOnePath) {
const int source = 0;
const int destination = 1;
const int a = 2;
const int num_nodes = 3;
const std::vector<ArcWithLength> arcs_with_length = {{source, a, 1.0},
{a, destination, 1.0}};
EXPECT_THAT(KShortestPathsOnDag(num_nodes, arcs_with_length, source,
destination, /*path_count=*/2),
ElementsAre(FieldsAre(
/*length=*/2.0, /*arc_path=*/ElementsAre(0, 1),
/*node_path=*/ElementsAre(source, a, destination))));
}
TEST(KShortestPathOnDagTest, GraphsWithNoArcs) {
EXPECT_THAT(
KShortestPathsOnDag(/*num_nodes=*/1, /*arcs_with_length=*/{},
/*source=*/0, /*destination=*/0, /*path_count=*/2),
ElementsAre(FieldsAre(/*length=*/0, /*arc_path=*/IsEmpty(),
/*node_path=*/ElementsAre(0))));
EXPECT_THAT(
KShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{},
/*source=*/0, /*destination=*/1, /*path_count=*/2),
ElementsAre(FieldsAre(/*length=*/kInf, /*arc_path=*/IsEmpty(),
/*node_path=*/IsEmpty())));
}
TEST(KShortestPathOnDagTest, SourceIsDestination) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
const std::vector<ArcWithLength> arcs_with_length = {
{source, destination, 1.0}};
EXPECT_THAT(KShortestPathsOnDag(num_nodes, arcs_with_length, source, source,
/*path_count=*/2),
ElementsAre(FieldsAre(
/*length=*/0.0, /*arc_path=*/IsEmpty(),
/*node_path=*/ElementsAre(source))));
}
TEST(KShortestPathOnDagTest, HasTwoPaths) {
const int source = 0;
const int a = 1;
const int destination = 2;
const int num_nodes = 3;
const std::vector<ArcWithLength> arcs_with_length = {
{source, a, 1.0}, {source, destination, 30.0}, {a, destination, 1.0}};
EXPECT_THAT(
KShortestPathsOnDag(num_nodes, arcs_with_length, source, destination,
/*path_count=*/3),
ElementsAre(FieldsAre(
/*length=*/2.0, /*arc_path=*/ElementsAre(0, 2),
/*node_path=*/ElementsAre(source, a, destination)),
FieldsAre(
/*length=*/30.0, /*arc_path=*/ElementsAre(1),
/*node_path=*/ElementsAre(source, destination))));
}
TEST(KShortestPathOnDagTest, HasTwoPathsWithLongerPath) {
const int source = 0;
const int a = 1;
const int b = 2;
const int c = 3;
const int destination = 4;
const int num_nodes = 5;
const std::vector<ArcWithLength> arcs_with_length = {
{source, a, 1.0},
{source, destination, 30.0},
{a, b, 1.0},
{b, c, 1.0},
{c, destination, 1.0}};
EXPECT_THAT(
KShortestPathsOnDag(num_nodes, arcs_with_length, source, destination,
/*path_count=*/3),
ElementsAre(FieldsAre(
/*length=*/4.0, /*arc_path=*/ElementsAre(0, 2, 3, 4),
/*node_path=*/ElementsAre(source, a, b, c, destination)),
FieldsAre(
/*length=*/30.0, /*arc_path=*/ElementsAre(1),
/*node_path=*/ElementsAre(source, destination))));
}
TEST(KShortestPathOnDagTest, HeapSizeMustBeLargerThanPathCount) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
const std::vector<ArcWithLength> arcs_with_length = {
{source, destination, 2.0},
{source, destination, 3.0},
{source, destination, 1.0}};
EXPECT_THAT(
KShortestPathsOnDag(num_nodes, arcs_with_length, source, destination,
/*path_count=*/2),
ElementsAre(FieldsAre(
/*length=*/1.0, /*arc_path=*/ElementsAre(2),
/*node_path=*/ElementsAre(source, destination)),
FieldsAre(
/*length=*/2.0, /*arc_path=*/ElementsAre(0),
/*node_path=*/ElementsAre(source, destination))));
}
TEST(KShortestPathOnDagTest, LargerGraphWithNegativeCost) {
const int source = 0;
const int a = 3;
const int b = 2;
const int c = 1;
const int destination = 4;
const int num_nodes = 5;
const std::vector<ArcWithLength> arcs_with_length = {
{a, c, 5.0}, {source, b, 7.0}, {a, b, 1.0},
{source, a, 3.0}, {c, destination, 5.0}, {b, destination, -2.0}};
EXPECT_THAT(
KShortestPathsOnDag(num_nodes, arcs_with_length, source, destination,
/*path_count=*/2),
ElementsAre(
FieldsAre(/*length=*/2.0, /*arc_path=*/ElementsAre(3, 2, 5),
/*node_path=*/ElementsAre(source, a, b, destination)),
FieldsAre(/*length=*/5.0, /*arc_path=*/ElementsAre(1, 5),
/*node_path=*/ElementsAre(source, b, destination))));
}
TEST(KShortestPathOnDagTest, SetsNotConnected) {
const int source = 0;
const int destination = 1;
const int a = 2;
const int num_nodes = 3;
const std::vector<ArcWithLength> arcs_with_length = {{source, a, 1.0}};
EXPECT_THAT(KShortestPathsOnDag(num_nodes, arcs_with_length, source,
destination, /*path_count=*/2),
ElementsAre(FieldsAre(/*length=*/kInf, /*arc_path=*/IsEmpty(),
/*node_path=*/IsEmpty())));
}
TEST(KShortestPathOnDagTest, TwoConnectedComponents) {
const int a = 0;
const int b = 1;
const int c = 2;
const int d = 3;
const int e = 4;
const int num_nodes = 5;
const std::vector<ArcWithLength> arcs_with_length = {
{a, b, 0.0}, {b, c, 0.0}, {d, e, 0.0}};
EXPECT_THAT(KShortestPathsOnDag(num_nodes, arcs_with_length, /*source=*/a,
/*destination=*/e, /*path_count=*/2),
ElementsAre(FieldsAre(/*length=*/kInf, /*arc_path=*/IsEmpty(),
/*node_path=*/IsEmpty())));
EXPECT_THAT(KShortestPathsOnDag(num_nodes, arcs_with_length, /*source=*/b,
/*destination=*/d, /*path_count=*/2),
ElementsAre(FieldsAre(/*length=*/kInf, /*arc_path=*/IsEmpty(),
/*node_path=*/IsEmpty())));
}
TEST(KShortestPathOnDagTest, SetsNotConnectedDueToInfiniteCost) {
const int source = 0;
const int destination = 1;
const int a = 2;
const int num_nodes = 3;
const std::vector<ArcWithLength> arcs_with_length = {{a, destination, 1.0},
{source, a, kInf}};
EXPECT_THAT(KShortestPathsOnDag(num_nodes, arcs_with_length, source,
destination, /*path_count=*/2),
ElementsAre(FieldsAre(/*length=*/kInf, /*arc_path=*/IsEmpty(),
/*node_path=*/IsEmpty())));
}
TEST(KShortestPathOnDagTest, AvoidInfiniteCost) {
const int source = 0;
const int destination = 1;
const int a = 2;
const int b = 3;
const int num_nodes = 4;
const std::vector<ArcWithLength> arcs_with_length = {{a, destination, 1.0},
{b, destination, 1.0},
{source, a, kInf},
{source, b, 3.0}};
EXPECT_THAT(KShortestPathsOnDag(num_nodes, arcs_with_length, source,
destination, /*path_count=*/2),
ElementsAre(FieldsAre(
/*length=*/4.0, /*arc_path=*/ElementsAre(3, 1),
/*node_path=*/ElementsAre(source, b, destination))));
}
TEST(KShortestPathOnDagTest, SourceNotFirst) {
const int destination = 0;
const int source = 1;
const int num_nodes = 2;
const std::vector<ArcWithLength> arcs_with_length = {
{source, destination, 1.0}};
EXPECT_THAT(KShortestPathsOnDag(num_nodes, arcs_with_length, source,
destination, /*path_count=*/2),
ElementsAre(FieldsAre(
/*length=*/1.0, /*arc_path=*/ElementsAre(0),
/*node_path=*/ElementsAre(source, destination))));
}
TEST(KShortestPathOnDagTest, MultipleArcs) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
const std::vector<ArcWithLength> arcs_with_length = {
{source, destination, 4.0},
{source, destination, 3.0},
{source, destination, -2.0},
{source, destination, 0.0}};
EXPECT_THAT(KShortestPathsOnDag(num_nodes, arcs_with_length, source,
destination, /*path_count=*/3),
ElementsAre(FieldsAre(
/*length=*/-2.0, /*arc_path=*/ElementsAre(2),
/*node_path=*/ElementsAre(source, destination)),
FieldsAre(
/*length=*/0.0, /*arc_path=*/ElementsAre(3),
/*node_path=*/ElementsAre(source, destination)),
FieldsAre(
/*length=*/3.0, /*arc_path=*/ElementsAre(1),
/*node_path=*/ElementsAre(source, destination))));
}
TEST(KShortestPathOnDagTest, UpdateCost) {
const int source = 0;
const int destination = 1;
const int a = 2;
const int b = 3;
const int num_nodes = 4;
std::vector<ArcWithLength> arcs_with_length = {{source, a, 5.0},
{source, b, 2.0},
{a, destination, 3.0},
{b, destination, 20.0}};
EXPECT_THAT(
KShortestPathsOnDag(num_nodes, arcs_with_length, source, destination,
/*path_count=*/2),
ElementsAre(FieldsAre(
/*length=*/8.0, /*arc_path=*/ElementsAre(0, 2),
/*node_path=*/ElementsAre(source, a, destination)),
FieldsAre(
/*length=*/22.0, /*arc_path=*/ElementsAre(1, 3),
/*node_path=*/ElementsAre(source, b, destination))));
// Update the length of arc b -> destination from 20.0 to -1.0.
arcs_with_length[3].length = -1.0;
EXPECT_THAT(
KShortestPathsOnDag(num_nodes, arcs_with_length, source, destination,
/*path_count=*/2),
ElementsAre(FieldsAre(
/*length=*/1.0, /*arc_path=*/ElementsAre(1, 3),
/*node_path=*/ElementsAre(source, b, destination)),
FieldsAre(
/*length=*/8.0, /*arc_path=*/ElementsAre(0, 2),
/*node_path=*/ElementsAre(source, a, destination))));
}
TEST(KShortestPathsOnDagWrapperTest, StrongIndices) {
const NodeIndex source_1(0);
const NodeIndex source_2(1);
const NodeIndex destination(2);
const NodeIndex num_nodes(3);
util::ListGraph<NodeIndex, ArcIndex> graph(num_nodes,
/*arc_capacity=*/ArcIndex(2));
using ArcLengths = util_intops::StrongVector<ArcIndex, double>;
ArcLengths arc_lengths;
graph.AddArc(source_1, destination);
arc_lengths.push_back(-6.0);
graph.AddArc(source_2, destination);
arc_lengths.push_back(3.0);
const std::vector<NodeIndex> topological_order = {source_2, source_1,
destination};
const int path_count = 2;
KShortestPathsOnDagWrapper<util::ListGraph<NodeIndex, ArcIndex>, ArcLengths>
shortest_paths_on_dag(&graph, &arc_lengths, topological_order,
path_count);
shortest_paths_on_dag.RunKShortestPathOnDag({source_1, source_2});
EXPECT_TRUE(shortest_paths_on_dag.IsReachable(destination));
EXPECT_THAT(shortest_paths_on_dag.LengthsTo(destination),
ElementsAre(-6.0, 3.0));
EXPECT_THAT(shortest_paths_on_dag.ArcPathsTo(destination),
ElementsAre(ElementsAre(ArcIndex(0)), ElementsAre(ArcIndex(1))));
EXPECT_THAT(shortest_paths_on_dag.NodePathsTo(destination),
ElementsAre(ElementsAre(source_1, destination),
ElementsAre(source_2, destination)));
}
TEST(KShortestPathsOnDagWrapperTest, MultipleSources) {
const int source_1 = 0;
const int source_2 = 1;
const int destination = 2;
const int num_nodes = 3;
util::ListGraph<> graph(num_nodes, /*arc_capacity=*/2);
std::vector<double> arc_lengths;
graph.AddArc(source_1, destination);
arc_lengths.push_back(-6.0);
graph.AddArc(source_2, destination);
arc_lengths.push_back(3.0);
const std::vector<int> topological_order = {source_2, source_1, destination};
const int path_count = 2;
KShortestPathsOnDagWrapper<util::ListGraph<>> shortest_paths_on_dag(
&graph, &arc_lengths, topological_order, path_count);
shortest_paths_on_dag.RunKShortestPathOnDag({source_1, source_2});
EXPECT_TRUE(shortest_paths_on_dag.IsReachable(destination));
EXPECT_THAT(shortest_paths_on_dag.LengthsTo(destination),
ElementsAre(-6.0, 3.0));
EXPECT_THAT(shortest_paths_on_dag.ArcPathsTo(destination),
ElementsAre(ElementsAre(0), ElementsAre(1)));
EXPECT_THAT(shortest_paths_on_dag.NodePathsTo(destination),
ElementsAre(ElementsAre(source_1, destination),
ElementsAre(source_2, destination)));
}
TEST(KShortestPathsOnDagWrapperTest, ShortestPathGoesThroughMultipleSources) {
const int source_1 = 0;
const int source_2 = 1;
const int destination = 2;
const int num_nodes = 3;
util::ListGraph<> graph(num_nodes, /*arc_capacity=*/3);
std::vector<double> arc_lengths;
graph.AddArc(source_1, source_2);
arc_lengths.push_back(-7.0);
graph.AddArc(source_2, destination);
arc_lengths.push_back(3.0);
graph.AddArc(source_1, destination);
arc_lengths.push_back(5.0);
const std::vector<int> topological_order = {source_1, source_2, destination};
const int path_count = 2;
KShortestPathsOnDagWrapper<util::ListGraph<>> shortest_paths_on_dag(
&graph, &arc_lengths, topological_order, path_count);
shortest_paths_on_dag.RunKShortestPathOnDag({source_1, source_2});
EXPECT_TRUE(shortest_paths_on_dag.IsReachable(destination));
EXPECT_THAT(shortest_paths_on_dag.LengthsTo(destination),
ElementsAre(-4.0, 3.0));
EXPECT_THAT(shortest_paths_on_dag.ArcPathsTo(destination),
ElementsAre(ElementsAre(0, 1), ElementsAre(1)));
EXPECT_THAT(shortest_paths_on_dag.NodePathsTo(destination),
ElementsAre(ElementsAre(source_1, source_2, destination),
ElementsAre(source_2, destination)));
}
TEST(KShortestPathsOnDagWrapperTest, MultipleDestinations) {
const int source = 0;
const int destination_1 = 1;
const int destination_2 = 2;
const int destination_3 = 3;
const int num_nodes = 4;
util::ListGraph<> graph(num_nodes, /*arc_capacity=*/3);
std::vector<double> arc_lengths;
graph.AddArc(source, destination_1);
arc_lengths.push_back(3.0);
graph.AddArc(source, destination_2);
arc_lengths.push_back(1.0);
graph.AddArc(source, destination_3);
arc_lengths.push_back(2.0);
const std::vector<int> topological_order = {source, destination_3,
destination_1, destination_2};
const int path_count = 2;
KShortestPathsOnDagWrapper<util::ListGraph<>> shortest_paths_on_dag(
&graph, &arc_lengths, topological_order, path_count);
shortest_paths_on_dag.RunKShortestPathOnDag({source});
EXPECT_TRUE(shortest_paths_on_dag.IsReachable(destination_1));
EXPECT_THAT(shortest_paths_on_dag.LengthsTo(destination_1)[0], 3.0);
EXPECT_THAT(shortest_paths_on_dag.ArcPathsTo(destination_1)[0],
ElementsAre(0));
EXPECT_THAT(shortest_paths_on_dag.NodePathsTo(destination_1)[0],
ElementsAre(source, destination_1));
EXPECT_TRUE(shortest_paths_on_dag.IsReachable(destination_2));
EXPECT_THAT(shortest_paths_on_dag.LengthsTo(destination_2)[0], 1.0);
EXPECT_THAT(shortest_paths_on_dag.ArcPathsTo(destination_2)[0],
ElementsAre(1));
EXPECT_THAT(shortest_paths_on_dag.NodePathsTo(destination_2)[0],
ElementsAre(source, destination_2));
EXPECT_TRUE(shortest_paths_on_dag.IsReachable(destination_3));
EXPECT_THAT(shortest_paths_on_dag.LengthsTo(destination_3)[0], 2.0);
EXPECT_THAT(shortest_paths_on_dag.ArcPathsTo(destination_3)[0],
ElementsAre(2));
EXPECT_THAT(shortest_paths_on_dag.NodePathsTo(destination_3)[0],
ElementsAre(source, destination_3));
}
TEST(KShortestPathsOnDagWrapperTest, RandomizedStressTest) {
absl::BitGen bit_gen;
const int kNumTests = 10000;
for (int test = 0; test < kNumTests; ++test) {
const int num_nodes = absl::Uniform(bit_gen, 2, 12);
const int num_arcs = absl::Uniform(
bit_gen, 1, std::min(num_nodes * (num_nodes - 1) / 2, 15));
// Generate a random DAG with random lengths.
const auto [graph, topological_order] = BuildRandomDag(num_nodes, num_arcs);
const std::vector<double> arc_lengths = GenerateRandomLengths(graph);
ShortestPathsOnDagWrapper<util::StaticGraph<>> shortest_path_on_dag(
&graph, &arc_lengths, topological_order);
const int path_count = 5;
KShortestPathsOnDagWrapper<util::StaticGraph<>> shortest_paths_on_dag(
&graph, &arc_lengths, topological_order, path_count);
for (int _ = 0; _ < 20; ++_) {
// Draw sources (*with* repetition) with initial distances.
const int num_sources = absl::Uniform(bit_gen, 1, 5);
std::vector<int> sources(num_sources);
for (int& source : sources) {
source = absl::Uniform(bit_gen, 0, num_nodes);
}
// Compute the number of paths
std::vector<int> all_paths_count(num_nodes);
for (const int source : sources) {
all_paths_count[source] = 1;
}
for (const int from : topological_order) {
for (const int arc : graph.OutgoingArcs(from)) {
const int to = graph.Head(arc);
all_paths_count[to] += all_paths_count[from];
}
}
shortest_path_on_dag.RunShortestPathOnDag(sources);
shortest_paths_on_dag.RunKShortestPathOnDag(sources);
for (const int node : shortest_path_on_dag.reached_nodes()) {
EXPECT_TRUE(shortest_paths_on_dag.IsReachable(node));
EXPECT_EQ(shortest_paths_on_dag.LengthsTo(node)[0],
shortest_path_on_dag.LengthTo(node))
<< node;
EXPECT_EQ(shortest_paths_on_dag.LengthsTo(node).size(),
std::min(all_paths_count[node], path_count))
<< node;
}
ASSERT_FALSE(HasFailure())
<< DUMP_VARS(num_nodes, num_arcs, num_sources, sources, arc_lengths)
<< "\n With graph:\n"
<< util::GraphToString(graph, util::PRINT_GRAPH_ARCS);
}
}
}
// Debug tests.
#ifndef NDEBUG
TEST(KShortestPathOnDagTest, MinusInfWeight) {
EXPECT_DEATH(
KShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, -kInf}},
/*source=*/0, /*destination=*/1, /*path_count=*/2),
"-inf");
}
TEST(KShortestPathOnDagTest, NaNWeight) {
EXPECT_DEATH(
KShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/
{{0, 1, std::numeric_limits<double>::quiet_NaN()}},
/*source=*/0, /*destination=*/1, /*path_count=*/2),
"NaN");
}
TEST(KShortestPathsOnDagWrapperTest, ValidateTopologicalOrder) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
util::ListGraph<> graph(num_nodes, /*arc_capacity=*/1);
std::vector<double> arc_lengths;
graph.AddArc(source, destination);
arc_lengths.push_back(1.0);
const std::vector<int> topological_order = {source};
const int path_count = 2;
EXPECT_DEATH(KShortestPathsOnDagWrapper<util::ListGraph<>>(
&graph, &arc_lengths, topological_order, path_count),
"Invalid topological order");
}
#endif // NDEBUG
// -----------------------------------------------------------------------------
// KShortestPathsOnDagWrapper benchmarks.
// -----------------------------------------------------------------------------
void BM_RandomDag_K(benchmark::State& state) {
absl::BitGen bit_gen;
// Generate a fixed random DAG.
const int num_nodes = state.range(0);
const int num_arcs = num_nodes * state.range(1);
const int path_count = state.range(2);
const auto [graph, topological_order] = BuildRandomDag(num_nodes, num_arcs);
// Generate 10 scenarios of random arc lengths.
const int num_scenarios = 10;
std::vector<std::vector<double>> arc_lengths_scenarios;
for (int _ = 0; _ < num_scenarios; ++_) {
arc_lengths_scenarios.push_back(GenerateRandomLengths(graph));
}
std::vector<double> arc_lengths = arc_lengths_scenarios.front();
KShortestPathsOnDagWrapper<util::StaticGraph<>> shortest_paths_on_dag(
&graph, &arc_lengths, topological_order, path_count);
for (auto _ : state) {
// Pick a arc lengths scenario at random.
arc_lengths =
arc_lengths_scenarios[absl::Uniform(bit_gen, 0, num_scenarios)];
shortest_paths_on_dag.RunKShortestPathOnDag({0});
CHECK(shortest_paths_on_dag.IsReachable(num_nodes - 1));
const std::vector<double> lengths =
shortest_paths_on_dag.LengthsTo(num_nodes - 1);
CHECK_GE(lengths[0], 0.0);
CHECK_LE(lengths[0], 10000.0);
}
state.SetItemsProcessed(state.iterations() * (num_nodes + num_arcs));
}
BENCHMARK(BM_RandomDag_K)
->Args({1000, 10, 4})
->Args({1 << 16, 4, 4})
->Args({1 << 16, 4, 16})
->Args({1 << 16, 16, 4})
->Args({1 << 16, 16, 16})
->Args({1 << 22, 4, 4})
->Args({1 << 22, 4, 16});
} // namespace
} // namespace operations_research