graph: add dag_* stuff

This commit is contained in:
Corentin Le Molgat
2025-03-13 15:35:07 +01:00
parent 461505f4af
commit 36ca3496c0
21 changed files with 5083 additions and 0 deletions

View File

@@ -628,6 +628,31 @@ cc_test(
],
)
cc_library(
name = "dag_connectivity",
srcs = ["dag_connectivity.cc"],
hdrs = ["dag_connectivity.h"],
deps = [
":topologicalsorter",
"//ortools/base",
"//ortools/base:container_logging",
"//ortools/util:bitset",
"@com_google_absl//absl/types:span",
],
)
cc_test(
name = "dag_connectivity_test",
srcs = ["dag_connectivity_test.cc"],
deps = [
":dag_connectivity",
"//ortools/base:gmock_main",
"//ortools/util:bitset",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/types:span",
],
)
cc_library(
name = "perfect_matching",
srcs = ["perfect_matching.cc"],
@@ -646,6 +671,40 @@ cc_library(
],
)
cc_library(
name = "dag_shortest_path",
srcs = ["dag_shortest_path.cc"],
hdrs = ["dag_shortest_path.h"],
deps = [
":graph",
":topologicalsorter",
"@com_google_absl//absl/algorithm:container",
"@com_google_absl//absl/log",
"@com_google_absl//absl/log:check",
"@com_google_absl//absl/status",
"@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/types:span",
],
)
cc_library(
name = "dag_constrained_shortest_path",
srcs = ["dag_constrained_shortest_path.cc"],
hdrs = ["dag_constrained_shortest_path.h"],
deps = [
":dag_shortest_path",
":graph",
":topologicalsorter",
"//ortools/base:threadpool",
"@com_google_absl//absl/algorithm:container",
"@com_google_absl//absl/base:log_severity",
"@com_google_absl//absl/log:check",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/types:span",
],
)
cc_library(
name = "rooted_tree",
hdrs = ["rooted_tree.h"],
@@ -696,6 +755,46 @@ cc_test(
],
)
cc_test(
name = "dag_shortest_path_test",
size = "small",
srcs = ["dag_shortest_path_test.cc"],
deps = [
":dag_shortest_path",
":graph",
":io",
"//ortools/base:dump_vars",
"//ortools/base:gmock_main",
"//ortools/util:flat_matrix",
"@com_google_absl//absl/algorithm:container",
"@com_google_absl//absl/log:check",
"@com_google_absl//absl/random",
"@com_google_absl//absl/status",
"@com_google_absl//absl/types:span",
"@com_google_benchmark//:benchmark",
],
)
cc_test(
name = "dag_constrained_shortest_path_test",
srcs = ["dag_constrained_shortest_path_test.cc"],
deps = [
":dag_constrained_shortest_path",
":graph",
":io",
"//ortools/base:dump_vars",
"//ortools/base:gmock_main",
"//ortools/math_opt/cpp:math_opt",
"//ortools/math_opt/solvers:cp_sat_solver",
"@com_google_absl//absl/algorithm:container",
"@com_google_absl//absl/log:check",
"@com_google_absl//absl/random",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/types:span",
"@com_google_benchmark//:benchmark",
],
)
# From util/graph
cc_library(
name = "connected_components",

View File

@@ -28,6 +28,16 @@ Generic algorithms for shortest paths:
Specific algorithms for paths:
* [`dag_shortest_path.h`][dag_shortest_path_h]: shortest paths on directed
acyclic graphs. If you have such a graph, this implementation is likely to
be the fastest. Unlike most implementations, these algorithms have two
interfaces: a "simple" one (list of edges and weights) and a standard one
(taking as input a graph data structure from
[`//ortools/graph/graph.h`][graph_h]).
* [`dag_constrained_shortest_path.`][dag_constrained_shortest_path_h]:
shortest paths on directed acyclic graphs with resource constraints.
* [`hamiltonian_path.h`][hamiltonian_path_h]: entry point for computing
minimum [Hamiltonian paths](https://en.wikipedia.org/wiki/Hamiltonian_path)
and cycles on directed graphs with costs on arcs, using a

View File

@@ -0,0 +1,122 @@
// 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_connectivity.h"
#include <algorithm>
#include <cstdint>
#include <utility>
#include <vector>
#include "absl/types/span.h"
#include "ortools/base/container_logging.h"
#include "ortools/base/logging.h"
#include "ortools/graph/topologicalsorter.h"
namespace operations_research {
// The algorithm is as follows:
// 1. Sort the nodes of the graph topologically. If a cycle is detected,
// terminate
// 2. Build the adjacency list for the graph, i.e., adj_list[i] is the list
// of nodes that can be directly reached from i.
// 3. Create a 2d bool vector x where x[i][j] indicates there is a path from
// i to j, and for each arc in "arcs", set x[i][j] to true
// 4. In reverse topological order (leaves first) for each node i, for each
// child j of i, for each node k reachable for j, set k to be reachable
// from i as well (x[i][k] = true for all k s.t. x[j][k] is true).
//
// The running times of the steps are:
// 1. O(num_arcs)
// 2. O(num_arcs)
// 3. O(num_nodes^2 + num_arcs)
// 4. O(num_nodes*num_arcs)
// Thus the total run time is O(num_nodes^2 + num_nodes*num_arcs).
//
// Implementation note: typically, step 4 will dominate. To speed up the inner
// loop, we use Bitset64, allowing use to merge 64 x[k][j] values at a time with
// the |= operator.
//
// For graphs where num_arcs is o(num_nodes), a different data structure could
// be used in 3, but this isn't really the interesting case (and prevents |=).
//
// A further improvement on this algorithm is possible, step four can run in
// time O(num_nodes*num_arcs_in_transitive_reduction), and as a by product,
// the transitive reduction can also be produced as output. For details, see
// "A REDUCT-AND_CLOSURE ALGORITHM FOR GRAPHS" (Alla Goralcikova and
// Vaclav Koubek 1979). The better typeset paper "AN IMPROVED ALGORITHM FOR
// TRANSITIVE CLOSURE ON ACYCLIC DIGRAPHS" (Klaus Simon 1988) gives a slight
// improvement on the result (less memory, same runtime).
std::vector<Bitset64<int64_t>> ComputeDagConnectivity(
absl::Span<const std::pair<int, int>> arcs, bool* error_was_cyclic,
std::vector<int>* error_cycle_out) {
CHECK(error_was_cyclic != nullptr);
CHECK(error_cycle_out != nullptr);
*error_was_cyclic = false;
error_cycle_out->clear();
if (arcs.empty()) return {};
int num_nodes = 0;
for (const std::pair<int, int>& arc : arcs) {
CHECK_GE(arc.first, 0);
CHECK_GE(arc.second, 0);
num_nodes = std::max(num_nodes, arc.first + 1);
num_nodes = std::max(num_nodes, arc.second + 1);
}
DenseIntStableTopologicalSorter sorter(num_nodes);
for (const auto& arc : arcs) {
sorter.AddEdge(arc.first, arc.second);
}
std::vector<int> topological_order;
int next;
while (sorter.GetNext(&next, error_was_cyclic, error_cycle_out)) {
topological_order.push_back(next);
}
if (*error_was_cyclic) return {};
std::vector<std::vector<int>> adjacency_list(num_nodes);
for (const auto& arc : arcs) {
adjacency_list[arc.first].push_back(arc.second);
}
std::vector<Bitset64<int64_t>> connectivity(num_nodes);
for (Bitset64<int64_t>& bitset : connectivity) {
bitset.Resize(num_nodes);
}
for (const auto& arc : arcs) {
connectivity[arc.first].Set(arc.second);
}
// Iterate over the nodes in reverse topological order.
std::reverse(topological_order.begin(), topological_order.end());
// NOTE(user): these two loops visit every arc in the graph, and each
// union is over a set of size given by the number of nodes. This gives the
// runtime in step 4 of O(num_nodes*num_arcs)
for (const int node : topological_order) {
for (const int child : adjacency_list[node]) {
connectivity[node].Union(connectivity[child]);
}
}
return connectivity;
}
std::vector<Bitset64<int64_t>> ComputeDagConnectivityOrDie(
absl::Span<const std::pair<int, int>> arcs) {
bool error_was_cyclic = false;
std::vector<int> error_cycle;
std::vector<Bitset64<int64_t>> result =
ComputeDagConnectivity(arcs, &error_was_cyclic, &error_cycle);
CHECK(!error_was_cyclic) << "Graph should have been acyclic but has cycle: "
<< gtl::LogContainer(error_cycle);
return result;
}
} // namespace operations_research

View File

@@ -0,0 +1,60 @@
// 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.
#ifndef OR_TOOLS_GRAPH_DAG_CONNECTIVITY_H_
#define OR_TOOLS_GRAPH_DAG_CONNECTIVITY_H_
#include <cstdint>
#include <utility>
#include <vector>
#include "absl/types/span.h"
#include "ortools/util/bitset.h"
namespace operations_research {
// Given a directed graph, as defined by the arc list "arcs", computes either:
// 1. If the graph is acyclic, the matrix of values x, where x[i][j] indicates
// that there is a directed path from i to j.
// 2. If the graph is cyclic, "error_cycle_out" is set to contain the cycle,
// and the return value is empty.
//
// The algorithm runs in O(num_nodes^2 + num_nodes*num_arcs).
//
// Inputs:
// arcs: each a in "arcs" is a directed edge from a.first to a.second. Must
// have a.first, a.second >= 0. The graph is assumed to have nodes
// {0,1,...,max_{a in arcs} max(a.first, a.second)}, or have no nodes
// if arcs is the empty list.
// error_was_cyclic: output arg, is set to true if a cycle is detected.
// error_cycle_out: output arg, if a cycle is detected, error_cycle_out is
// set to contain the nodes of the cycle in order.
//
// Note: useful for computing the transitive closure of a binary relation, e.g.
// given the relation i < j for i,j in S that is transitive and some known
// values i < j, create a node for each i in S and an arc for each known
// relationship. Then any relationship implied by transitivity is given by
// the resulting matrix produced, or if the relation fails transitivity, a cycle
// proving this is produced.
std::vector<Bitset64<int64_t>> ComputeDagConnectivity(
absl::Span<const std::pair<int, int>> arcs, bool* error_was_cyclic,
std::vector<int>* error_cycle_out);
// Like above, but will CHECK fail if the digraph with arc list "arcs"
// contains a cycle.
std::vector<Bitset64<int64_t>> ComputeDagConnectivityOrDie(
absl::Span<const std::pair<int, int>> arcs);
} // namespace operations_research
#endif // OR_TOOLS_GRAPH_DAG_CONNECTIVITY_H_

View File

@@ -0,0 +1,182 @@
// 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_connectivity.h"
#include <cstdint>
#include <utility>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/types/span.h"
#include "gtest/gtest.h"
#include "ortools/base/gmock.h"
#include "ortools/util/bitset.h"
using std::pair;
using std::vector;
namespace operations_research {
namespace {
TEST(DagConnectivityTest, EmptyGraph) {
vector<pair<int, int>> arc_list;
bool error = false;
vector<int> cycle;
vector<Bitset64<int64_t>> conn =
ComputeDagConnectivity(arc_list, &error, &cycle);
ASSERT_FALSE(error);
EXPECT_TRUE(cycle.empty());
EXPECT_TRUE(conn.empty());
}
void CheckMatrix(vector<vector<bool>> expected,
absl::Span<const Bitset64<int64_t>> actual) {
int size = expected.size();
ASSERT_EQ(size, actual.size());
for (int i = 0; i < size; i++) {
SCOPED_TRACE(absl::StrCat("row", i));
ASSERT_EQ(size, expected[i].size());
ASSERT_EQ(size, actual[i].size());
}
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
SCOPED_TRACE(absl::StrCat("entry:", i, ",", j));
EXPECT_EQ(expected[i][j], actual[i][j]);
}
}
}
TEST(DagConnectivityTest, SimpleGraph) {
vector<pair<int, int>> arc_list({{1, 0}});
bool error = false;
vector<int> cycle;
vector<Bitset64<int64_t>> conn =
ComputeDagConnectivity(arc_list, &error, &cycle);
ASSERT_FALSE(error);
EXPECT_TRUE(cycle.empty());
CheckMatrix({{false, false}, {true, false}}, conn);
}
TEST(DagConnectivityTest, SimpleSparseGraph) {
vector<pair<int, int>> arc_list({{3, 1}});
bool error = false;
vector<int> cycle;
vector<Bitset64<int64_t>> conn =
ComputeDagConnectivity(arc_list, &error, &cycle);
ASSERT_FALSE(error);
EXPECT_TRUE(cycle.empty());
vector<vector<bool>> expected(4, vector<bool>(4));
expected[3][1] = true;
CheckMatrix(expected, conn);
}
TEST(DagConnectivityTest, SelfCycle) {
vector<pair<int, int>> arc_list({{0, 1}, {1, 1}});
bool error = false;
vector<int> cycle;
vector<Bitset64<int64_t>> conn =
ComputeDagConnectivity(arc_list, &error, &cycle);
ASSERT_TRUE(error);
EXPECT_THAT(cycle, testing::ElementsAre(1));
}
TEST(DagConnectivityTest, LongCycle) {
vector<pair<int, int>> arc_list({{2, 3}, {3, 5}, {5, 2}});
bool error = false;
vector<int> cycle;
vector<Bitset64<int64_t>> conn =
ComputeDagConnectivity(arc_list, &error, &cycle);
ASSERT_TRUE(error);
EXPECT_THAT(cycle, testing::UnorderedElementsAre(2, 3, 5));
}
TEST(DagConnectivityTest, BasicTransitive) {
vector<pair<int, int>> arc_list({{0, 1}, {1, 2}});
bool error = false;
vector<int> cycle;
vector<Bitset64<int64_t>> conn =
ComputeDagConnectivity(arc_list, &error, &cycle);
ASSERT_FALSE(error);
EXPECT_TRUE(cycle.empty());
vector<vector<bool>> expected(
{{false, true, true}, {false, false, true}, {false, false, false}});
CheckMatrix(expected, conn);
}
TEST(DagConnectivityTest, SparseTransitive) {
vector<pair<int, int>> arc_list({{2, 5}, {5, 7}});
bool error = false;
vector<int> cycle;
vector<Bitset64<int64_t>> conn =
ComputeDagConnectivity(arc_list, &error, &cycle);
ASSERT_FALSE(error);
EXPECT_TRUE(cycle.empty());
vector<vector<bool>> expected(8, vector<bool>(8));
expected[2][5] = true;
expected[2][7] = true;
expected[5][7] = true;
CheckMatrix(expected, conn);
}
TEST(DagConnectivityTest, RealGraph) {
vector<pair<int, int>> arc_list;
arc_list.push_back({8, 0});
arc_list.push_back({1, 2});
arc_list.push_back({1, 3});
arc_list.push_back({3, 2});
arc_list.push_back({4, 3});
arc_list.push_back({4, 5});
arc_list.push_back({5, 2});
arc_list.push_back({7, 5});
arc_list.push_back({5, 6});
bool error = false;
vector<int> cycle;
vector<Bitset64<int64_t>> conn =
ComputeDagConnectivity(arc_list, &error, &cycle);
ASSERT_FALSE(error);
EXPECT_TRUE(cycle.empty());
vector<vector<bool>> expected(9, vector<bool>(9));
expected[1][2] = true;
expected[1][3] = true;
expected[3][2] = true;
expected[4][3] = true;
expected[4][2] = true;
expected[4][5] = true;
expected[4][6] = true;
expected[5][2] = true;
expected[5][6] = true;
expected[7][5] = true;
expected[7][2] = true;
expected[7][6] = true;
expected[8][0] = true;
CheckMatrix(expected, conn);
}
TEST(ComputeDagConnectivityOrDie, SimpleGraph) {
vector<pair<int, int>> arc_list({{0, 1}, {1, 2}});
vector<Bitset64<int64_t>> conn = ComputeDagConnectivityOrDie(arc_list);
vector<vector<bool>> expected(
{{false, true, true}, {false, false, true}, {false, false, false}});
CheckMatrix(expected, conn);
}
TEST(ComputeDagConnectivityOrDieDeathTest, SimpleCycleCausesDeath) {
vector<pair<int, int>> arc_list({{2, 3}, {3, 5}, {5, 2}});
EXPECT_DEATH(
{ ComputeDagConnectivityOrDie(arc_list); },
"Graph should have been acyclic but has cycle:");
}
} // namespace
} // namespace operations_research

View File

@@ -0,0 +1,95 @@
// 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_constrained_shortest_path.h"
#include <utility>
#include <vector>
#include "absl/log/check.h"
#include "absl/status/statusor.h"
#include "absl/types/span.h"
#include "ortools/graph/dag_shortest_path.h"
#include "ortools/graph/graph.h"
#include "ortools/graph/topologicalsorter.h"
namespace operations_research {
namespace {
void ApplyMapping(absl::Span<const int> mapping, std::vector<int>& values) {
if (!mapping.empty()) {
for (int i = 0; i < values.size(); ++i) {
values[i] = mapping[values[i]];
}
}
}
} // namespace
PathWithLength ConstrainedShortestPathsOnDag(
const int num_nodes,
absl::Span<const ArcWithLengthAndResources> arcs_with_length_and_resources,
int source, int destination, const std::vector<double>& max_resources) {
using GraphType = util::StaticGraph<>;
using NodeIndex = GraphType::NodeIndex;
using ArcIndex = GraphType::ArcIndex;
const int num_arcs = arcs_with_length_and_resources.size();
GraphType graph(num_nodes, num_arcs);
std::vector<double> arc_lengths;
arc_lengths.reserve(num_arcs);
std::vector<std::vector<double>> arc_resources(max_resources.size());
for (int i = 0; i < max_resources.size(); ++i) {
arc_resources[i].reserve(num_arcs);
}
for (const auto& arc : arcs_with_length_and_resources) {
graph.AddArc(arc.from, arc.to);
arc_lengths.push_back(arc.length);
for (int i = 0; i < arc.resources.size(); ++i) {
arc_resources[i].push_back(arc.resources[i]);
}
}
std::vector<ArcIndex> permutation;
graph.Build(&permutation);
util::Permute(permutation, &arc_lengths);
for (int i = 0; i < max_resources.size(); ++i) {
util::Permute(permutation, &arc_resources[i]);
}
std::vector<ArcIndex> inverse_permutation =
internal::GetInversePermutation(permutation);
const absl::StatusOr<std::vector<NodeIndex>> topological_order =
util::graph::FastTopologicalSort(graph);
CHECK_OK(topological_order) << "arcs_with_length form a cycle.";
std::vector<NodeIndex> sources = {source};
std::vector<NodeIndex> destinations = {destination};
ConstrainedShortestPathsOnDagWrapper<util::StaticGraph<>>
constrained_shortest_path_on_dag(&graph, &arc_lengths, &arc_resources,
*topological_order, sources,
destinations, &max_resources);
GraphPathWithLength<GraphType> path_with_length =
constrained_shortest_path_on_dag.RunConstrainedShortestPathOnDag();
ApplyMapping(inverse_permutation, path_with_length.arc_path);
return {.length = path_with_length.length,
.arc_path = std::move(path_with_length.arc_path),
.node_path = std::move(path_with_length.node_path)};
}
} // namespace operations_research

View File

@@ -0,0 +1,932 @@
// 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.
#ifndef OR_TOOLS_GRAPH_DAG_CONSTRAINED_SHORTEST_PATH_H_
#define OR_TOOLS_GRAPH_DAG_CONSTRAINED_SHORTEST_PATH_H_
#include <cmath>
#include <limits>
#include <vector>
#include "absl/algorithm/container.h"
#include "absl/base/log_severity.h"
#include "absl/log/check.h"
#include "absl/strings/str_format.h"
#include "absl/types/span.h"
#include "ortools/base/threadpool.h"
#include "ortools/graph/dag_shortest_path.h"
#include "ortools/graph/graph.h"
namespace operations_research {
// This library provides APIs to compute the constrained shortest path (CSP) on
// a given directed acyclic graph (DAG) with resources on each arc. A CSP is a
// shortest path on a DAG which does not exceed a set of maximum resources
// consumption. The algorithm is exponential and has no guarantee to finish. It
// is based on bi-drectionnal search. First is a forward pass from the source to
// nodes “somewhere in the middle” to generate forward labels, just as the
// onedirectional labeling algorithm we discussed; then a symmetric backward
// pass from the destination generates backward labels; and finally at each node
// with both forward and backward labels, it joins any pair of labels to form a
// feasible complete path. Intuitively, the number of labels grows exponentially
// with the number of arcs in the path. The overall number of labels are then
// expected to be smaller with shorter paths. For DAG with a topological
// ordering, we can pick any node (usually right in the middle) as a *midpoint*
// to stop each pass at. Then labels can be joined at only one half of the nodes
// by considering all edges between each half.
//
// In the DAG, multiple arcs between the same pair of nodes is allowed. However,
// self-loop arcs are not allowed.
//
// Note that we use the length formalism here, but the arc lengths can represent
// any numeric physical quantity. A shortest path will just be a path minimizing
// this quantity where the length/resources of a path is the sum of the
// length/resources of its arcs. An arc length can be negative, or +inf
// (indicating that it should not be used). An arc length cannot be -inf or nan.
//
// Resources on each arc must be non-negative and cannot be +inf or nan.
// -----------------------------------------------------------------------------
// Basic API.
// -----------------------------------------------------------------------------
// `tail` and `head` should both be in [0, num_nodes)
// If the length is +inf, then the arc is not used.
struct ArcWithLengthAndResources {
int from = 0;
int to = 0;
double length = 0.0;
std::vector<double> resources;
};
// Returns {+inf, {}, {}} if there is no path of finite length from the source
// to the destination. Dies if `arcs_with_length_and_resources` has a cycle.
PathWithLength ConstrainedShortestPathsOnDag(
int num_nodes,
absl::Span<const ArcWithLengthAndResources> arcs_with_length_and_resources,
int source, int destination, const std::vector<double>& max_resources);
// -----------------------------------------------------------------------------
// Advanced API.
// -----------------------------------------------------------------------------
template <class GraphType>
#if __cplusplus >= 202002L
requires DagGraphType<GraphType>
#endif
struct GraphPathWithLength {
double length = 0.0;
// The returned arc indices points into the `arcs_with_length` passed to the
// function below.
std::vector<typename GraphType::ArcIndex> arc_path;
std::vector<typename GraphType::NodeIndex>
node_path; // includes the source node.
};
// A wrapper that holds the memory needed to run many constrained shortest path
// computations efficiently on the given DAG (on which resources do not change).
// `GraphType` can use one of the interfaces defined in `util/graph/graph.h`.
template <class GraphType>
#if __cplusplus >= 202002L
requires DagGraphType<GraphType>
#endif
class ConstrainedShortestPathsOnDagWrapper {
public:
using NodeIndex = typename GraphType::NodeIndex;
using ArcIndex = typename GraphType::ArcIndex;
// IMPORTANT: All arguments must outlive the class.
//
// The vectors of `arc_lengths` and `arc_resources[i]` (for all resource i)
// *must* be of size `graph.num_arcs()` and indexed the same way as in
// `graph`. The vector `arc_resources` and `max_resources` *must* be of same
// size.
//
// You *must* provide a topological order. You can use
// `util::graph::FastTopologicalSort(graph)` to compute one if you don't
// already have one. An invalid topological order results in an upper bound
// for all shortest path computations. For maximum performance, you can
// further reindex the nodes under the topological order so that the memory
// access pattern is generally forward instead of random. For example, if the
// topological order for a graph with 4 nodes is [2,1,0,3], you can re-label
// the nodes 2, 1, and 0 to 0, 1, and 2 (and updates arcs accordingly).
//
// Validity of arcs and topological order are DCHECKed.
//
// If the number of labels in memory exceeds `max_num_created_labels / 2` at
// any point in each pass of the algorithm, new labels are not generated
// anymore and it returns the best path found so far, most particularly the
// empty path if none were found.
//
// IMPORTANT: You cannot modify anything except `arc_lengths` between calls to
// the `RunConstrainedShortestPathOnDag()` function.
ConstrainedShortestPathsOnDagWrapper(
const GraphType* graph, const std::vector<double>* arc_lengths,
const std::vector<std::vector<double>>* arc_resources,
absl::Span<const NodeIndex> topological_order,
absl::Span<const NodeIndex> sources,
absl::Span<const NodeIndex> destinations,
const std::vector<double>* max_resources,
int max_num_created_labels = 1e9);
// Returns {+inf, {}, {}} if there is no constrained path of finite length
// wihtin resources constraints from one node in `sources` to one node in
// `destinations`.
GraphPathWithLength<GraphType> RunConstrainedShortestPathOnDag();
// For benchmarking and informational purposes, returns the number of labels
// generated in the call of `RunConstrainedShortestPathOnDag()`.
int label_count() const {
return lengths_from_sources_[FORWARD].size() +
lengths_from_sources_[BACKWARD].size();
}
private:
enum Direction {
FORWARD = 0,
BACKWARD = 1,
};
inline static Direction Reverse(Direction d) {
return d == FORWARD ? BACKWARD : FORWARD;
}
// A LabelPair includes the `length` of a path that can be constructed by
// merging the paths from two *linkable* labels corresponding to
// `label_index`.
struct LabelPair {
double length = 0.0;
int label_index[2];
};
void RunHalfConstrainedShortestPathOnDag(
const GraphType& reverse_graph, absl::Span<const double> arc_lengths,
absl::Span<const std::vector<double>> arc_resources,
absl::Span<const std::vector<double>> min_arc_resources,
absl::Span<const double> max_resources, int max_num_created_labels,
std::vector<double>& lengths_from_sources,
std::vector<std::vector<double>>& resources_from_sources,
std::vector<ArcIndex>& incoming_arc_indices_from_sources,
std::vector<int>& incoming_label_indices_from_sources,
std::vector<int>& first_label, std::vector<int>& num_labels);
// Returns the arc index linking two nodes from each pass forming the best
// path. Returns -1 if no better path than the one found from
// `best_label_pair` is found.
ArcIndex MergeHalfRuns(
const GraphType& graph, absl::Span<const double> arc_lengths,
absl::Span<const std::vector<double>> arc_resources,
absl::Span<const double> max_resources,
const std::vector<NodeIndex> sub_node_indices[2],
const std::vector<double> lengths_from_sources[2],
const std::vector<std::vector<double>> resources_from_sources[2],
const std::vector<int> first_label[2],
const std::vector<int> num_labels[2], LabelPair& best_label_pair);
// Returns the path as list of arc indices that starts from a node in
// `sources` (if `direction` iS FORWARD) or `destinations` (if `direction` is
// BACKWARD) and ends in node represented by `best_label_index`.
std::vector<ArcIndex> ArcPathTo(
int best_label_index,
absl::Span<const ArcIndex> incoming_arc_indices_from_sources,
absl::Span<const int> incoming_label_indices_from_sources) const;
// Returns the list of all the nodes implied by a given `arc_path`.
std::vector<NodeIndex> NodePathImpliedBy(absl::Span<const ArcIndex> arc_path,
const GraphType& graph) const;
static constexpr double kTolerance = 1e-6;
const GraphType* const graph_;
const std::vector<double>* const arc_lengths_;
const std::vector<std::vector<double>>* const arc_resources_;
const std::vector<double>* const max_resources_;
absl::Span<const NodeIndex> sources_;
absl::Span<const NodeIndex> destinations_;
const int num_resources_;
// Data about *reachable* sub-graphs split in two for bidirectional search.
// Reachable nodes are nodes that can be reached given the resources
// constraints, i.e., for each resource, the sum of the minimum resource to
// get to a node from a node in `sources` and to get from a node to a node in
// `destinations` should be less than the maximum resource. Reachable arcs are
// arcs linking reachable nodes.
//
// `sub_reverse_graph_[dir]` is the reachable sub-graph split in *half* with
// an additional linked to sources (resp. destinations) for the forward (resp.
// backward) direction. For the forward (resp. backward) direction, nodes are
// indexed using the original (resp. reverse) topological order.
GraphType sub_reverse_graph_[2];
std::vector<std::vector<double>> sub_arc_resources_[2];
// `sub_full_arc_indices_[dir]` has size `sub_reverse_graph_[dir].num_arcs()`
// such that `sub_full_arc_indices_[dir][sub_arc] = arc` where `sub_arc` is
// the arc in the reachable sub-graph for direction `dir` (i.e.
// `sub_reverse_graph[dir]`) and `arc` is the arc in the original graph (i.e.
// `graph`).
std::vector<NodeIndex> sub_full_arc_indices_[2];
// `sub_node_indices_[dir]` has size `graph->num_nodes()` such that
// `sub_node_indices[dir][node] = sub_node` where `node` is the node in the
// original graph (i.e. `graph`) and `sub_node` is the node in the reachable
// sub-graph for direction `dir` (i.e. `sub_reverse_graph[dir]`) and -1 if
// `node` is not present in reachable sub-graph.
std::vector<NodeIndex> sub_node_indices_[2];
// `sub_is_source_[dir][sub_dir]` has size
// `sub_reverse_graph_[dir].num_nodes()` such that
// `sub_is_source_[dir][sub_dir][sub_node]` is true if `sub_node` is a node in
// the reachable sub-graph for direction `dir` (i.e. `sub_reverse_graph[dir]`)
// which is a source (resp. destination) is `sub_dir` is FORWARD (resp.
// BACKWARD).
std::vector<bool> sub_is_source_[2][2];
// `sub_min_arc_resources_[dir]` has size `max_resources->size()` and
// `sub_min_arc_resources_[dir][r]`, `sub_reverse_graph_[dir].num_nodes()`
// such that `sub_min_arc_resources_[dir][r][sub_node]` is the minimum of
// resource r needed to get to a destination (resp. come from a source) if
// `dir` is FORWARD (resp. BACKWARD).
std::vector<std::vector<double>> sub_min_arc_resources_[2];
// Maximum number of labels created for each sub-graph.
int max_num_created_labels_[2];
// Data about the last call of the RunConstrainedShortestPathOnDag()
// function. A path is only added to the following vectors if and only if
// it is feasible with respect to all resources.
// A Label includes the cumulative length, resources and the previous arc used
// in the path to get to this node.
// Instead of having a single vector of `Label` objects (cl/590819865), we
// split them into 3 vectors of more fundamental types as this improves
// push_back operations and memory release.
std::vector<double> lengths_from_sources_[2];
std::vector<std::vector<double>> resources_from_sources_[2];
std::vector<ArcIndex> incoming_arc_indices_from_sources_[2];
std::vector<int> incoming_label_indices_from_sources_[2];
std::vector<int> node_first_label_[2];
std::vector<int> node_num_labels_[2];
};
namespace internal {
template <typename T>
std::vector<T> GetInversePermutation(const std::vector<T>& permutation);
} // namespace internal
// -----------------------------------------------------------------------------
// Implementation.
// -----------------------------------------------------------------------------
template <class GraphType>
#if __cplusplus >= 202002L
requires DagGraphType<GraphType>
#endif
ConstrainedShortestPathsOnDagWrapper<GraphType>::
ConstrainedShortestPathsOnDagWrapper(
const GraphType* graph, const std::vector<double>* arc_lengths,
const std::vector<std::vector<double>>* arc_resources,
absl::Span<const NodeIndex> topological_order,
absl::Span<const NodeIndex> sources,
absl::Span<const NodeIndex> destinations,
const std::vector<double>* max_resources, int max_num_created_labels)
: graph_(graph),
arc_lengths_(arc_lengths),
arc_resources_(arc_resources),
max_resources_(max_resources),
sources_(sources),
destinations_(destinations),
num_resources_(max_resources->size()) {
CHECK(graph_ != nullptr);
CHECK(arc_lengths_ != nullptr);
CHECK(arc_resources_ != nullptr);
CHECK(!sources_.empty());
CHECK(!destinations_.empty());
CHECK(max_resources_ != nullptr);
CHECK(!max_resources_->empty())
<< "max_resources cannot be empty. Use "
"ortools/graph/dag_shortest_path.h instead";
if (DEBUG_MODE) {
CHECK_EQ(arc_lengths->size(), graph->num_arcs());
CHECK_EQ(arc_resources->size(), max_resources->size());
for (absl::Span<const double> arcs_resource : *arc_resources) {
CHECK_EQ(arcs_resource.size(), graph->num_arcs());
for (const double arc_resource : arcs_resource) {
CHECK(arc_resource >= 0 &&
arc_resource != std::numeric_limits<double>::infinity() &&
!std::isnan(arc_resource))
<< absl::StrFormat("resource cannot be negative nor +inf nor NaN");
}
}
for (const double arc_length : *arc_lengths) {
CHECK(arc_length != -std::numeric_limits<double>::infinity() &&
!std::isnan(arc_length))
<< absl::StrFormat("length cannot be -inf nor NaN");
}
CHECK_OK(TopologicalOrderIsValid(*graph, topological_order))
<< "Invalid topological order";
for (const double max_resource : *max_resources) {
CHECK(max_resource >= 0 &&
max_resource != std::numeric_limits<double>::infinity() &&
!std::isnan(max_resource))
<< absl::StrFormat(
"max_resource cannot be negative not +inf nor NaN");
}
std::vector<bool> is_source(graph->num_nodes(), false);
for (const NodeIndex source : sources) {
is_source[source] = true;
}
for (const NodeIndex destination : destinations) {
CHECK(!is_source[destination])
<< "A node cannot be both a source and destination";
}
}
// Full graphs.
const GraphType* full_graph[2];
const std::vector<std::vector<double>>* full_arc_resources[2];
absl::Span<const NodeIndex> full_topological_order[2];
absl::Span<const NodeIndex> full_sources[2];
// Forward.
const int num_nodes = graph->num_nodes();
const int num_arcs = graph->num_arcs();
full_graph[FORWARD] = graph;
full_arc_resources[FORWARD] = arc_resources;
full_topological_order[FORWARD] = topological_order;
full_sources[FORWARD] = sources;
// Backward.
GraphType full_backward_graph(num_nodes, num_arcs);
for (ArcIndex arc_index = 0; arc_index < num_arcs; ++arc_index) {
full_backward_graph.AddArc(graph->Head(arc_index), graph->Tail(arc_index));
}
std::vector<ArcIndex> full_permutation;
full_backward_graph.Build(&full_permutation);
const std::vector<ArcIndex> full_inverse_arc_indices =
internal::GetInversePermutation(full_permutation);
std::vector<std::vector<double>> backward_arc_resources(num_resources_);
for (int r = 0; r < num_resources_; ++r) {
backward_arc_resources[r] = (*arc_resources)[r];
util::Permute(full_permutation, &backward_arc_resources[r]);
}
std::vector<NodeIndex> full_backward_topological_order;
full_backward_topological_order.reserve(num_nodes);
for (int i = num_nodes - 1; i >= 0; --i) {
full_backward_topological_order.push_back(topological_order[i]);
}
full_graph[BACKWARD] = &full_backward_graph;
full_arc_resources[BACKWARD] = &backward_arc_resources;
full_topological_order[BACKWARD] = full_backward_topological_order;
full_sources[BACKWARD] = destinations;
// Get the minimum resources sources -> node and node -> destination for each
// node.
std::vector<std::vector<double>> full_min_arc_resources[2];
for (const Direction dir : {FORWARD, BACKWARD}) {
full_min_arc_resources[dir].reserve(num_resources_);
std::vector<double> full_arc_resource = full_arc_resources[dir]->front();
ShortestPathsOnDagWrapper<GraphType> shortest_paths_on_dag(
full_graph[dir], &full_arc_resource, full_topological_order[dir]);
for (int r = 0; r < num_resources_; ++r) {
full_arc_resource = (*(full_arc_resources[dir]))[r];
shortest_paths_on_dag.RunShortestPathOnDag(full_sources[dir]);
full_min_arc_resources[dir].push_back(shortest_paths_on_dag.LengthTo());
}
}
// Get reachable subgraph.
std::vector<bool> is_reachable(num_nodes, true);
std::vector<NodeIndex> sub_topological_order;
sub_topological_order.reserve(num_nodes);
for (const NodeIndex node_index : topological_order) {
for (int r = 0; r < num_resources_; ++r) {
if (full_min_arc_resources[FORWARD][r][node_index] +
full_min_arc_resources[BACKWARD][r][node_index] >
(*max_resources)[r]) {
is_reachable[node_index] = false;
break;
}
}
if (is_reachable[node_index]) {
sub_topological_order.push_back(node_index);
}
}
const int reachable_node_count = sub_topological_order.size();
// We split the number of labels evenly between each search (+1 for the
// additional source node).
max_num_created_labels_[BACKWARD] = max_num_created_labels / 2 + 1;
max_num_created_labels_[FORWARD] =
max_num_created_labels - max_num_created_labels / 2 + 1;
// Split sub-graphs and related information.
// The split is based on the number of paths. This is used as a simple proxy
// for the number of labels.
int mid_index = 0;
{
// We use double to avoid overflow. Note that this is an heuristic, so we
// don't care too much if we are not precise enough.
std::vector<double> path_count[2];
for (const Direction dir : {FORWARD, BACKWARD}) {
const GraphType& reverse_full_graph = *(full_graph[Reverse(dir)]);
path_count[dir].resize(num_nodes);
for (const NodeIndex source : full_sources[dir]) {
++path_count[dir][source];
}
for (const NodeIndex to : full_topological_order[dir]) {
if (!is_reachable[to]) continue;
for (const ArcIndex arc : reverse_full_graph.OutgoingArcs(to)) {
const NodeIndex from = reverse_full_graph.Head(arc);
if (!is_reachable[from]) continue;
path_count[dir][to] += path_count[dir][from];
}
}
}
for (const NodeIndex node_index : sub_topological_order) {
if (path_count[FORWARD][node_index] > path_count[BACKWARD][node_index]) {
break;
}
++mid_index;
}
if (mid_index == reachable_node_count) {
mid_index = reachable_node_count / 2;
}
}
for (const Direction dir : {FORWARD, BACKWARD}) {
absl::Span<const NodeIndex> const sub_nodes =
dir == FORWARD
? absl::MakeSpan(sub_topological_order).subspan(0, mid_index)
: absl::MakeSpan(sub_topological_order)
.subspan(mid_index, reachable_node_count - mid_index);
sub_node_indices_[dir].assign(num_nodes, -1);
sub_min_arc_resources_[dir].resize(num_resources_);
for (int r = 0; r < num_resources_; ++r) {
sub_min_arc_resources_[dir][r].resize(sub_nodes.size());
}
for (NodeIndex i = 0; i < sub_nodes.size(); ++i) {
const NodeIndex sub_node_index =
dir == FORWARD ? i : sub_nodes.size() - 1 - i;
sub_node_indices_[dir][sub_nodes[i]] = sub_node_index;
for (int r = 0; r < num_resources_; ++r) {
sub_min_arc_resources_[dir][r][sub_node_index] =
full_min_arc_resources[Reverse(dir)][r][sub_nodes[i]];
}
}
// IMPORTANT: The sub-graph has an additional node linked to sources (resp.
// destinations) for the forward (resp. backward) direction. This additional
// node is indexed with the last index. All added arcs are given to have an
// arc index in the original graph of -1.
const int sub_arcs_count = num_arcs + full_sources[dir].size();
sub_reverse_graph_[dir] = GraphType(sub_nodes.size() + 1, sub_arcs_count);
sub_arc_resources_[dir].resize(num_resources_);
for (int r = 0; r < num_resources_; ++r) {
sub_arc_resources_[dir][r].reserve(sub_arcs_count);
}
sub_full_arc_indices_[dir].reserve(sub_arcs_count);
const GraphType& reverse_full_graph = *(full_graph[Reverse(dir)]);
for (ArcIndex arc_index = 0; arc_index < num_arcs; ++arc_index) {
const NodeIndex from =
sub_node_indices_[dir][reverse_full_graph.Tail(arc_index)];
const NodeIndex to =
sub_node_indices_[dir][reverse_full_graph.Head(arc_index)];
if (from == -1 || to == -1) {
continue;
}
sub_reverse_graph_[dir].AddArc(from, to);
ArcIndex sub_full_arc_index;
if (dir == FORWARD && !full_inverse_arc_indices.empty()) {
sub_full_arc_index = full_inverse_arc_indices[arc_index];
} else {
sub_full_arc_index = arc_index;
}
for (int r = 0; r < num_resources_; ++r) {
sub_arc_resources_[dir][r].push_back(
(*arc_resources_)[r][sub_full_arc_index]);
}
sub_full_arc_indices_[dir].push_back(sub_full_arc_index);
}
for (const NodeIndex source : full_sources[dir]) {
const NodeIndex sub_source = sub_node_indices_[dir][source];
if (sub_source == -1) {
continue;
}
sub_reverse_graph_[dir].AddArc(sub_source, sub_nodes.size());
for (int r = 0; r < num_resources_; ++r) {
sub_arc_resources_[dir][r].push_back(0.0);
}
sub_full_arc_indices_[dir].push_back(-1);
}
std::vector<ArcIndex> sub_permutation;
sub_reverse_graph_[dir].Build(&sub_permutation);
for (int r = 0; r < num_resources_; ++r) {
util::Permute(sub_permutation, &sub_arc_resources_[dir][r]);
}
util::Permute(sub_permutation, &sub_full_arc_indices_[dir]);
}
// Memory allocation is done here and only once in order to avoid
// reallocation at each call of `RunConstrainedShortestPathOnDag()` for
// better performance.
for (const Direction dir : {FORWARD, BACKWARD}) {
resources_from_sources_[dir].resize(num_resources_);
node_first_label_[dir].resize(sub_reverse_graph_[dir].size());
node_num_labels_[dir].resize(sub_reverse_graph_[dir].size());
}
}
template <class GraphType>
#if __cplusplus >= 202002L
requires DagGraphType<GraphType>
#endif
GraphPathWithLength<GraphType> ConstrainedShortestPathsOnDagWrapper<
GraphType>::RunConstrainedShortestPathOnDag() {
// Assign lengths on sub-relevant graphs.
std::vector<double> sub_arc_lengths[2];
for (const Direction dir : {FORWARD, BACKWARD}) {
sub_arc_lengths[dir].reserve(sub_reverse_graph_[dir].num_arcs());
for (ArcIndex sub_arc_index = 0;
sub_arc_index < sub_reverse_graph_[dir].num_arcs(); ++sub_arc_index) {
const ArcIndex arc_index = sub_full_arc_indices_[dir][sub_arc_index];
if (arc_index == -1) {
sub_arc_lengths[dir].push_back(0.0);
continue;
}
sub_arc_lengths[dir].push_back((*arc_lengths_)[arc_index]);
}
}
{
ThreadPool search_threads(2);
search_threads.StartWorkers();
for (const Direction dir : {FORWARD, BACKWARD}) {
search_threads.Schedule([this, dir, &sub_arc_lengths]() {
RunHalfConstrainedShortestPathOnDag(
/*reverse_graph=*/sub_reverse_graph_[dir],
/*arc_lengths=*/sub_arc_lengths[dir],
/*arc_resources=*/sub_arc_resources_[dir],
/*min_arc_resources=*/sub_min_arc_resources_[dir],
/*max_resources=*/*max_resources_,
/*max_num_created_labels=*/max_num_created_labels_[dir],
/*lengths_from_sources=*/lengths_from_sources_[dir],
/*resources_from_sources=*/resources_from_sources_[dir],
/*incoming_arc_indices_from_sources=*/
incoming_arc_indices_from_sources_[dir],
/*incoming_label_indices_from_sources=*/
incoming_label_indices_from_sources_[dir],
/*first_label=*/node_first_label_[dir],
/*num_labels=*/node_num_labels_[dir]);
});
}
}
// Check destinations within relevant half sub-graphs.
LabelPair best_label_pair = {
.length = std::numeric_limits<double>::infinity(),
.label_index = {-1, -1}};
for (const Direction dir : {FORWARD, BACKWARD}) {
absl::Span<const NodeIndex> destinations =
dir == FORWARD ? destinations_ : sources_;
for (const NodeIndex dst : destinations) {
const NodeIndex sub_dst = sub_node_indices_[dir][dst];
if (sub_dst == -1) {
continue;
}
const int num_labels_dst = node_num_labels_[dir][sub_dst];
if (num_labels_dst == 0) {
continue;
}
const int first_label_dst = node_first_label_[dir][sub_dst];
for (int label_index = first_label_dst;
label_index < first_label_dst + num_labels_dst; ++label_index) {
const double length_dst = lengths_from_sources_[dir][label_index];
if (length_dst < best_label_pair.length) {
best_label_pair.length = length_dst;
best_label_pair.label_index[dir] = label_index;
}
}
}
}
const ArcIndex merging_arc_index = MergeHalfRuns(
/*graph=*/*graph_, /*arc_lengths=*/*arc_lengths_,
/*arc_resources=*/*arc_resources_,
/*max_resources=*/*max_resources_,
/*sub_node_indices=*/sub_node_indices_,
/*lengths_from_sources=*/lengths_from_sources_,
/*resources_from_sources=*/resources_from_sources_,
/*first_label=*/node_first_label_,
/*num_labels=*/node_num_labels_, /*best_label_pair=*/best_label_pair);
std::vector<ArcIndex> arc_path;
for (const Direction dir : {FORWARD, BACKWARD}) {
for (const ArcIndex sub_arc_index : ArcPathTo(
/*best_label_index=*/best_label_pair.label_index[dir],
/*incoming_arc_indices_from_sources=*/
incoming_arc_indices_from_sources_[dir],
/*incoming_label_indices_from_sources=*/
incoming_label_indices_from_sources_[dir])) {
const ArcIndex arc_index = sub_full_arc_indices_[dir][sub_arc_index];
if (arc_index == -1) {
break;
}
arc_path.push_back(arc_index);
}
if (dir == FORWARD && merging_arc_index != -1) {
absl::c_reverse(arc_path);
arc_path.push_back(merging_arc_index);
}
}
// Clear all labels from the next run.
for (const Direction dir : {FORWARD, BACKWARD}) {
lengths_from_sources_[dir].clear();
for (int r = 0; r < num_resources_; ++r) {
resources_from_sources_[dir][r].clear();
}
incoming_arc_indices_from_sources_[dir].clear();
incoming_label_indices_from_sources_[dir].clear();
}
return {.length = best_label_pair.length,
.arc_path = arc_path,
.node_path = NodePathImpliedBy(arc_path, *graph_)};
}
template <class GraphType>
#if __cplusplus >= 202002L
requires DagGraphType<GraphType>
#endif
void ConstrainedShortestPathsOnDagWrapper<GraphType>::
RunHalfConstrainedShortestPathOnDag(
const GraphType& reverse_graph, absl::Span<const double> arc_lengths,
absl::Span<const std::vector<double>> arc_resources,
absl::Span<const std::vector<double>> min_arc_resources,
absl::Span<const double> max_resources,
const int max_num_created_labels,
std::vector<double>& lengths_from_sources,
std::vector<std::vector<double>>& resources_from_sources,
std::vector<ArcIndex>& incoming_arc_indices_from_sources,
std::vector<int>& incoming_label_indices_from_sources,
std::vector<int>& first_label, std::vector<int>& num_labels) {
// Initialize source node.
const NodeIndex source_node = reverse_graph.num_nodes() - 1;
first_label[source_node] = 0;
num_labels[source_node] = 1;
lengths_from_sources.push_back(0);
for (int r = 0; r < num_resources_; ++r) {
resources_from_sources[r].push_back(0);
}
incoming_arc_indices_from_sources.push_back(-1);
incoming_label_indices_from_sources.push_back(-1);
std::vector<double> lengths_to;
std::vector<std::vector<double>> resources_to(num_resources_);
std::vector<ArcIndex> incoming_arc_indices_to;
std::vector<int> incoming_label_indices_to;
std::vector<int> label_indices_to;
std::vector<double> resources(num_resources_);
for (NodeIndex to = 0; to < source_node; ++to) {
lengths_to.clear();
for (int r = 0; r < num_resources_; ++r) {
resources_to[r].clear();
}
incoming_arc_indices_to.clear();
incoming_label_indices_to.clear();
for (const ArcIndex reverse_arc_index : reverse_graph.OutgoingArcs(to)) {
const NodeIndex from = reverse_graph.Head(reverse_arc_index);
const double arc_length = arc_lengths[reverse_arc_index];
DCHECK(arc_length != -std::numeric_limits<double>::infinity());
if (arc_length == std::numeric_limits<double>::infinity()) {
continue;
}
for (int label_index = first_label[from];
label_index < first_label[from] + num_labels[from]; ++label_index) {
bool path_is_feasible = true;
for (int r = 0; r < num_resources_; ++r) {
DCHECK_GE(arc_resources[r][reverse_arc_index], 0.0);
resources[r] = resources_from_sources[r][label_index] +
arc_resources[r][reverse_arc_index];
if (resources[r] + min_arc_resources[r][to] > max_resources[r]) {
path_is_feasible = false;
break;
}
}
if (!path_is_feasible) {
continue;
}
lengths_to.push_back(lengths_from_sources[label_index] + arc_length);
for (int r = 0; r < num_resources_; ++r) {
resources_to[r].push_back(resources[r]);
}
incoming_arc_indices_to.push_back(reverse_arc_index);
incoming_label_indices_to.push_back(label_index);
}
}
// Sort labels lexicographically with lengths then resources.
label_indices_to.clear();
label_indices_to.reserve(lengths_to.size());
for (int i = 0; i < lengths_to.size(); ++i) {
label_indices_to.push_back(i);
}
absl::c_sort(label_indices_to, [&](const int i, const int j) {
if (lengths_to[i] < lengths_to[j]) return true;
if (lengths_to[i] > lengths_to[j]) return false;
for (int r = 0; r < num_resources_; ++r) {
if (resources_to[r][i] < resources_to[r][j]) return true;
if (resources_to[r][i] > resources_to[r][j]) return false;
}
return i < j;
});
first_label[to] = lengths_from_sources.size();
int& num_labels_to = num_labels[to];
// Reset the number of labels to zero otherwise it holds the previous run
// result.
num_labels_to = 0;
for (int i = 0; i < label_indices_to.size(); ++i) {
// Check if label "i" on node `to` is dominated by any other label.
const int label_i_index = label_indices_to[i];
bool label_i_is_dominated = false;
for (int j = 0; j < i - 1; ++j) {
const int label_j_index = label_indices_to[j];
if (lengths_to[label_i_index] <= lengths_to[label_j_index]) continue;
bool label_j_dominates_label_i = true;
for (int r = 0; r < num_resources_; ++r) {
if (resources_to[r][label_i_index] <=
resources_to[r][label_j_index]) {
label_j_dominates_label_i = false;
break;
}
}
if (label_j_dominates_label_i) {
label_i_is_dominated = true;
break;
}
}
if (label_i_is_dominated) continue;
lengths_from_sources.push_back(lengths_to[label_i_index]);
for (int r = 0; r < num_resources_; ++r) {
resources_from_sources[r].push_back(resources_to[r][label_i_index]);
}
incoming_arc_indices_from_sources.push_back(
incoming_arc_indices_to[label_i_index]);
incoming_label_indices_from_sources.push_back(
incoming_label_indices_to[label_i_index]);
++num_labels_to;
if (lengths_from_sources.size() >= max_num_created_labels) {
return;
}
}
}
}
template <class GraphType>
#if __cplusplus >= 202002L
requires DagGraphType<GraphType>
#endif
typename GraphType::ArcIndex
ConstrainedShortestPathsOnDagWrapper<GraphType>::MergeHalfRuns(
const GraphType& graph, absl::Span<const double> arc_lengths,
absl::Span<const std::vector<double>> arc_resources,
absl::Span<const double> max_resources,
const std::vector<NodeIndex> sub_node_indices[2],
const std::vector<double> lengths_from_sources[2],
const std::vector<std::vector<double>> resources_from_sources[2],
const std::vector<int> first_label[2], const std::vector<int> num_labels[2],
LabelPair& best_label_pair) {
const std::vector<NodeIndex>& forward_sub_node_indices =
sub_node_indices[FORWARD];
absl::Span<const double> forward_lengths = lengths_from_sources[FORWARD];
const std::vector<std::vector<double>>& forward_resources =
resources_from_sources[FORWARD];
absl::Span<const int> forward_first_label = first_label[FORWARD];
absl::Span<const int> forward_num_labels = num_labels[FORWARD];
const std::vector<NodeIndex>& backward_sub_node_indices =
sub_node_indices[BACKWARD];
absl::Span<const double> backward_lengths = lengths_from_sources[BACKWARD];
const std::vector<std::vector<double>>& backward_resources =
resources_from_sources[BACKWARD];
absl::Span<const int> backward_first_label = first_label[BACKWARD];
absl::Span<const int> backward_num_labels = num_labels[BACKWARD];
ArcIndex merging_arc_index = -1;
for (ArcIndex arc_index = 0; arc_index < graph.num_arcs(); ++arc_index) {
const NodeIndex sub_from = forward_sub_node_indices[graph.Tail(arc_index)];
if (sub_from == -1) {
continue;
}
const NodeIndex sub_to = backward_sub_node_indices[graph.Head(arc_index)];
if (sub_to == -1) {
continue;
}
const int num_labels_from = forward_num_labels[sub_from];
if (num_labels_from == 0) {
continue;
}
const int num_labels_to = backward_num_labels[sub_to];
if (num_labels_to == 0) {
continue;
}
const double arc_length = arc_lengths[arc_index];
DCHECK(arc_length != -std::numeric_limits<double>::infinity());
if (arc_length == std::numeric_limits<double>::infinity()) {
continue;
}
const int first_label_from = forward_first_label[sub_from];
const int first_label_to = backward_first_label[sub_to];
for (int label_to_index = first_label_to;
label_to_index < first_label_to + num_labels_to; ++label_to_index) {
const double length_to = backward_lengths[label_to_index];
if (arc_length + length_to >= best_label_pair.length) {
continue;
}
for (int label_from_index = first_label_from;
label_from_index < first_label_from + num_labels_from;
++label_from_index) {
const double length_from = forward_lengths[label_from_index];
if (length_from + arc_length + length_to >= best_label_pair.length) {
continue;
}
bool path_is_feasible = true;
for (int r = 0; r < num_resources_; ++r) {
DCHECK_GE(arc_resources[r][arc_index], 0.0);
if (forward_resources[r][label_from_index] +
arc_resources[r][arc_index] +
backward_resources[r][label_to_index] >
max_resources[r]) {
path_is_feasible = false;
break;
}
}
if (!path_is_feasible) {
continue;
}
best_label_pair.length = length_from + arc_length + length_to;
best_label_pair.label_index[FORWARD] = label_from_index;
best_label_pair.label_index[BACKWARD] = label_to_index;
merging_arc_index = arc_index;
}
}
}
return merging_arc_index;
}
template <class GraphType>
#if __cplusplus >= 202002L
requires DagGraphType<GraphType>
#endif
std::vector<typename GraphType::ArcIndex>
ConstrainedShortestPathsOnDagWrapper<GraphType>::ArcPathTo(
const int best_label_index,
absl::Span<const ArcIndex> incoming_arc_indices_from_sources,
absl::Span<const int> incoming_label_indices_from_sources) const {
int current_label_index = best_label_index;
std::vector<ArcIndex> arc_path;
for (int i = 0; i < graph_->num_nodes(); ++i) {
if (current_label_index == -1) {
break;
}
arc_path.push_back(incoming_arc_indices_from_sources[current_label_index]);
current_label_index =
incoming_label_indices_from_sources[current_label_index];
}
return arc_path;
}
template <typename GraphType>
#if __cplusplus >= 202002L
requires DagGraphType<GraphType>
#endif
std::vector<typename GraphType::NodeIndex>
ConstrainedShortestPathsOnDagWrapper<GraphType>::NodePathImpliedBy(
absl::Span<const ArcIndex> arc_path, const GraphType& graph) const {
if (arc_path.empty()) {
return {};
}
std::vector<NodeIndex> node_path;
node_path.reserve(arc_path.size() + 1);
for (const ArcIndex arc_index : arc_path) {
node_path.push_back(graph.Tail(arc_index));
}
node_path.push_back(graph.Head(arc_path.back()));
return node_path;
}
namespace internal {
template <typename T>
std::vector<T> GetInversePermutation(const std::vector<T>& permutation) {
std::vector<T> inverse_permutation(permutation.size());
if (!permutation.empty()) {
for (T i = 0; i < permutation.size(); ++i) {
inverse_permutation[permutation[i]] = i;
}
}
return inverse_permutation;
}
} // namespace internal
} // namespace operations_research
#endif // OR_TOOLS_GRAPH_DAG_CONSTRAINED_SHORTEST_PATH_H_

View File

@@ -0,0 +1,855 @@
// 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_constrained_shortest_path.h"
#include <algorithm>
#include <cstdint>
#include <limits>
#include <utility>
#include <vector>
#include "absl/algorithm/container.h"
#include "absl/log/check.h"
#include "absl/random/random.h"
#include "absl/strings/str_cat.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/graph/graph.h"
#include "ortools/graph/graph_io.h"
#include "ortools/math_opt/cpp/math_opt.h"
namespace operations_research {
namespace {
constexpr double kInf = std::numeric_limits<double>::infinity();
using ::testing::ElementsAre;
using ::testing::FieldsAre;
using ::testing::IsEmpty;
TEST(GetInversePermutationTest, EmptyPermutation) {
EXPECT_THAT(internal::GetInversePermutation<int>({}), IsEmpty());
}
TEST(GetInversePermutationTest, SingleElementPermutation) {
EXPECT_THAT(internal::GetInversePermutation<int>({0}), ElementsAre(0));
}
TEST(GetInversePermutationTest, ThreeElementPermutation) {
EXPECT_THAT(internal::GetInversePermutation<int>({1, 2, 0}),
ElementsAre(2, 0, 1));
}
TEST(GetInversePermutationTest, RandomPermutation) {
const int num_tests = 100;
const int max_size = 1000;
absl::BitGen bitgen;
for (int unused = 0; unused < num_tests; ++unused) {
const int num_elements = absl::Uniform<int>(bitgen, 0, max_size);
std::vector<int> permutation(num_elements);
absl::c_iota(permutation, 0);
absl::c_shuffle(permutation, bitgen);
const std::vector<int> inverse_permutation =
internal::GetInversePermutation<int>(permutation);
for (int i = 0; i < num_elements; ++i) {
EXPECT_EQ(inverse_permutation[permutation[i]], i);
}
}
}
TEST(ConstrainedShortestPathOnDagTest, 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<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{source, a, 5.0, {6.0}},
{source, b, 2.0, {4.0}},
{a, destination, 3.0, {2.0}},
{b, destination, 20.0, {3.0}}};
EXPECT_THAT(ConstrainedShortestPathsOnDag(
num_nodes, arcs_with_length_and_resources, source,
destination, /*max_resources=*/{7.0}),
FieldsAre(/*length=*/22.0, /*arc_path=*/ElementsAre(1, 3),
/*node_path=*/ElementsAre(source, b, destination)));
}
TEST(ConstrainedShortestPathOnDagTest, SimpleGraphTwoPaths) {
const int source = 0;
const int destination = 1;
const int a = 2;
const int b = 3;
const int num_nodes = 4;
const std::vector<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{source, a, 5.0, {2.0}},
{source, b, 2.0, {1.0}},
{a, destination, 3.0, {1.0}},
{b, destination, 20.0, {1.0}}};
EXPECT_THAT(ConstrainedShortestPathsOnDag(
num_nodes, arcs_with_length_and_resources, source,
destination, /*max_resources=*/{6.0}),
FieldsAre(/*length=*/8.0, /*arc_path=*/ElementsAre(0, 2),
/*node_path=*/ElementsAre(source, a, destination)));
}
TEST(ConstrainedShortestPathOnDagTest, 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<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{a, c, 5.0, {5.0}}, {source, b, 7.0, {4.0}},
{a, b, 1.0, {3.0}}, {source, a, 3.0, {4.0}},
{c, destination, 5.0, {1.0}}, {b, destination, -2.0, {1.0}}};
EXPECT_THAT(ConstrainedShortestPathsOnDag(
num_nodes, arcs_with_length_and_resources, source,
destination, /*max_resources=*/{6.0}),
FieldsAre(/*length=*/5.0, /*arc_path=*/ElementsAre(1, 5),
/*node_path=*/ElementsAre(source, b, destination)));
}
TEST(ConstrainedShortestPathOnDagTest, LargerGraphWithDominance) {
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<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{a, c, 5.0, {1.0}}, {source, b, 1.0, {3.0}},
{a, b, 7.0, {4.0}}, {source, a, 3.0, {4.0}},
{c, destination, 5.0, {1.0}}, {b, destination, -2.0, {1.0}}};
EXPECT_THAT(ConstrainedShortestPathsOnDag(
num_nodes, arcs_with_length_and_resources, source,
destination, /*max_resources=*/{6.0}),
FieldsAre(/*length=*/-1.0, /*arc_path=*/ElementsAre(1, 5),
/*node_path=*/ElementsAre(source, b, destination)));
}
TEST(ConstrainedShortestPathOnDagTest, LargerGraphNoMaximumDuration) {
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<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{a, c, 5.0, {1.0}}, {source, b, 7.0, {4.0}},
{a, b, 1.0, {3.0}}, {source, a, 3.0, {4.0}},
{c, destination, 5.0, {1.0}}, {b, destination, -2.0, {1.0}}};
EXPECT_THAT(
ConstrainedShortestPathsOnDag(
num_nodes, arcs_with_length_and_resources, source, destination,
/*max_resources=*/{std::numeric_limits<double>::max()}),
FieldsAre(/*length=*/2.0, /*arc_path=*/ElementsAre(3, 2, 5),
/*node_path=*/ElementsAre(source, a, b, destination)));
}
TEST(ConstrainedShortestPathOnDagTest, GraphWithInefficientEdge) {
const int source = 0;
const int a = 1;
const int destination = 2;
const int num_nodes = 3;
const std::vector<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{source, a, 3.0, {4.0}},
{source, destination, 9.0, {6.0}},
{a, destination, 5.0, {1.0}}};
EXPECT_THAT(
ConstrainedShortestPathsOnDag(num_nodes, arcs_with_length_and_resources,
source, destination,
/*max_resources=*/{6.0}),
FieldsAre(/*length=*/8.0, /*arc_path=*/ElementsAre(0, 2),
/*node_path=*/ElementsAre(source, a, destination)));
}
TEST(ConstrainedShortestPathOnDagTest, NoResources) {
const int source = 0;
const int destination = 1;
const int a = 2;
const int num_nodes = 3;
const std::vector<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{source, a, 5.0, {}}, {a, destination, 3.0, {}}};
EXPECT_DEATH(
ConstrainedShortestPathsOnDag(num_nodes, arcs_with_length_and_resources,
source, destination,
/*max_resources=*/{}),
"ortools/graph/dag_shortest_path.h");
}
TEST(ConstrainedShortestPathOnDagTest, Cycle) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
const std::vector<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{source, destination, 1.0, {0.0}}, {destination, source, 2.0, {1.0}}};
EXPECT_DEATH(
ConstrainedShortestPathsOnDag(num_nodes, arcs_with_length_and_resources,
source, destination,
/*max_resources=*/{0.0}),
"cycle");
}
TEST(ConstrainedShortestPathOnDagTest, SetsNotConnected) {
const int source = 0;
const int destination = 1;
const int a = 2;
const int num_nodes = 3;
const std::vector<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{source, a, 1.0, {0.0}}};
EXPECT_THAT(ConstrainedShortestPathsOnDag(
num_nodes, arcs_with_length_and_resources, source,
destination, /*max_resources=*/{0.0}),
FieldsAre(/*length=*/kInf,
/*arc_path=*/IsEmpty(),
/*node_path=*/IsEmpty()));
}
TEST(ConstrainedShortestPathOnDagTest, SetsNotConnectedDueToInfiniteCost) {
const int source = 2;
const int destination = 0;
const int a = 1;
const int num_nodes = 3;
const std::vector<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{a, destination, 1.0, {0.0}}, {source, a, kInf, {0.0}}};
EXPECT_THAT(ConstrainedShortestPathsOnDag(
num_nodes, arcs_with_length_and_resources, source,
destination, /*max_resources=*/{0.0}),
FieldsAre(/*length=*/kInf,
/*arc_path=*/IsEmpty(),
/*node_path=*/IsEmpty()));
}
TEST(ConstrainedShortestPathOnDagTest, SetsNotConnectedDueToLackOfResources) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
const std::vector<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{source, destination, 1.0, {1.0, 8.0}},
{source, destination, 2.0, {7.0, 2.0}},
{source, destination, 3.0, {6.0, 3.0}}};
EXPECT_THAT(ConstrainedShortestPathsOnDag(
num_nodes, arcs_with_length_and_resources, source,
destination, /*max_resources=*/{4.0, 4.0}),
FieldsAre(/*length=*/kInf,
/*arc_path=*/IsEmpty(),
/*node_path=*/IsEmpty()));
}
TEST(ConstrainedShortestPathOnDagTest, MultipleArcs) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
const std::vector<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{source, destination, 4.0, {2.0}}, {source, destination, 2.0, {4.0}}};
EXPECT_THAT(ConstrainedShortestPathsOnDag(
num_nodes, arcs_with_length_and_resources, source,
destination, /*max_resources=*/{3.0}),
FieldsAre(/*length=*/4.0, /*arc_path=*/ElementsAre(0),
/*node_path=*/ElementsAre(source, destination)));
}
TEST(ConstrainedShortestPathOnDagTest, UpdateArcsLengthAndResources) {
const int source = 0;
const int destination = 1;
const int a = 2;
const int b = 3;
const int num_nodes = 4;
std::vector<ArcWithLengthAndResources> arcs_with_length_and_resources = {
{source, a, 5.0, {1.0, 3.0}},
{source, b, 2.0, {4.0, 10.0}},
{a, destination, 3.0, {5.0, 9.0}},
{b, destination, 20.0, {2.0, 2.0}}};
const std::vector<double> max_resources = {6.0, 12.0};
EXPECT_THAT(
ConstrainedShortestPathsOnDag(num_nodes, arcs_with_length_and_resources,
source, destination, max_resources),
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_and_resources[3].length = -1.0;
EXPECT_THAT(
ConstrainedShortestPathsOnDag(num_nodes, arcs_with_length_and_resources,
source, destination, max_resources),
FieldsAre(/*length=*/1.0, /*arc_path=*/ElementsAre(1, 3),
/*node_path=*/ElementsAre(source, b, destination)));
// Update the first resource of arc source -> b from 4.0 to 5.0 making
// the path source -> b -> destination infeasible.
arcs_with_length_and_resources[1].resources[0] = 5.0;
EXPECT_THAT(
ConstrainedShortestPathsOnDag(num_nodes, arcs_with_length_and_resources,
source, destination, max_resources),
FieldsAre(/*length=*/8.0, /*arc_path=*/ElementsAre(0, 2),
/*node_path=*/ElementsAre(source, a, destination)));
}
TEST(ConstrainedShortestPathsOnDagWrapperTest,
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;
std::vector<std::vector<double>> arc_resources(1);
graph.AddArc(source_1, source_2);
arc_lengths.push_back(-7.0);
arc_resources[0].push_back(2.0);
graph.AddArc(source_2, destination);
arc_lengths.push_back(3.0);
arc_resources[0].push_back(3.0);
const std::vector<int> topological_order = {source_1, source_2, destination};
const std::vector<int> sources = {source_1, source_2};
const std::vector<int> destinations = {destination};
const std::vector<double> max_resources = {6.0};
ConstrainedShortestPathsOnDagWrapper<util::ListGraph<>>
constrained_shortest_path_on_dag(&graph, &arc_lengths, &arc_resources,
topological_order, sources, destinations,
&max_resources);
EXPECT_THAT(
constrained_shortest_path_on_dag.RunConstrainedShortestPathOnDag(),
FieldsAre(/*length=*/-4.0, /*arc_path=*/ElementsAre(0, 1),
/*node_path=*/ElementsAre(source_1, source_2, destination)));
}
TEST(ConstrainedShortestPathsOnDagWrapperTest, 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;
std::vector<std::vector<double>> arc_resources(1);
graph.AddArc(source, destination_1);
arc_lengths.push_back(3.0);
arc_resources[0].push_back(5.0);
graph.AddArc(source, destination_2);
arc_lengths.push_back(1.0);
arc_resources[0].push_back(7.0);
graph.AddArc(source, destination_3);
arc_lengths.push_back(2.0);
arc_resources[0].push_back(6.0);
const std::vector<int> sources = {source};
const std::vector<int> destinations = {destination_1, destination_2,
destination_3};
const std::vector<int> topological_order = {source, destination_3,
destination_1, destination_2};
const std::vector<double> max_resources = {6.0};
ConstrainedShortestPathsOnDagWrapper<util::ListGraph<>>
constrained_shortest_path_on_dag(&graph, &arc_lengths, &arc_resources,
topological_order, sources, destinations,
&max_resources);
EXPECT_THAT(
constrained_shortest_path_on_dag.RunConstrainedShortestPathOnDag(),
FieldsAre(/*length=*/2.0, /*arc_path=*/ElementsAre(2),
/*node_path=*/ElementsAre(source, destination_3)));
}
TEST(ConstrainedShortestPathsOnDagWrapperTest, UpdateArcsLength) {
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;
std::vector<std::vector<double>> arc_resources(2);
graph.AddArc(source, a);
arc_lengths.push_back(5.0);
arc_resources[0].push_back(1.0);
arc_resources[1].push_back(3.0);
graph.AddArc(source, b);
arc_lengths.push_back(2.0);
arc_resources[0].push_back(4.0);
arc_resources[1].push_back(10.0);
graph.AddArc(a, destination);
arc_lengths.push_back(3.0);
arc_resources[0].push_back(5.0);
arc_resources[1].push_back(9.0);
graph.AddArc(b, destination);
arc_lengths.push_back(20.0);
arc_resources[0].push_back(2.0);
arc_resources[1].push_back(2.0);
const std::vector<int> topological_order = {source, a, b, destination};
const std::vector<int> sources = {source};
const std::vector<int> destinations = {destination};
const std::vector<double> max_resources = {6.0, 12.0};
ConstrainedShortestPathsOnDagWrapper<util::ListGraph<>>
constrained_shortest_path_on_dag(&graph, &arc_lengths, &arc_resources,
topological_order, sources, destinations,
&max_resources);
EXPECT_THAT(
constrained_shortest_path_on_dag.RunConstrainedShortestPathOnDag(),
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.
arc_lengths[3] = -1.0;
EXPECT_THAT(
constrained_shortest_path_on_dag.RunConstrainedShortestPathOnDag(),
FieldsAre(/*length=*/1.0, /*arc_path=*/ElementsAre(1, 3),
/*node_path=*/ElementsAre(source, b, destination)));
}
TEST(ConstrainedShortestPathsOnDagWrapperTest, LimitMaximumNumberOfLabels) {
const int source = 0;
const int destination = 1;
const int a = 2;
const int num_nodes = 3;
util::ListGraph<> graph(num_nodes, /*arc_capacity=*/2);
std::vector<double> arc_lengths;
std::vector<std::vector<double>> arc_resources(1);
graph.AddArc(source, a);
arc_lengths.push_back(5.0);
arc_resources[0].push_back(2.0);
graph.AddArc(a, destination);
arc_lengths.push_back(-2.0);
arc_resources[0].push_back(1.0);
const std::vector<int> topological_order = {source, a, destination};
const std::vector<int> sources = {source};
const std::vector<int> destinations = {destination};
const std::vector<double> max_resources = {6.0};
ConstrainedShortestPathsOnDagWrapper<util::ListGraph<>>
constrained_shortest_path_on_dag_with_one_label(
&graph, &arc_lengths, &arc_resources, topological_order, sources,
destinations, &max_resources, /*max_num_created_labels=*/1);
ConstrainedShortestPathsOnDagWrapper<util::ListGraph<>>
constrained_shortest_path_on_dag_with_four_labels(
&graph, &arc_lengths, &arc_resources, topological_order, sources,
destinations, &max_resources, /*max_num_created_labels=*/4);
EXPECT_THAT(constrained_shortest_path_on_dag_with_one_label
.RunConstrainedShortestPathOnDag(),
FieldsAre(/*length=*/kInf,
/*arc_path=*/IsEmpty(),
/*node_path=*/IsEmpty()));
EXPECT_THAT(constrained_shortest_path_on_dag_with_four_labels
.RunConstrainedShortestPathOnDag(),
FieldsAre(/*length=*/3.0, /*arc_path=*/ElementsAre(0, 1),
/*node_path=*/ElementsAre(source, a, 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.
template <typename NodeIndex = int32_t, typename ArcIndex = int32_t>
std::pair<util::StaticGraph<NodeIndex, ArcIndex>, std::vector<NodeIndex>>
BuildRandomDag(const NodeIndex num_nodes, const ArcIndex 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<NodeIndex> topological_order(num_nodes);
topological_order.back() = num_nodes - 1;
absl::Span<NodeIndex> 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);
ArcIndex edges_added = 0;
util::StaticGraph<NodeIndex, ArcIndex> graph(num_nodes, num_arcs);
graph.AddArc(0, num_nodes - 1);
while (edges_added < num_arcs - 1) {
NodeIndex start_index = absl::Uniform(bit_gen, 0, num_nodes - 1);
NodeIndex 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
// `start_to_end_value`.
template <class GraphType>
std::vector<double> GenerateRandomIntegerValues(
const GraphType& graph, const double min_value = 0.0,
const double max_value = 10.0, const double start_to_end_value = 10000.0) {
absl::BitGen bit_gen;
std::vector<double> arc_values;
arc_values.reserve(graph.num_arcs());
bool start_to_end_value_set = false;
for (typename GraphType::ArcIndex arc = 0; arc < graph.num_arcs(); ++arc) {
if (!start_to_end_value_set && graph.Tail(arc) == 0 &&
graph.Head(arc) == graph.num_nodes() - 1) {
arc_values.push_back(start_to_end_value);
start_to_end_value_set = true;
continue;
}
arc_values.push_back(
static_cast<double>(absl::Uniform<int>(bit_gen, min_value, max_value)));
}
return arc_values;
}
double SolveConstrainedShortestPathUsingIntegerProgramming(
const util::StaticGraph<>& graph, absl::Span<const double> arc_lengths,
absl::Span<const std::vector<double>> arc_resources,
absl::Span<const double> max_resources,
absl::Span<const util::StaticGraph<>::NodeIndex> sources,
absl::Span<const util::StaticGraph<>::NodeIndex> destinations) {
using NodeIndex = util::StaticGraph<>::NodeIndex;
using ArcIndex = util::StaticGraph<>::ArcIndex;
math_opt::Model model;
std::vector<math_opt::Variable> arc_variables;
std::vector<math_opt::LinearExpression> flow_conservation(graph.num_nodes(),
0.0);
for (ArcIndex arc_index = 0; arc_index < graph.num_arcs(); ++arc_index) {
arc_variables.push_back(model.AddBinaryVariable(absl::StrCat(
arc_index, "_", graph.Tail(arc_index), "->", graph.Head(arc_index))));
model.set_objective_coefficient(arc_variables[arc_index],
arc_lengths[arc_index]);
flow_conservation[graph.Head(arc_index)] -= arc_variables[arc_index];
flow_conservation[graph.Tail(arc_index)] += arc_variables[arc_index];
}
math_opt::LinearExpression all_sources;
math_opt::LinearExpression all_destinations;
for (NodeIndex node_index = 0; node_index < graph.num_nodes(); ++node_index) {
math_opt::LinearExpression net_flow = 0;
if (absl::c_linear_search(sources, node_index)) {
const math_opt::Variable s = model.AddBinaryVariable();
all_sources += s;
net_flow += s;
}
if (absl::c_linear_search(destinations, node_index)) {
const math_opt::Variable t = model.AddBinaryVariable();
all_destinations += t;
net_flow -= t;
}
model.AddLinearConstraint(flow_conservation[node_index] == net_flow);
}
model.AddLinearConstraint(all_sources == 1);
model.AddLinearConstraint(all_destinations == 1);
for (int r = 0; r < max_resources.size(); ++r) {
math_opt::LinearExpression variable_resources;
for (ArcIndex arc_index = 0; arc_index < graph.num_arcs(); ++arc_index) {
variable_resources +=
arc_resources[r][arc_index] * arc_variables[arc_index];
}
model.AddLinearConstraint(variable_resources <= max_resources[r]);
}
const absl::StatusOr<math_opt::SolveResult> result =
math_opt::Solve(model, math_opt::SolverType::kCpSat, {});
CHECK_OK(result.status())
<< util::GraphToString(graph, util::PRINT_GRAPH_ARCS);
CHECK_OK(result->termination.EnsureIsOptimal())
<< util::GraphToString(graph, util::PRINT_GRAPH_ARCS);
return result->objective_value();
}
TEST(ConstrainedShortestPathsOnDagWrapperTest,
RandomizedStressTestSingleResource) {
absl::BitGen bit_gen;
const int kNumTests = 50;
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 resources
const auto [graph, topological_order] = BuildRandomDag(num_nodes, num_arcs);
std::vector<double> arc_lengths(num_arcs);
std::vector<std::vector<double>> arc_resources(1);
arc_resources[0] = GenerateRandomIntegerValues(graph, /*min_value=*/1.0,
/*max_value=*/10.0,
/*start_to_end_value=*/1.0);
const std::vector<int> sources = {0};
const std::vector<int> destinations = {num_nodes - 1};
const std::vector<double> max_resources = {15.0};
ConstrainedShortestPathsOnDagWrapper<util::StaticGraph<>>
constrained_shortest_path_on_dag(&graph, &arc_lengths, &arc_resources,
topological_order, sources,
destinations, &max_resources);
const int kNumSamples = 5;
for (int _ = 0; _ < kNumSamples; ++_) {
arc_lengths = GenerateRandomIntegerValues(graph);
const GraphPathWithLength<util::StaticGraph<>> path_with_length =
constrained_shortest_path_on_dag.RunConstrainedShortestPathOnDag();
EXPECT_NEAR(path_with_length.length,
SolveConstrainedShortestPathUsingIntegerProgramming(
graph, arc_lengths, arc_resources, max_resources, sources,
destinations),
1e-5);
ASSERT_FALSE(HasFailure())
<< DUMP_VARS(num_nodes, num_arcs, arc_lengths) << "\n With graph :\n "
<< util::GraphToString(graph, util::PRINT_GRAPH_ARCS);
}
}
}
// -----------------------------------------------------------------------------
// Benchmark.
// -----------------------------------------------------------------------------
template <typename NodeIndex = int32_t, typename ArcIndex = int32_t>
void BM_RandomDag(benchmark::State& state) {
absl::BitGen bit_gen;
// Generate a fixed random DAG.
const NodeIndex num_nodes = state.range(0);
const ArcIndex num_arcs = num_nodes * state.range(1);
const auto [graph, topological_order] = BuildRandomDag(num_nodes, num_arcs);
// Generate 20 scenarios of random arc lengths.
const int num_scenarios = 20;
std::vector<std::vector<double>> arc_lengths_scenarios;
for (int _ = 0; _ < num_scenarios; ++_) {
arc_lengths_scenarios.push_back(GenerateRandomIntegerValues(graph));
}
std::vector<double> arc_lengths(num_arcs);
std::vector<std::vector<double>> arc_resources(1);
arc_resources[0] =
GenerateRandomIntegerValues(graph, /*min_value=*/1.0,
/*max_value=*/10.0,
/*start_to_end_value=*/num_nodes * 0.2);
const std::vector<NodeIndex> sources = {0};
const std::vector<NodeIndex> destinations = {num_nodes - 1};
const std::vector<double> max_resources = {num_nodes * 0.2};
ConstrainedShortestPathsOnDagWrapper<util::StaticGraph<NodeIndex, ArcIndex>>
constrained_shortest_path_on_dag(&graph, &arc_lengths, &arc_resources,
topological_order, sources, destinations,
&max_resources);
int total_label_count = 0;
for (auto _ : state) {
// Pick a arc lengths scenario at random.
arc_lengths =
arc_lengths_scenarios[absl::Uniform(bit_gen, 0, num_scenarios)];
const GraphPathWithLength<util::StaticGraph<NodeIndex, ArcIndex>>
path_with_length =
constrained_shortest_path_on_dag.RunConstrainedShortestPathOnDag();
total_label_count += constrained_shortest_path_on_dag.label_count();
CHECK_GE(path_with_length.length, 0.0);
CHECK_LE(path_with_length.length, 10000.0);
}
state.SetItemsProcessed(std::max(1, total_label_count));
}
BENCHMARK(BM_RandomDag<int32_t, int32_t>)
->ArgPair(1 << 10, 16)
->ArgPair(1 << 16, 4)
->ArgPair(1 << 16, 16)
->ArgPair(1 << 19, 4);
BENCHMARK(BM_RandomDag<int64_t, int64_t>)
->ArgPair(1 << 19, 16)
->ArgPair(1 << 22, 4);
// Generate a 2-dimensional grid DAG.
// Eg. for width=3, height=2, it generates this:
// 0 ----> 1 ----> 2
// | | |
// | | |
// v v v
// 3 ----> 4 ----> 5
void BM_GridDAG(benchmark::State& state) {
const int64_t width = state.range(0);
const int64_t height = state.range(1);
const int num_resources = state.range(2);
const int num_nodes = width * height;
const int num_arcs = 2 * num_nodes - width - height;
util::StaticGraph<> graph(num_nodes, num_arcs);
// Add horizontal edges.
for (int i = 0; i < height; ++i) {
for (int j = 1; j < width; ++j) {
const int left = i * width + (j - 1);
const int right = i * width + j;
graph.AddArc(left, right);
}
}
// Add vertical edges.
for (int i = 1; i < height; ++i) {
for (int j = 0; j < width; ++j) {
const int up = (i - 1) * width + j;
const int down = i * width + j;
graph.AddArc(up, down);
}
}
graph.Build();
std::vector<int> topological_order(num_nodes);
absl::c_iota(topological_order, 0);
// Generate 20 scenarios of random arc lengths.
absl::BitGen bit_gen;
const int kNumScenarios = 20;
std::vector<std::vector<double>> arc_lengths_scenarios;
for (int unused = 0; unused < kNumScenarios; ++unused) {
std::vector<double> arc_lengths(graph.num_arcs());
for (int i = 0; i < graph.num_arcs(); ++i) {
arc_lengths[i] = absl::Uniform<double>(bit_gen, 0, 1);
}
arc_lengths_scenarios.push_back(arc_lengths);
}
std::vector<std::vector<double>> arc_resources(num_resources);
for (int r = 0; r < num_resources; ++r) {
arc_resources[r].resize(graph.num_arcs());
for (int i = 0; i < graph.num_arcs(); ++i) {
arc_resources[r][i] = absl::Uniform<double>(bit_gen, 0, 1);
}
}
std::vector<double> arc_lengths(num_arcs);
const std::vector<int> sources = {0};
const std::vector<int> destinations = {num_nodes - 1};
std::vector<double> max_resources(num_resources);
// Each path from source to destination has `(width + height - 2)` arcs. Each
// arc has mean resource(s) 0.5. We want to consider paths with half (0.5) the
// mean resource(s).
const double max_resource = (width + height - 2) * 0.5 * 0.5;
for (int r = 0; r < num_resources; ++r) {
max_resources[r] = max_resource;
}
ConstrainedShortestPathsOnDagWrapper<util::StaticGraph<>>
constrained_shortest_path_on_dag(&graph, &arc_lengths, &arc_resources,
topological_order, sources, destinations,
&max_resources);
int total_label_count = 0;
for (auto _ : state) {
// Pick a arc lengths scenario at random.
arc_lengths =
arc_lengths_scenarios[absl::Uniform<int>(bit_gen, 0, kNumScenarios)];
const GraphPathWithLength<util::StaticGraph<>> path_with_length =
constrained_shortest_path_on_dag.RunConstrainedShortestPathOnDag();
total_label_count += constrained_shortest_path_on_dag.label_count();
CHECK_GE(path_with_length.length, 0.0);
}
state.SetItemsProcessed(std::max(1, total_label_count));
}
BENCHMARK(BM_GridDAG)
->Args({100, 100, 1})
->Args({100, 100, 2})
->Args({1000, 100, 1})
->Args({1000, 100, 2});
// -----------------------------------------------------------------------------
// Debug tests.
// -----------------------------------------------------------------------------
#ifndef NDEBUG
TEST(ConstrainedShortestPathOnDagTest, MinusInfWeight) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
const std::vector<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{source, destination, -kInf, {0.0}}};
EXPECT_DEATH(ConstrainedShortestPathsOnDag(
num_nodes, arcs_with_length_and_resources, source,
destination, /*max_resources=*/{0.0}),
"-inf");
}
TEST(ConstrainedShortestPathOnDagTest, NaNWeight) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
const std::vector<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{source, destination, std::numeric_limits<double>::quiet_NaN(), {0.0}}};
EXPECT_DEATH(ConstrainedShortestPathsOnDag(
num_nodes, arcs_with_length_and_resources, source,
destination, /*max_resources=*/{0.0}),
"NaN");
}
TEST(ConstrainedShortestPathOnDagTest, InfResource) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
const std::vector<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{source, destination, 0.0, {kInf}}};
EXPECT_DEATH(ConstrainedShortestPathsOnDag(
num_nodes, arcs_with_length_and_resources, source,
destination, /*max_resources=*/{0.0}),
"inf");
}
TEST(ConstrainedShortestPathOnDagTest, NegativeMaxResource) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
const std::vector<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{source, destination, 0.0, {0.0}}};
EXPECT_DEATH(ConstrainedShortestPathsOnDag(
num_nodes, arcs_with_length_and_resources, source,
destination, /*max_resources=*/{-1.0}),
"negative");
}
TEST(ConstrainedShortestPathOnDagTest, SourceIsDestination) {
const int source = 0;
const int num_nodes = 1;
EXPECT_DEATH(
ConstrainedShortestPathsOnDag(
num_nodes, /*arcs_with_length_and_resources=*/{}, source, source,
/*max_resources=*/{0.0}),
"source and destination");
}
TEST(ConstrainedShortestPathsOnDagWrapperTest, 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;
std::vector<std::vector<double>> arc_resources(1);
graph.AddArc(source, destination);
arc_lengths.push_back(1.0);
arc_resources[0].push_back({1.0});
const std::vector<int> topological_order = {source};
const std::vector<int> sources = {source};
const std::vector<int> destinations = {destination};
const std::vector<double> max_resources = {0.0};
EXPECT_DEATH(ConstrainedShortestPathsOnDagWrapper<util::ListGraph<>>(
&graph, &arc_lengths, &arc_resources, topological_order,
sources, destinations, &max_resources),
"Invalid topological order");
}
#endif // NDEBUG
} // namespace
} // namespace operations_research

View File

@@ -0,0 +1,136 @@
// 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 <limits>
#include <utility>
#include <vector>
#include "absl/log/check.h"
#include "absl/types/span.h"
#include "ortools/graph/graph.h"
#include "ortools/graph/topologicalsorter.h"
namespace operations_research {
namespace {
using GraphType = util::StaticGraph<>;
using NodeIndex = GraphType::NodeIndex;
using ArcIndex = GraphType::ArcIndex;
struct ShortestPathOnDagProblem {
GraphType graph;
std::vector<double> arc_lengths;
std::vector<ArcIndex> original_arc_indices;
std::vector<NodeIndex> topological_order;
};
ShortestPathOnDagProblem ReadProblem(
const int num_nodes, absl::Span<const ArcWithLength> arcs_with_length) {
GraphType graph(num_nodes, arcs_with_length.size());
std::vector<double> arc_lengths;
arc_lengths.reserve(arcs_with_length.size());
for (const auto& arc : arcs_with_length) {
graph.AddArc(arc.from, arc.to);
arc_lengths.push_back(arc.length);
}
std::vector<ArcIndex> permutation;
graph.Build(&permutation);
util::Permute(permutation, &arc_lengths);
std::vector<ArcIndex> original_arc_indices(permutation.size());
if (!permutation.empty()) {
for (ArcIndex i = 0; i < permutation.size(); ++i) {
original_arc_indices[permutation[i]] = i;
}
}
absl::StatusOr<std::vector<NodeIndex>> topological_order =
util::graph::FastTopologicalSort(graph);
CHECK_OK(topological_order) << "arcs_with_length form a cycle.";
return ShortestPathOnDagProblem{
.graph = std::move(graph),
.arc_lengths = std::move(arc_lengths),
.original_arc_indices = std::move(original_arc_indices),
.topological_order = std::move(topological_order).value()};
}
void GetOriginalArcPath(absl::Span<const ArcIndex> original_arc_indices,
std::vector<ArcIndex>& arc_path) {
if (original_arc_indices.empty()) {
return;
}
for (int i = 0; i < arc_path.size(); ++i) {
arc_path[i] = original_arc_indices[arc_path[i]];
}
}
} // namespace
PathWithLength ShortestPathsOnDag(
const int num_nodes, absl::Span<const ArcWithLength> arcs_with_length,
const int source, const int destination) {
const ShortestPathOnDagProblem problem =
ReadProblem(num_nodes, arcs_with_length);
ShortestPathsOnDagWrapper<util::StaticGraph<>> shortest_path_on_dag(
&problem.graph, &problem.arc_lengths, problem.topological_order);
shortest_path_on_dag.RunShortestPathOnDag({source});
if (!shortest_path_on_dag.IsReachable(destination)) {
return PathWithLength{.length = std::numeric_limits<double>::infinity()};
}
std::vector<int> arc_path = shortest_path_on_dag.ArcPathTo(destination);
GetOriginalArcPath(problem.original_arc_indices, arc_path);
return PathWithLength{
.length = shortest_path_on_dag.LengthTo(destination),
.arc_path = std::move(arc_path),
.node_path = shortest_path_on_dag.NodePathTo(destination)};
}
std::vector<PathWithLength> KShortestPathsOnDag(
const int num_nodes, absl::Span<const ArcWithLength> arcs_with_length,
const int source, const int destination, const int path_count) {
const ShortestPathOnDagProblem problem =
ReadProblem(num_nodes, arcs_with_length);
KShortestPathsOnDagWrapper<GraphType> shortest_paths_on_dag(
&problem.graph, &problem.arc_lengths, problem.topological_order,
path_count);
shortest_paths_on_dag.RunKShortestPathOnDag({source});
if (!shortest_paths_on_dag.IsReachable(destination)) {
return {PathWithLength{.length = std::numeric_limits<double>::infinity()}};
}
std::vector<double> lengths = shortest_paths_on_dag.LengthsTo(destination);
std::vector<std::vector<GraphType::ArcIndex>> arc_paths =
shortest_paths_on_dag.ArcPathsTo(destination);
std::vector<std::vector<GraphType::NodeIndex>> node_paths =
shortest_paths_on_dag.NodePathsTo(destination);
std::vector<PathWithLength> paths;
paths.reserve(lengths.size());
for (int k = 0; k < lengths.size(); ++k) {
GetOriginalArcPath(problem.original_arc_indices, arc_paths[k]);
paths.push_back(PathWithLength{.length = lengths[k],
.arc_path = std::move(arc_paths[k]),
.node_path = std::move(node_paths[k])});
}
return paths;
}
} // namespace operations_research

View File

@@ -0,0 +1,714 @@
// 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.
#ifndef OR_TOOLS_GRAPH_DAG_SHORTEST_PATH_H_
#define OR_TOOLS_GRAPH_DAG_SHORTEST_PATH_H_
#include <cmath>
#if __cplusplus >= 202002L
#include <concepts>
#endif
#include <functional>
#include <limits>
#include <vector>
#include "absl/algorithm/container.h"
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/strings/str_format.h"
#include "absl/types/span.h"
namespace operations_research {
// TODO(b/332475231): extend to non-floating lengths.
// TODO(b/332476147): extend to allow for length functor.
// This library provides a few APIs to compute the shortest path on a given
// directed acyclic graph (DAG).
//
// In the DAG, multiple arcs between the same pair of nodes is allowed. However,
// self-loop arcs are not allowed.
//
// Note that we use the length formalism here, but the arc lengths can represent
// any numeric physical quantity. A shortest path will just be a path minimizing
// this quantity where the length of a path is the sum of the length of its
// arcs. An arc length can be negative, or +inf (indicating that it should not
// be used). An arc length cannot be -inf or nan.
// -----------------------------------------------------------------------------
// Basic API.
// -----------------------------------------------------------------------------
// `from` and `to` should both be in [0, num_nodes).
// If the length is +inf, then the arc should not be used.
struct ArcWithLength {
int from = 0;
int to = 0;
double length = 0.0;
};
struct PathWithLength {
double length = 0.0;
// The returned arc indices points into the `arcs_with_length` passed to the
// function below.
std::vector<int> arc_path;
std::vector<int> node_path; // includes the source node.
};
// Returns {+inf, {}, {}} if there is no path of finite length from the source
// to the destination. Dies if `arcs_with_length` has a cycle.
PathWithLength ShortestPathsOnDag(
int num_nodes, absl::Span<const ArcWithLength> arcs_with_length, int source,
int destination);
// Returns the k-shortest paths by increasing length. Returns fewer than k paths
// if there are fewer than k paths from the source to the destination. Returns
// {{+inf, {}, {}}} if there is no path of finite length from the source to the
// destination. Dies if `arcs_with_length` has a cycle.
std::vector<PathWithLength> KShortestPathsOnDag(
int num_nodes, absl::Span<const ArcWithLength> arcs_with_length, int source,
int destination, int path_count);
// -----------------------------------------------------------------------------
// Advanced API.
// -----------------------------------------------------------------------------
// This concept only enforces the standard graph API needed for all algorithms
// on DAGs. One could add the requirement of being a DAG wihtin this concept
// (which is done before running the algorithm).
#if __cplusplus >= 202002L
template <class GraphType>
concept DagGraphType = requires(GraphType graph) {
{ typename GraphType::NodeIndex{} };
{ typename GraphType::ArcIndex{} };
{ graph.num_nodes() } -> std::same_as<typename GraphType::NodeIndex>;
{ graph.num_arcs() } -> std::same_as<typename GraphType::ArcIndex>;
{ graph.OutgoingArcs(typename GraphType::NodeIndex{}) };
{
graph.Tail(typename GraphType::ArcIndex{})
} -> std::same_as<typename GraphType::NodeIndex>;
{
graph.Head(typename GraphType::ArcIndex{})
} -> std::same_as<typename GraphType::NodeIndex>;
{ graph.Build() };
};
#endif
// A wrapper that holds the memory needed to run many shortest path computations
// efficiently on the given DAG. One call of `RunShortestPathOnDag()` has time
// complexity O(|E| + |V|) and space complexity O(|V|).
// `GraphType` can use any of the interfaces defined in `util/graph/graph.h`.
template <class GraphType>
#if __cplusplus >= 202002L
requires DagGraphType<GraphType>
#endif
class ShortestPathsOnDagWrapper {
public:
using NodeIndex = typename GraphType::NodeIndex;
using ArcIndex = typename GraphType::ArcIndex;
// IMPORTANT: All arguments must outlive the class.
//
// The vector of `arc_lengths` *must* be of size `graph.num_arcs()` and
// indexed the same way as in `graph`.
//
// You *must* provide a topological order. You can use
// `util::graph::FastTopologicalSort(graph)` to compute one if you don't
// already have one. An invalid topological order results in an upper bound
// for all shortest path computations. For maximum performance, you can
// further reindex the nodes under the topological order so that the memory
// access pattern is generally forward instead of random. For example, if the
// topological order for a graph with 4 nodes is [2,1,0,3], you can re-label
// the nodes 2, 1, and 0 to 0, 1, and 2 (and updates arcs accordingly).
//
// Validity of arcs and topological order are CHECKed if compiled in DEBUG
// mode.
//
// SUBTLE: You can modify the graph, the arc lengths or the topological order
// between calls to the `RunShortestPathOnDag()` function. That's fine. Doing
// so will obviously invalidate the result API of the last shortest path run,
// which could return an upper bound, junk, or crash.
ShortestPathsOnDagWrapper(const GraphType* graph,
const std::vector<double>* arc_lengths,
absl::Span<const NodeIndex> topological_order);
// Computes the shortest path to all reachable nodes from the given sources.
// This must be called before any of the query functions below.
void RunShortestPathOnDag(absl::Span<const NodeIndex> sources);
// Returns true if `node` is reachable from at least one source, i.e., the
// length from at least one source is finite.
bool IsReachable(NodeIndex node) const;
const std::vector<NodeIndex>& reached_nodes() const { return reached_nodes_; }
// Returns the length of the shortest path from `node`'s source to `node`.
double LengthTo(NodeIndex node) const { return length_from_sources_[node]; }
std::vector<double> LengthTo() const { return length_from_sources_; }
// Returns the list of all the arcs in the shortest path from `node`'s
// source to `node`. CHECKs if the node is reachable.
std::vector<ArcIndex> ArcPathTo(NodeIndex node) const;
// Returns the list of all the nodes in the shortest path from `node`'s
// source to `node` (including the source). CHECKs if the node is reachable.
std::vector<NodeIndex> NodePathTo(NodeIndex node) const;
// Accessors to the underlying graph and arc lengths.
const GraphType& graph() const { return *graph_; }
const std::vector<double>& arc_lengths() const { return *arc_lengths_; }
private:
static constexpr double kInf = std::numeric_limits<double>::infinity();
const GraphType* const graph_;
const std::vector<double>* const arc_lengths_;
absl::Span<const NodeIndex> const topological_order_;
// Data about the last call of the RunShortestPathOnDag() function.
std::vector<double> length_from_sources_;
std::vector<ArcIndex> incoming_shortest_path_arc_;
std::vector<NodeIndex> reached_nodes_;
};
// A wrapper that holds the memory needed to run many k-shortest paths
// computations efficiently on the given DAG. One call of
// `RunKShortestPathOnDag()` has time complexity O(|E| + k|V|log(d)) where d is
// the mean degree of the graph and space complexity O(k|V|).
// `GraphType` can use any of the interfaces defined in `util/graph/graph.h`.
// IMPORTANT: Only use if `path_count > 1` (k > 1) otherwise use
// `ShortestPathsOnDagWrapper`.
template <class GraphType>
#if __cplusplus >= 202002L
requires DagGraphType<GraphType>
#endif
class KShortestPathsOnDagWrapper {
public:
using NodeIndex = typename GraphType::NodeIndex;
using ArcIndex = typename GraphType::ArcIndex;
// IMPORTANT: All arguments must outlive the class.
//
// The vector of `arc_lengths` *must* be of size `graph.num_arcs()` and
// indexed the same way as in `graph`.
//
// You *must* provide a topological order. You can use
// `util::graph::FastTopologicalSort(graph)` to compute one if you don't
// already have one. An invalid topological order results in an upper bound
// for all shortest path computations. For maximum performance, you can
// further reindex the nodes under the topological order so that the memory
// access pattern is generally forward instead of random. For example, if the
// topological order for a graph with 4 nodes is [2,1,0,3], you can re-label
// the nodes 2, 1, and 0 to 0, 1, and 2 (and updates arcs accordingly).
//
// Validity of arcs and topological order are CHECKed if compiled in DEBUG
// mode.
//
// SUBTLE: You can modify the graph, the arc lengths or the topological order
// between calls to the `RunKShortestPathOnDag()` function. That's fine. Doing
// so will obviously invalidate the result API of the last shortest path run,
// which could return an upper bound, junk, or crash.
KShortestPathsOnDagWrapper(const GraphType* graph,
const std::vector<double>* arc_lengths,
absl::Span<const NodeIndex> topological_order,
int path_count);
// Computes the shortest path to all reachable nodes from the given sources.
// This must be called before any of the query functions below.
void RunKShortestPathOnDag(absl::Span<const NodeIndex> sources);
// Returns true if `node` is reachable from at least one source, i.e., the
// length of the shortest path from at least one source is finite.
bool IsReachable(NodeIndex node) const;
const std::vector<NodeIndex>& reached_nodes() const { return reached_nodes_; }
// Returns the lengths of the k-shortest paths from `node`'s source to `node`
// in increasing order. If there are less than k paths, return all path
// lengths.
std::vector<double> LengthsTo(NodeIndex node) const;
// Returns the list of all the arcs of the k-shortest paths from `node`'s
// source to `node`.
std::vector<std::vector<ArcIndex>> ArcPathsTo(NodeIndex node) const;
// Returns the list of all the nodes of the k-shortest paths from `node`'s
// source to `node` (including the source). CHECKs if the node is reachable.
std::vector<std::vector<NodeIndex>> NodePathsTo(NodeIndex node) const;
// Accessors to the underlying graph and arc lengths.
const GraphType& graph() const { return *graph_; }
const std::vector<double>& arc_lengths() const { return *arc_lengths_; }
int path_count() const { return path_count_; }
private:
static constexpr double kInf = std::numeric_limits<double>::infinity();
const GraphType* const graph_;
const std::vector<double>* const arc_lengths_;
absl::Span<const NodeIndex> const topological_order_;
const int path_count_;
GraphType reverse_graph_;
// Maps reverse arc indices to indices in the original graph.
std::vector<ArcIndex> arc_indices_;
// Data about the last call of the `RunKShortestPathOnDag()` function. The
// first dimension is the index of the path (1st being the shortest). The
// second dimension are nodes.
std::vector<std::vector<double>> lengths_from_sources_;
std::vector<std::vector<ArcIndex>> incoming_shortest_paths_arc_;
std::vector<std::vector<int>> incoming_shortest_paths_index_;
std::vector<bool> is_source_;
std::vector<NodeIndex> reached_nodes_;
};
template <class GraphType>
#if __cplusplus >= 202002L
requires DagGraphType<GraphType>
#endif
absl::Status TopologicalOrderIsValid(
const GraphType& graph,
absl::Span<const typename GraphType::NodeIndex> topological_order);
// -----------------------------------------------------------------------------
// Implementations.
// -----------------------------------------------------------------------------
// TODO(b/332475804): If `ArcPathTo` and/or `NodePathTo` functions become
// bottlenecks:
// (1) have the class preallocate a buffer of size `num_nodes`
// (2) assign into an index rather than with push_back
// (3) return by absl::Span (or return a copy) with known size.
template <typename GraphType>
#if __cplusplus >= 202002L
requires DagGraphType<GraphType>
#endif
std::vector<typename GraphType::NodeIndex> NodePathImpliedBy(
absl::Span<const typename GraphType::ArcIndex> arc_path,
const GraphType& graph) {
CHECK(!arc_path.empty());
std::vector<typename GraphType::NodeIndex> node_path;
node_path.reserve(arc_path.size() + 1);
for (const typename GraphType::ArcIndex arc_index : arc_path) {
node_path.push_back(graph.Tail(arc_index));
}
node_path.push_back(graph.Head(arc_path.back()));
return node_path;
}
template <class GraphType>
#if __cplusplus >= 202002L
requires DagGraphType<GraphType>
#endif
void CheckNodeIsValid(typename GraphType::NodeIndex node,
const GraphType& graph) {
CHECK_GE(node, 0) << "Node must be nonnegative. Input value: " << node;
CHECK_LT(node, graph.num_nodes())
<< "Node must be a valid node. Input value: " << node
<< ". Number of nodes in the input graph: " << graph.num_nodes();
}
template <class GraphType>
#if __cplusplus >= 202002L
requires DagGraphType<GraphType>
#endif
absl::Status TopologicalOrderIsValid(
const GraphType& graph,
absl::Span<const typename GraphType::NodeIndex> topological_order) {
using NodeIndex = typename GraphType::NodeIndex;
using ArcIndex = typename GraphType::ArcIndex;
const NodeIndex num_nodes = graph.num_nodes();
if (topological_order.size() != num_nodes) {
return absl::InvalidArgumentError(absl::StrFormat(
"topological_order.size() = %i, != graph.num_nodes() = %i",
topological_order.size(), num_nodes));
}
std::vector<NodeIndex> inverse_topology(num_nodes, -1);
for (NodeIndex node = 0; node < topological_order.size(); ++node) {
if (inverse_topology[topological_order[node]] >= 0) {
return absl::InvalidArgumentError(
absl::StrFormat("node % i appears twice in topological order",
topological_order[node]));
}
inverse_topology[topological_order[node]] = node;
}
for (NodeIndex tail = 0; tail < num_nodes; ++tail) {
for (const ArcIndex arc : graph.OutgoingArcs(tail)) {
const NodeIndex head = graph.Head(arc);
if (inverse_topology[tail] >= inverse_topology[head]) {
return absl::InvalidArgumentError(absl::StrFormat(
"arc (%i, %i) is inconsistent with topological order", tail, head));
}
}
}
return absl::OkStatus();
}
// -----------------------------------------------------------------------------
// ShortestPathsOnDagWrapper implementation.
// -----------------------------------------------------------------------------
template <class GraphType>
#if __cplusplus >= 202002L
requires DagGraphType<GraphType>
#endif
ShortestPathsOnDagWrapper<GraphType>::ShortestPathsOnDagWrapper(
const GraphType* graph, const std::vector<double>* arc_lengths,
absl::Span<const NodeIndex> topological_order)
: graph_(graph),
arc_lengths_(arc_lengths),
topological_order_(topological_order) {
CHECK(graph_ != nullptr);
CHECK(arc_lengths_ != nullptr);
CHECK_GT(graph_->num_nodes(), 0) << "The graph is empty: it has no nodes";
CHECK_GT(graph_->num_arcs(), 0) << "The graph is empty: it has no arcs";
#ifndef NDEBUG
CHECK_EQ(arc_lengths_->size(), graph_->num_arcs());
for (const double arc_length : *arc_lengths_) {
CHECK(arc_length != -kInf && !std::isnan(arc_length))
<< absl::StrFormat("length cannot be -inf nor NaN");
}
CHECK_OK(TopologicalOrderIsValid(*graph_, topological_order_))
<< "Invalid topological order";
#endif
// Memory allocation is done here and only once in order to avoid reallocation
// at each call of `RunShortestPathOnDag()` for better performance.
length_from_sources_.resize(graph_->num_nodes(), kInf);
incoming_shortest_path_arc_.resize(graph_->num_nodes(), -1);
reached_nodes_.reserve(graph_->num_nodes());
}
template <class GraphType>
#if __cplusplus >= 202002L
requires DagGraphType<GraphType>
#endif
void ShortestPathsOnDagWrapper<GraphType>::RunShortestPathOnDag(
absl::Span<const NodeIndex> sources) {
// Caching the vector addresses allow to not fetch it on each access.
const absl::Span<double> length_from_sources =
absl::MakeSpan(length_from_sources_);
const absl::Span<const double> arc_lengths = *arc_lengths_;
// Avoid reassigning `incoming_shortest_path_arc_` at every call for better
// performance, so it only makes sense for nodes that are reachable from at
// least one source, the other ones will contain junk.
for (const NodeIndex node : reached_nodes_) {
length_from_sources[node] = kInf;
}
DCHECK(std::all_of(length_from_sources.begin(), length_from_sources.end(),
[](double l) { return l == kInf; }));
reached_nodes_.clear();
for (const NodeIndex source : sources) {
CheckNodeIsValid(source, *graph_);
length_from_sources[source] = 0.0;
}
for (const NodeIndex tail : topological_order_) {
const double length_to_tail = length_from_sources[tail];
// Stop exploring a node as soon as its length to all sources is +inf.
if (length_to_tail == kInf) {
continue;
}
reached_nodes_.push_back(tail);
for (const ArcIndex arc : graph_->OutgoingArcs(tail)) {
const NodeIndex head = graph_->Head(arc);
DCHECK(arc_lengths[arc] != -kInf);
const double length_to_head = arc_lengths[arc] + length_to_tail;
if (length_to_head < length_from_sources[head]) {
length_from_sources[head] = length_to_head;
incoming_shortest_path_arc_[head] = arc;
}
}
}
}
template <class GraphType>
#if __cplusplus >= 202002L
requires DagGraphType<GraphType>
#endif
bool ShortestPathsOnDagWrapper<GraphType>::IsReachable(NodeIndex node) const {
CheckNodeIsValid(node, *graph_);
return length_from_sources_[node] < kInf;
}
template <class GraphType>
#if __cplusplus >= 202002L
requires DagGraphType<GraphType>
#endif
std::vector<typename GraphType::ArcIndex>
ShortestPathsOnDagWrapper<GraphType>::ArcPathTo(NodeIndex node) const {
CHECK(IsReachable(node));
std::vector<ArcIndex> arc_path;
NodeIndex current_node = node;
for (int i = 0; i < graph_->num_nodes(); ++i) {
ArcIndex current_arc = incoming_shortest_path_arc_[current_node];
if (current_arc == -1) {
break;
}
arc_path.push_back(current_arc);
current_node = graph_->Tail(current_arc);
}
absl::c_reverse(arc_path);
return arc_path;
}
template <class GraphType>
#if __cplusplus >= 202002L
requires DagGraphType<GraphType>
#endif
std::vector<typename GraphType::NodeIndex>
ShortestPathsOnDagWrapper<GraphType>::NodePathTo(NodeIndex node) const {
const std::vector<typename GraphType::ArcIndex> arc_path = ArcPathTo(node);
if (arc_path.empty()) {
return {node};
}
return NodePathImpliedBy(ArcPathTo(node), *graph_);
}
// -----------------------------------------------------------------------------
// KShortestPathsOnDagWrapper implementation.
// -----------------------------------------------------------------------------
template <class GraphType>
#if __cplusplus >= 202002L
requires DagGraphType<GraphType>
#endif
KShortestPathsOnDagWrapper<GraphType>::KShortestPathsOnDagWrapper(
const GraphType* graph, const std::vector<double>* arc_lengths,
absl::Span<const NodeIndex> topological_order, const int path_count)
: graph_(graph),
arc_lengths_(arc_lengths),
topological_order_(topological_order),
path_count_(path_count) {
CHECK(graph_ != nullptr);
CHECK(arc_lengths_ != nullptr);
CHECK_GT(graph_->num_nodes(), 0) << "The graph is empty: it has no nodes";
CHECK_GT(graph_->num_arcs(), 0) << "The graph is empty: it has no arcs";
CHECK_GT(path_count_, 0) << "path_count must be greater than 0";
#ifndef NDEBUG
CHECK_EQ(arc_lengths_->size(), graph_->num_arcs());
for (const double arc_length : *arc_lengths_) {
CHECK(arc_length != -kInf && !std::isnan(arc_length))
<< absl::StrFormat("length cannot be -inf nor NaN");
}
CHECK_OK(TopologicalOrderIsValid(*graph_, topological_order_))
<< "Invalid topological order";
#endif
// TODO(b/332475713): Optimize if reverse graph is already provided in
// `GraphType`.
const int num_arcs = graph_->num_arcs();
reverse_graph_ = GraphType(graph_->num_nodes(), num_arcs);
for (ArcIndex arc_index = 0; arc_index < num_arcs; ++arc_index) {
reverse_graph_.AddArc(graph->Head(arc_index), graph->Tail(arc_index));
}
std::vector<ArcIndex> permutation;
reverse_graph_.Build(&permutation);
arc_indices_.resize(permutation.size());
if (!permutation.empty()) {
for (int i = 0; i < permutation.size(); ++i) {
arc_indices_[permutation[i]] = i;
}
}
// Memory allocation is done here and only once in order to avoid reallocation
// at each call of `RunKShortestPathOnDag()` for better performance.
lengths_from_sources_.resize(path_count_);
incoming_shortest_paths_arc_.resize(path_count_);
incoming_shortest_paths_index_.resize(path_count_);
for (int k = 0; k < path_count_; ++k) {
lengths_from_sources_[k].resize(graph_->num_nodes(), kInf);
incoming_shortest_paths_arc_[k].resize(graph_->num_nodes(), -1);
incoming_shortest_paths_index_[k].resize(graph_->num_nodes(), -1);
}
is_source_.resize(graph_->num_nodes(), false);
reached_nodes_.reserve(graph_->num_nodes());
}
template <class GraphType>
#if __cplusplus >= 202002L
requires DagGraphType<GraphType>
#endif
void KShortestPathsOnDagWrapper<GraphType>::RunKShortestPathOnDag(
absl::Span<const NodeIndex> sources) {
// Caching the vector addresses allow to not fetch it on each access.
const absl::Span<const double> arc_lengths = *arc_lengths_;
const absl::Span<const ArcIndex> arc_indices = arc_indices_;
// Avoid reassigning `incoming_shortest_path_arc_` at every call for better
// performance, so it only makes sense for nodes that are reachable from at
// least one source, the other ones will contain junk.
for (const NodeIndex node : reached_nodes_) {
is_source_[node] = false;
for (int k = 0; k < path_count_; ++k) {
lengths_from_sources_[k][node] = kInf;
}
}
reached_nodes_.clear();
#ifndef NDEBUG
for (int k = 0; k < path_count_; ++k) {
CHECK(std::all_of(lengths_from_sources_[k].begin(),
lengths_from_sources_[k].end(),
[](double l) { return l == kInf; }));
}
#endif
for (const NodeIndex source : sources) {
CheckNodeIsValid(source, *graph_);
is_source_[source] = true;
}
struct IncomingArcPath {
double path_length = 0.0;
ArcIndex arc_index = 0;
double arc_length = 0.0;
NodeIndex from = 0;
int path_index = 0;
bool operator<(const IncomingArcPath& other) const {
return std::tie(path_length, from) <
std::tie(other.path_length, other.from);
}
bool operator>(const IncomingArcPath& other) const { return other < *this; }
};
std::vector<IncomingArcPath> min_heap;
auto comp = std::greater<IncomingArcPath>();
for (const NodeIndex to : topological_order_) {
min_heap.clear();
if (is_source_[to]) {
min_heap.push_back({.arc_index = -1});
}
for (const ArcIndex reverse_arc_index : reverse_graph_.OutgoingArcs(to)) {
const ArcIndex arc_index = arc_indices.empty()
? reverse_arc_index
: arc_indices[reverse_arc_index];
const NodeIndex from = graph_->Tail(arc_index);
const double arc_length = arc_lengths[arc_index];
DCHECK(arc_length != -kInf);
const double path_length =
lengths_from_sources_.front()[from] + arc_length;
if (path_length == kInf) {
continue;
}
min_heap.push_back({.path_length = path_length,
.arc_index = arc_index,
.arc_length = arc_length,
.from = from});
std::push_heap(min_heap.begin(), min_heap.end(), comp);
}
if (min_heap.empty()) {
continue;
}
reached_nodes_.push_back(to);
for (int k = 0; k < path_count_; ++k) {
std::pop_heap(min_heap.begin(), min_heap.end(), comp);
IncomingArcPath& incoming_arc_path = min_heap.back();
lengths_from_sources_[k][to] = incoming_arc_path.path_length;
incoming_shortest_paths_arc_[k][to] = incoming_arc_path.arc_index;
incoming_shortest_paths_index_[k][to] = incoming_arc_path.path_index;
if (incoming_arc_path.arc_index != -1 &&
incoming_arc_path.path_index < path_count_ - 1 &&
lengths_from_sources_[incoming_arc_path.path_index + 1]
[incoming_arc_path.from] < kInf) {
++incoming_arc_path.path_index;
incoming_arc_path.path_length =
lengths_from_sources_[incoming_arc_path.path_index]
[incoming_arc_path.from] +
incoming_arc_path.arc_length;
std::push_heap(min_heap.begin(), min_heap.end(), comp);
} else {
min_heap.pop_back();
if (min_heap.empty()) {
break;
}
}
}
}
}
template <class GraphType>
#if __cplusplus >= 202002L
requires DagGraphType<GraphType>
#endif
bool KShortestPathsOnDagWrapper<GraphType>::IsReachable(NodeIndex node) const {
CheckNodeIsValid(node, *graph_);
return lengths_from_sources_.front()[node] < kInf;
}
template <class GraphType>
#if __cplusplus >= 202002L
requires DagGraphType<GraphType>
#endif
std::vector<double> KShortestPathsOnDagWrapper<GraphType>::LengthsTo(
NodeIndex node) const {
std::vector<double> lengths_to;
lengths_to.reserve(path_count_);
for (int k = 0; k < path_count_; ++k) {
const double length_to = lengths_from_sources_[k][node];
if (length_to == kInf) {
break;
}
lengths_to.push_back(length_to);
}
return lengths_to;
}
template <class GraphType>
#if __cplusplus >= 202002L
requires DagGraphType<GraphType>
#endif
std::vector<std::vector<typename GraphType::ArcIndex>>
KShortestPathsOnDagWrapper<GraphType>::ArcPathsTo(NodeIndex node) const {
std::vector<std::vector<ArcIndex>> arc_paths;
arc_paths.reserve(path_count_);
for (int k = 0; k < path_count_; ++k) {
if (lengths_from_sources_[k][node] == kInf) {
break;
}
std::vector<ArcIndex> arc_path;
int current_path_index = k;
NodeIndex current_node = node;
for (int i = 0; i < graph_->num_nodes(); ++i) {
ArcIndex current_arc =
incoming_shortest_paths_arc_[current_path_index][current_node];
if (current_arc == -1) {
break;
}
arc_path.push_back(current_arc);
current_path_index =
incoming_shortest_paths_index_[current_path_index][current_node];
current_node = graph_->Tail(current_arc);
}
absl::c_reverse(arc_path);
arc_paths.push_back(arc_path);
}
return arc_paths;
}
template <class GraphType>
#if __cplusplus >= 202002L
requires DagGraphType<GraphType>
#endif
std::vector<std::vector<typename GraphType::NodeIndex>>
KShortestPathsOnDagWrapper<GraphType>::NodePathsTo(NodeIndex node) const {
const std::vector<std::vector<ArcIndex>> arc_paths = ArcPathsTo(node);
std::vector<std::vector<NodeIndex>> node_paths(arc_paths.size());
for (int k = 0; k < arc_paths.size(); ++k) {
if (arc_paths[k].empty()) {
node_paths[k] = {node};
} else {
node_paths[k] = NodePathImpliedBy(arc_paths[k], *graph_);
}
}
return node_paths;
}
} // namespace operations_research
#endif // OR_TOOLS_GRAPH_DAG_SHORTEST_PATH_H_

File diff suppressed because it is too large Load Diff

View File

@@ -31,6 +31,22 @@ code_sample_cc(name = "bfs_one_to_all")
code_sample_cc(name = "bfs_undirected")
code_sample_cc(name = "dag_shortest_path_one_to_all")
code_sample_cc(name = "dag_shortest_path_sequential")
code_sample_cc(name = "dag_simple_shortest_path")
code_sample_cc(name = "dag_multiple_shortest_paths_one_to_all")
code_sample_cc(name = "dag_multiple_shortest_paths_sequential")
code_sample_cc(name = "dag_simple_multiple_shortest_paths")
code_sample_cc(name = "dag_constrained_shortest_path_sequential")
code_sample_cc(name = "dag_simple_constrained_shortest_path")
code_sample_cc(name = "dijkstra_all_pairs_shortest_paths")
code_sample_cc(name = "dijkstra_directed")

View File

@@ -29,6 +29,8 @@ def code_sample_cc(name):
"//ortools/graph:assignment",
"//ortools/graph:bounded_dijkstra",
"//ortools/graph:bfs",
"//ortools/graph:dag_constrained_shortest_path",
"//ortools/graph:dag_shortest_path",
"//ortools/graph:linear_assignment",
"//ortools/graph:max_flow",
"//ortools/graph:min_cost_flow",
@@ -49,6 +51,8 @@ def code_sample_cc(name):
"//ortools/graph:assignment",
"//ortools/graph:bounded_dijkstra",
"//ortools/graph:bfs",
"//ortools/graph:dag_constrained_shortest_path",
"//ortools/graph:dag_shortest_path",
"//ortools/graph:linear_assignment",
"//ortools/graph:max_flow",
"//ortools/graph:min_cost_flow",

View File

@@ -0,0 +1,139 @@
// 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.
// [START imports]
#include <cstdint>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/graph/dag_constrained_shortest_path.h"
#include "ortools/graph/graph.h"
// [END imports]
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
// [START graph]
// Create a graph with n + 2 nodes, indexed from 0:
// * Node n is `source`
// * Node n+1 is `dest`
// * Nodes M = [0, 1, ..., n-1] are in the middle.
//
// There is a single resource constraints with limit 1.
//
// The graph has 3 * n - 1 arcs (with weights and both resources):
// * (source -> i) with weight 100 and no resource use for i in M
// * (i -> dest) with weight 100 and no resource use for i in M
// * (i -> (i+1)) with weight 1 and resource use of 1 for i = 0, ..., n-2
//
// Every path [source, i, dest] for i in M is a constrained shortest path from
// source to dest with weight 200.
const int n = 5;
const int source = n;
const int dest = n + 1;
const int num_arcs = 3 * n - 1;
util::StaticGraph<> graph;
// There are 3 types of arcs: (1) source to M, (2) M to dest, and (3) within
// M. This vector stores all of them, first of type (1), then type (2),
// then type (3). The arcs are ordered by i in M within each type.
std::vector<double> weights(num_arcs);
// Resources are first indexed by resource, then by arc.
std::vector<std::vector<double>> resources(1, std::vector<double>(num_arcs));
for (int i = 0; i < n; ++i) {
graph.AddArc(source, i);
weights[i] = 100.0;
resources[0][i] = 0.0;
}
for (int i = 0; i < n; ++i) {
graph.AddArc(i, dest);
weights[n + i] = 100.0;
resources[0][n + i] = 0.0;
}
for (int i = 0; i + 1 < n; ++i) {
graph.AddArc(i, i + 1);
weights[2 * n + i] = 1.0;
resources[0][2 * n + i] = 1.0;
}
// Static graph reorders the arcs at Build() time, use permutation to get from
// the old ordering to the new one.
std::vector<int32_t> permutation;
graph.Build(&permutation);
util::Permute(permutation, &weights);
util::Permute(permutation, &resources[0]);
// [END graph]
// [START first-path]
// A reusable shortest path calculator.
// We need a topological order. For this structured graph, we find it by hand
// instead of using util::graph::FastTopologicalSort().
std::vector<int32_t> topological_order = {source};
for (int32_t i = 0; i < n; ++i) {
topological_order.push_back(i);
}
topological_order.push_back(dest);
const std::vector<int> sources = {source};
const std::vector<int> destinations = {dest};
const std::vector<double> max_resources = {1.0};
operations_research::ConstrainedShortestPathsOnDagWrapper<util::StaticGraph<>>
constrained_shortest_path_on_dag(&graph, &weights, &resources,
topological_order, sources, destinations,
&max_resources);
operations_research::GraphPathWithLength<util::StaticGraph<>>
initial_constrained_shortest_path =
constrained_shortest_path_on_dag.RunConstrainedShortestPathOnDag();
std::cout << "Initial distance: " << initial_constrained_shortest_path.length
<< std::endl;
std::cout << "Initial path: "
<< absl::StrJoin(initial_constrained_shortest_path.node_path, ", ")
<< std::endl;
// [END first-path]
// [START more-paths]
// Now, we make a single arc from source to M free, and a single arc from M
// to dest free, and resolve. If the free edge from the source hits before
// the free edge to the dest in M, we use both, walking through M. Otherwise,
// we use only one free arc.
std::vector<std::pair<int, int>> fast_paths = {{2, 3}, {8, 1}, {3, 7}};
for (const auto [free_from_source, free_to_dest] : fast_paths) {
weights[permutation[free_from_source]] = 0;
weights[permutation[n + free_to_dest]] = 0;
operations_research::GraphPathWithLength<util::StaticGraph<>>
constrained_shortest_path =
constrained_shortest_path_on_dag.RunConstrainedShortestPathOnDag();
std::cout << "source -> " << free_from_source << " and " << free_to_dest
<< " -> dest are now free" << std::endl;
std::string label = absl::StrCat("_", free_from_source, "_", free_to_dest);
std::cout << "Distance" << label << ": " << constrained_shortest_path.length
<< std::endl;
std::cout << "Path" << label << ": "
<< absl::StrJoin(constrained_shortest_path.node_path, ", ")
<< std::endl;
// Restore the old weights
weights[permutation[free_from_source]] = 100;
weights[permutation[n + free_to_dest]] = 100;
}
// [END more-paths]
return 0;
}

View File

@@ -0,0 +1,87 @@
// 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 <cstdint>
#include <iostream>
#include <vector>
#include "absl/log/check.h"
#include "absl/status/status.h"
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/base/status_macros.h"
#include "ortools/graph/dag_shortest_path.h"
#include "ortools/graph/graph.h"
#include "ortools/graph/topologicalsorter.h"
namespace {
absl::Status Main() {
util::StaticGraph<> graph;
std::vector<double> weights;
graph.AddArc(0, 1);
weights.push_back(2.0);
graph.AddArc(0, 2);
weights.push_back(5.0);
graph.AddArc(1, 4);
weights.push_back(1.0);
graph.AddArc(2, 4);
weights.push_back(-3.0);
graph.AddArc(3, 4);
weights.push_back(0.0);
// Static graph reorders the arcs at Build() time, use permutation to get
// from the old ordering to the new one.
std::vector<int32_t> permutation;
graph.Build(&permutation);
util::Permute(permutation, &weights);
// We need a topological order. We can find it by hand on this small graph,
// e.g., {0, 1, 2, 3, 4}, but we demonstrate how to compute one instead.
ASSIGN_OR_RETURN(const std::vector<int32_t> topological_order,
util::graph::FastTopologicalSort(graph));
operations_research::KShortestPathsOnDagWrapper<util::StaticGraph<>>
shortest_paths_on_dag(&graph, &weights, topological_order,
/*path_count=*/2);
const int source = 0;
shortest_paths_on_dag.RunKShortestPathOnDag({source});
// For each node other than 0, print its distance and the shortest path.
for (int node = 1; node < 5; ++node) {
std::cout << "Node " << node << ":\n";
if (!shortest_paths_on_dag.IsReachable(node)) {
std::cout << "\tNo path to node " << node << std::endl;
continue;
}
const std::vector<double> lengths = shortest_paths_on_dag.LengthsTo(node);
const std::vector<std::vector<int32_t>> paths =
shortest_paths_on_dag.NodePathsTo(node);
for (int path_index = 0; path_index < lengths.size(); ++path_index) {
std::cout << "\t#" << (path_index + 1) << " shortest path to node "
<< node << " has length: " << lengths[path_index] << std::endl;
std::cout << "\t#" << (path_index + 1) << " shortest path to node "
<< node << " is: " << absl::StrJoin(paths[path_index], ", ")
<< std::endl;
}
}
return absl::OkStatus();
}
} // namespace
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
QCHECK_OK(Main());
return 0;
}

View File

@@ -0,0 +1,135 @@
// 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.
// [START imports]
#include <cstdint>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/graph/dag_shortest_path.h"
#include "ortools/graph/graph.h"
// [END imports]
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
// [START graph]
// Create a graph with n + 2 nodes, indexed from 0:
// * Node n is `source`
// * Node n+1 is `dest`
// * Nodes M = [0, 1, ..., n-1] are in the middle.
//
// The graph has 3 * n - 1 arcs (with weights):
// * (source -> i) with weight 100 + i for i in M
// * (i -> dest) with weight 100 + i for i in M
// * (i -> (i+1)) with weight 10 for i = 0, ..., n-2
const int n = 10;
const int source = n;
const int dest = n + 1;
util::StaticGraph<> graph;
// There are 3 types of arcs: (1) source to M, (2) M to dest, and (3) within
// M. This vector stores all of them, first of type (1), then type (2),
// then type (3). The arcs are ordered by i in M within each type.
std::vector<double> weights(3 * n - 1);
for (int i = 0; i < n; ++i) {
graph.AddArc(source, i);
weights[i] = 100.0 + i;
}
for (int i = 0; i < n; ++i) {
graph.AddArc(i, dest);
weights[n + i] = 100.0 + i;
}
for (int i = 0; i + 1 < n; ++i) {
graph.AddArc(i, i + 1);
weights[2 * n + i] = 10.0;
}
// Static graph reorders the arcs at Build() time, use permutation to get from
// the old ordering to the new one.
std::vector<int32_t> permutation;
graph.Build(&permutation);
util::Permute(permutation, &weights);
// [END graph]
// [START first-path]
// A reusable shortest path calculator.
// We need a topological order. For this structured graph, we find it by hand
// instead of using util::graph::FastTopologicalSort().
std::vector<int32_t> topological_order = {source};
for (int32_t i = 0; i < n; ++i) {
topological_order.push_back(i);
}
topological_order.push_back(dest);
operations_research::KShortestPathsOnDagWrapper<util::StaticGraph<>>
shortest_paths_on_dag(&graph, &weights, topological_order,
/*path_count=*/2);
shortest_paths_on_dag.RunKShortestPathOnDag({source});
const std::vector<double> initial_lengths =
shortest_paths_on_dag.LengthsTo(dest);
const std::vector<std::vector<int32_t>> initial_paths =
shortest_paths_on_dag.NodePathsTo(dest);
std::cout << "No free arcs" << std::endl;
for (int path_index = 0; path_index < initial_lengths.size(); ++path_index) {
std::cout << "\t#" << (path_index + 1)
<< " shortest path has length: " << initial_lengths[path_index]
<< std::endl;
std::cout << "\t#" << (path_index + 1) << " shortest path is: "
<< absl::StrJoin(initial_paths[path_index], ", ") << std::endl;
}
// [END first-path]
// [START more-paths]
// Now, we make a single arc from source to M free, and a single arc from M
// to dest free, and resolve. If the free edge from the source hits before
// the free edge to the dest in M, we use both, walking through M. Otherwise,
// we use only one free arc.
std::vector<std::pair<int, int>> fast_paths = {
{2, 4}, {8, 1}, {3, 3}, {0, 0}};
for (const auto [free_from_source, free_to_dest] : fast_paths) {
weights[permutation[free_from_source]] = 0;
weights[permutation[n + free_to_dest]] = 0;
shortest_paths_on_dag.RunKShortestPathOnDag({source});
std::cout << "source -> " << free_from_source << " and " << free_to_dest
<< " -> dest are now free" << std::endl;
std::string label =
absl::StrCat(" (", free_from_source, ", ", free_to_dest, ")");
const std::vector<double> lengths = shortest_paths_on_dag.LengthsTo(dest);
const std::vector<std::vector<int32_t>> paths =
shortest_paths_on_dag.NodePathsTo(dest);
for (int path_index = 0; path_index < lengths.size(); ++path_index) {
std::cout << "\t#" << (path_index + 1) << " shortest path" << label
<< " has length: " << lengths[path_index] << std::endl;
std::cout << "\t#" << (path_index + 1) << " shortest path" << label
<< " is: " << absl::StrJoin(paths[path_index], ", ")
<< std::endl;
}
// Restore the old weights
weights[permutation[free_from_source]] = 100 + free_from_source;
weights[permutation[n + free_to_dest]] = 100 + free_to_dest;
}
// [END more-paths]
return 0;
}

View File

@@ -0,0 +1,81 @@
// 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 <cstdint>
#include <iostream>
#include <vector>
#include "absl/log/check.h"
#include "absl/status/status.h"
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/base/status_macros.h"
#include "ortools/graph/dag_shortest_path.h"
#include "ortools/graph/graph.h"
#include "ortools/graph/topologicalsorter.h"
namespace {
absl::Status Main() {
util::StaticGraph<> graph;
std::vector<double> weights;
graph.AddArc(0, 2);
weights.push_back(5.0);
graph.AddArc(0, 3);
weights.push_back(4.0);
graph.AddArc(1, 3);
weights.push_back(1.0);
graph.AddArc(2, 4);
weights.push_back(-3.0);
graph.AddArc(3, 4);
weights.push_back(0.0);
// Static graph reorders the arcs at Build() time, use permutation to get
// from the old ordering to the new one.
std::vector<int32_t> permutation;
graph.Build(&permutation);
util::Permute(permutation, &weights);
// We need a topological order. We can find it by hand on this small graph,
// e.g., {0, 1, 2, 3, 4}, but we demonstrate how to compute one instead.
ASSIGN_OR_RETURN(const std::vector<int32_t> topological_order,
util::graph::FastTopologicalSort(graph));
operations_research::ShortestPathsOnDagWrapper<util::StaticGraph<>>
shortest_path_on_dag(&graph, &weights, topological_order);
const int source = 0;
shortest_path_on_dag.RunShortestPathOnDag({source});
// For each node other than 0, print its distance and the shortest path.
for (int i = 1; i < 5; ++i) {
if (shortest_path_on_dag.IsReachable(i)) {
std::cout << "Length of shortest path to node " << i << ": "
<< shortest_path_on_dag.LengthTo(i) << std::endl;
std::cout << "Shortest path to node " << i << ": "
<< absl::StrJoin(shortest_path_on_dag.NodePathTo(i), ", ")
<< std::endl;
} else {
std::cout << "No path to node: " << i << std::endl;
}
}
return absl::OkStatus();
}
} // namespace
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
QCHECK_OK(Main());
return 0;
}

View File

@@ -0,0 +1,120 @@
// 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.
// [START imports]
#include <cstdint>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/graph/dag_shortest_path.h"
#include "ortools/graph/graph.h"
// [END imports]
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
// [START graph]
// Create a graph with n + 2 nodes, indexed from 0:
// * Node n is `source`
// * Node n+1 is `dest`
// * Nodes M = [0, 1, ..., n-1] are in the middle.
//
// The graph has 3 * n - 1 arcs (with weights):
// * (source -> i) with weight 100 for i in M
// * (i -> dest) with weight 100 for i in M
// * (i -> (i+1)) with weight 1 for i = 0, ..., n-2
//
// Every path [source, i, dest] for i in M is a shortest path from source to
// dest with weight 200.
const int n = 10;
const int source = n;
const int dest = n + 1;
util::StaticGraph<> graph;
// There are 3 types of arcs: (1) source to M, (2) M to dest, and (3) within
// M. This vector stores all of them, first of type (1), then type (2),
// then type (3). The arcs are ordered by i in M within each type.
std::vector<double> weights(3 * n - 1);
for (int i = 0; i < n; ++i) {
graph.AddArc(source, i);
weights[i] = 100.0;
}
for (int i = 0; i < n; ++i) {
graph.AddArc(i, dest);
weights[n + i] = 100.0;
}
for (int i = 0; i + 1 < n; ++i) {
graph.AddArc(i, i + 1);
weights[2 * n + i] = 1.0;
}
// Static graph reorders the arcs at Build() time, use permutation to get from
// the old ordering to the new one.
std::vector<int32_t> permutation;
graph.Build(&permutation);
util::Permute(permutation, &weights);
// [END graph]
// [START first-path]
// A reusable shortest path calculator.
// We need a topological order. For this structured graph, we find it by hand
// instead of using util::graph::FastTopologicalSort().
std::vector<int32_t> topological_order = {source};
for (int i = 0; i < n; ++i) {
topological_order.push_back(i);
}
topological_order.push_back(dest);
operations_research::ShortestPathsOnDagWrapper<util::StaticGraph<>>
shortest_path_on_dag(&graph, &weights, topological_order);
shortest_path_on_dag.RunShortestPathOnDag({source});
std::cout << "Initial distance: " << shortest_path_on_dag.LengthTo(dest)
<< std::endl;
std::cout << "Initial path: "
<< absl::StrJoin(shortest_path_on_dag.NodePathTo(dest), ", ")
<< std::endl;
// [END first-path]
// [START more-paths]
// Now, we make a single arc from source to M free, and a single arc from M
// to dest free, and resolve. If the free edge from the source hits before
// the free edge to the dest in M, we use both, walking through M. Otherwise,
// we use only one free arc.
std::vector<std::pair<int, int>> fast_paths = {{2, 4}, {8, 1}, {3, 7}};
for (const auto [free_from_source, free_to_dest] : fast_paths) {
weights[permutation[free_from_source]] = 0;
weights[permutation[n + free_to_dest]] = 0;
shortest_path_on_dag.RunShortestPathOnDag({source});
std::cout << "source -> " << free_from_source << " and " << free_to_dest
<< " -> dest are now free" << std::endl;
std::string label = absl::StrCat("_", free_from_source, "_", free_to_dest);
std::cout << "Distance" << label << ": "
<< shortest_path_on_dag.LengthTo(dest) << std::endl;
std::cout << "Path" << label << ": "
<< absl::StrJoin(shortest_path_on_dag.NodePathTo(dest), ", ")
<< std::endl;
// Restore the old weights
weights[permutation[free_from_source]] = 100;
weights[permutation[n + free_to_dest]] = 100;
}
// [END more-paths]
return 0;
}

View File

@@ -0,0 +1,47 @@
// 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 <iostream>
#include <vector>
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/graph/dag_constrained_shortest_path.h"
#include "ortools/graph/dag_shortest_path.h"
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
// The input graph, encoded as a list of arcs with distances.
std::vector<operations_research::ArcWithLengthAndResources> arcs = {
{.from = 0, .to = 1, .length = 5, .resources = {1, 2}},
{.from = 0, .to = 2, .length = 4, .resources = {3, 2}},
{.from = 0, .to = 2, .length = 1, .resources = {2, 3}},
{.from = 1, .to = 3, .length = -3, .resources = {8, 0}},
{.from = 2, .to = 3, .length = 0, .resources = {3, 1}}};
const int num_nodes = 4;
const std::vector<double> max_resources = {6, 3};
const int source = 0;
const int destination = 3;
const operations_research::PathWithLength path_with_length =
operations_research::ConstrainedShortestPathsOnDag(
num_nodes, arcs, source, destination, max_resources);
// Print to length of the path and then the nodes in the path.
std::cout << "Constrained shortest path length: " << path_with_length.length
<< std::endl;
std::cout << "Constrained shortest path nodes: "
<< absl::StrJoin(path_with_length.node_path, ", ") << std::endl;
return 0;
}

View File

@@ -0,0 +1,47 @@
// 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 <iostream>
#include <vector>
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/graph/dag_shortest_path.h"
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
// The input graph, encoded as a list of arcs with distances.
std::vector<operations_research::ArcWithLength> arcs = {
{.from = 0, .to = 1, .length = 2}, {.from = 0, .to = 2, .length = 5},
{.from = 0, .to = 3, .length = 4}, {.from = 1, .to = 4, .length = 1},
{.from = 2, .to = 4, .length = -3}, {.from = 3, .to = 4, .length = 0}};
const int num_nodes = 5;
const int source = 0;
const int destination = 4;
const int path_count = 2;
const std::vector<operations_research::PathWithLength> paths_with_length =
operations_research::KShortestPathsOnDag(num_nodes, arcs, source,
destination, path_count);
for (int path_index = 0; path_index < paths_with_length.size();
++path_index) {
std::cout << "#" << (path_index + 1) << " shortest path has length: "
<< paths_with_length[path_index].length << std::endl;
std::cout << "#" << (path_index + 1) << " shortest path is: "
<< absl::StrJoin(paths_with_length[path_index].node_path, ", ")
<< std::endl;
}
return 0;
}

View File

@@ -0,0 +1,44 @@
// 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 <iostream>
#include <vector>
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/graph/dag_shortest_path.h"
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
// The input graph, encoded as a list of arcs with distances.
std::vector<operations_research::ArcWithLength> arcs = {
{.from = 0, .to = 2, .length = 5},
{.from = 0, .to = 3, .length = 4},
{.from = 1, .to = 3, .length = 1},
{.from = 2, .to = 4, .length = -3},
{.from = 3, .to = 4, .length = 0}};
const int num_nodes = 5;
const int source = 0;
const int destination = 4;
const operations_research::PathWithLength path_with_length =
operations_research::ShortestPathsOnDag(num_nodes, arcs, source,
destination);
// Print to length of the path and then the nodes in the path.
std::cout << "Shortest path length: " << path_with_length.length << std::endl;
std::cout << "Shortest path nodes: "
<< absl::StrJoin(path_with_length.node_path, ", ") << std::endl;
return 0;
}