graph: add dag_* stuff
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
122
ortools/graph/dag_connectivity.cc
Normal file
122
ortools/graph/dag_connectivity.cc
Normal 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
|
||||
60
ortools/graph/dag_connectivity.h
Normal file
60
ortools/graph/dag_connectivity.h
Normal 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_
|
||||
182
ortools/graph/dag_connectivity_test.cc
Normal file
182
ortools/graph/dag_connectivity_test.cc
Normal 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
|
||||
95
ortools/graph/dag_constrained_shortest_path.cc
Normal file
95
ortools/graph/dag_constrained_shortest_path.cc
Normal 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
|
||||
932
ortools/graph/dag_constrained_shortest_path.h
Normal file
932
ortools/graph/dag_constrained_shortest_path.h
Normal 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_
|
||||
855
ortools/graph/dag_constrained_shortest_path_test.cc
Normal file
855
ortools/graph/dag_constrained_shortest_path_test.cc
Normal 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
|
||||
136
ortools/graph/dag_shortest_path.cc
Normal file
136
ortools/graph/dag_shortest_path.cc
Normal 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
|
||||
714
ortools/graph/dag_shortest_path.h
Normal file
714
ortools/graph/dag_shortest_path.h
Normal 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_
|
||||
1158
ortools/graph/dag_shortest_path_test.cc
Normal file
1158
ortools/graph/dag_shortest_path_test.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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")
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
135
ortools/graph/samples/dag_multiple_shortest_paths_sequential.cc
Normal file
135
ortools/graph/samples/dag_multiple_shortest_paths_sequential.cc
Normal 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;
|
||||
}
|
||||
81
ortools/graph/samples/dag_shortest_path_one_to_all.cc
Normal file
81
ortools/graph/samples/dag_shortest_path_one_to_all.cc
Normal 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;
|
||||
}
|
||||
120
ortools/graph/samples/dag_shortest_path_sequential.cc
Normal file
120
ortools/graph/samples/dag_shortest_path_sequential.cc
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
47
ortools/graph/samples/dag_simple_multiple_shortest_paths.cc
Normal file
47
ortools/graph/samples/dag_simple_multiple_shortest_paths.cc
Normal 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;
|
||||
}
|
||||
44
ortools/graph/samples/dag_simple_shortest_path.cc
Normal file
44
ortools/graph/samples/dag_simple_shortest_path.cc
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user