graph: remove all dag_*
This commit is contained in:
@@ -653,40 +653,6 @@ 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"],
|
||||
@@ -737,47 +703,6 @@ 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",
|
||||
":dag_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",
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
# Graph and Network Flows
|
||||
|
||||
This directory contains data structures and algorithms for graph and
|
||||
network flow problems.
|
||||
This directory contains data structures and algorithms for graph and network
|
||||
flow problems.
|
||||
|
||||
It contains in particular:
|
||||
|
||||
* well-tuned algorithms (for example, shortest paths and
|
||||
[Hamiltonian paths](https://en.wikipedia.org/wiki/Hamiltonian_path)).
|
||||
* hard-to-find algorithms (Hamiltonian paths, push-relabel flow algorithms).
|
||||
* other, more common algorithms, that are useful to use with `EbertGraph`.
|
||||
|
||||
Graph representations:
|
||||
|
||||
* [`ebert_graph.h`][ebert_graph_h]: entry point for a directed graph class.
|
||||
Deprecated. Prefer using [`//ortools/graph/graph.h`][graph_h].
|
||||
* other, more common algorithms, that are useful to use with graphs from
|
||||
`util/graph`.
|
||||
|
||||
Generic algorithms for shortest paths:
|
||||
|
||||
@@ -32,16 +28,6 @@ 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
|
||||
@@ -54,15 +40,15 @@ Specific algorithms for paths:
|
||||
Graph decompositions:
|
||||
|
||||
* [`connected_components.h`][connected_components_h]: entry point for computing
|
||||
connected components in an undirected graph. (It does not need `ebert_graph.h`
|
||||
or `digraph.h`.)
|
||||
connected components in an undirected graph. (It does not need an explicit
|
||||
graph class.)
|
||||
|
||||
* [`strongly_connected_components.h`][strongly_connected_components_h]: entry
|
||||
point for computing the strongly connected components of a directed graph.
|
||||
|
||||
* [`cliques.h`][cliques_h]: entry point for computing maximum cliques and
|
||||
clique covers in a directed graph, based on the Bron-Kerbosch algorithm. (It
|
||||
does not need `ebert_graph.h` or `digraph.h`.)
|
||||
clique covers in a directed graph, based on the Bron-Kerbosch algorithm.(It
|
||||
does not need an explicit graph class.)
|
||||
|
||||
Flow algorithms:
|
||||
|
||||
@@ -82,14 +68,14 @@ Flow algorithms:
|
||||
|
||||
## Wrappers
|
||||
|
||||
* [`python`](python): the SWIG code that makes the wrapper available in Python
|
||||
and its unit tests.
|
||||
* [`python`](python): the SWIG code that makes the wrapper available in Python
|
||||
and its unit tests.
|
||||
|
||||
* [`java`](java): the SWIG code that makes the wrapper available in Java
|
||||
and its unit tests.
|
||||
* [`java`](java): the SWIG code that makes the wrapper available in Java and
|
||||
its unit tests.
|
||||
|
||||
* [`csharp`](csharp): the SWIG code that makes the wrapper available in C#
|
||||
and its unit tests.
|
||||
* [`csharp`](csharp): the SWIG code that makes the wrapper available in C# and
|
||||
its unit tests.
|
||||
|
||||
## Samples
|
||||
|
||||
@@ -97,7 +83,6 @@ You can find some canonical examples in [`samples`][samples].
|
||||
|
||||
<!-- Links used throughout the document. -->
|
||||
|
||||
[ebert_graph_h]: ../graph/ebert_graph.h
|
||||
[graph_h]: ../graph/graph.h
|
||||
[bounded_dijkstra_h]: ../graph/bounded_dijkstra.h
|
||||
[bidirectional_dijkstra_h]: ../graph/bidirectional_dijkstra.h
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
// 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 <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 =
|
||||
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);
|
||||
|
||||
PathWithLength path_with_length =
|
||||
constrained_shortest_path_on_dag.RunConstrainedShortestPathOnDag();
|
||||
|
||||
ApplyMapping(inverse_permutation, path_with_length.arc_path);
|
||||
|
||||
return path_with_length;
|
||||
}
|
||||
|
||||
std::vector<int> GetInversePermutation(absl::Span<const int> permutation) {
|
||||
std::vector<int> inverse_permutation(permutation.size());
|
||||
if (!permutation.empty()) {
|
||||
for (int i = 0; i < permutation.size(); ++i) {
|
||||
inverse_permutation[permutation[i]] = i;
|
||||
}
|
||||
}
|
||||
return inverse_permutation;
|
||||
}
|
||||
|
||||
} // namespace operations_research
|
||||
@@ -1,901 +0,0 @@
|
||||
// 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.
|
||||
// -----------------------------------------------------------------------------
|
||||
// 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`.
|
||||
PathWithLength 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];
|
||||
};
|
||||
|
||||
std::vector<int> GetInversePermutation(absl::Span<const int> permutation);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// 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 =
|
||||
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
|
||||
PathWithLength 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 operations_research
|
||||
|
||||
#endif // OR_TOOLS_GRAPH_DAG_CONSTRAINED_SHORTEST_PATH_H_
|
||||
@@ -1,821 +0,0 @@
|
||||
// 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/dag_shortest_path.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(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.
|
||||
std::pair<util::StaticGraph<>, std::vector<int>> BuildRandomDag(
|
||||
const int64_t num_nodes, const int64_t num_arcs) {
|
||||
absl::BitGen bit_gen;
|
||||
CHECK_GE(num_nodes, 2);
|
||||
CHECK_GE(num_arcs, 1);
|
||||
CHECK_LE(num_arcs, (num_nodes * (num_nodes - 1)) / 2);
|
||||
std::vector<int> topological_order(num_nodes);
|
||||
topological_order.back() = num_nodes - 1;
|
||||
absl::Span<int> non_start_end =
|
||||
absl::MakeSpan(topological_order).subspan(1, num_nodes - 2);
|
||||
absl::c_iota(non_start_end, 1);
|
||||
absl::c_shuffle(non_start_end, bit_gen);
|
||||
int edges_added = 0;
|
||||
util::StaticGraph<> graph(num_nodes, num_arcs);
|
||||
graph.AddArc(0, num_nodes - 1);
|
||||
while (edges_added < num_arcs - 1) {
|
||||
int start_index = absl::Uniform(bit_gen, 0, num_nodes - 1);
|
||||
int end_index = absl::Uniform(bit_gen, start_index + 1, num_nodes);
|
||||
graph.AddArc(topological_order[start_index], topological_order[end_index]);
|
||||
edges_added++;
|
||||
}
|
||||
graph.Build();
|
||||
return {graph, topological_order};
|
||||
}
|
||||
|
||||
// The length of each arc is drawn uniformly at random within a given interval
|
||||
// except if the first arc from 0 to num_nodes-1 where it is set to
|
||||
// `start_to_end_value`.
|
||||
std::vector<double> GenerateRandomIntegerValues(
|
||||
const util::StaticGraph<>& 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 (util::StaticGraph<>::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 PathWithLength 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.
|
||||
// -----------------------------------------------------------------------------
|
||||
void BM_RandomDag(benchmark::State& state) {
|
||||
absl::BitGen bit_gen;
|
||||
// Generate a fixed random DAG.
|
||||
const int num_nodes = state.range(0);
|
||||
const int num_arcs = num_nodes * state.range(1);
|
||||
const auto [graph, topological_order] = BuildRandomDag(num_nodes, num_arcs);
|
||||
// Generate 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<int> sources = {0};
|
||||
const std::vector<int> destinations = {num_nodes - 1};
|
||||
const std::vector<double> max_resources = {num_nodes * 0.2};
|
||||
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(bit_gen, 0, num_scenarios)];
|
||||
const PathWithLength 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)
|
||||
->ArgPair(1 << 10, 16)
|
||||
->ArgPair(1 << 16, 4)
|
||||
->ArgPair(1 << 16, 16)
|
||||
->ArgPair(1 << 19, 4)
|
||||
->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 PathWithLength 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
|
||||
@@ -1,136 +0,0 @@
|
||||
// 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
|
||||
@@ -1,714 +0,0 @@
|
||||
// Copyright 2010-2025 Google LLC
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef OR_TOOLS_GRAPH_DAG_SHORTEST_PATH_H_
|
||||
#define OR_TOOLS_GRAPH_DAG_SHORTEST_PATH_H_
|
||||
|
||||
#include <cmath>
|
||||
#if __cplusplus >= 202002L
|
||||
#include <concepts>
|
||||
#endif
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/algorithm/container.h"
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/log/log.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/types/span.h"
|
||||
|
||||
namespace operations_research {
|
||||
// TODO(b/332475231): extend to non-floating lengths.
|
||||
// TODO(b/332476147): extend to allow for length functor.
|
||||
|
||||
// This library provides a few APIs to compute the shortest path on a given
|
||||
// directed acyclic graph (DAG).
|
||||
//
|
||||
// In the DAG, multiple arcs between the same pair of nodes is allowed. However,
|
||||
// self-loop arcs are not allowed.
|
||||
//
|
||||
// Note that we use the length formalism here, but the arc lengths can represent
|
||||
// any numeric physical quantity. A shortest path will just be a path minimizing
|
||||
// this quantity where the length of a path is the sum of the length of its
|
||||
// arcs. An arc length can be negative, or +inf (indicating that it should not
|
||||
// be used). An arc length cannot be -inf or nan.
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Basic API.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// `from` and `to` should both be in [0, num_nodes).
|
||||
// If the length is +inf, then the arc should not be used.
|
||||
struct ArcWithLength {
|
||||
int from = 0;
|
||||
int to = 0;
|
||||
double length = 0.0;
|
||||
};
|
||||
|
||||
struct PathWithLength {
|
||||
double length = 0.0;
|
||||
// The returned arc indices points into the `arcs_with_length` passed to the
|
||||
// function below.
|
||||
std::vector<int> arc_path;
|
||||
std::vector<int> node_path; // includes the source node.
|
||||
};
|
||||
|
||||
// Returns {+inf, {}, {}} if there is no path of finite length from the source
|
||||
// to the destination. Dies if `arcs_with_length` has a cycle.
|
||||
PathWithLength ShortestPathsOnDag(
|
||||
int num_nodes, absl::Span<const ArcWithLength> arcs_with_length, int source,
|
||||
int destination);
|
||||
|
||||
// Returns the k-shortest paths by increasing length. Returns fewer than k paths
|
||||
// if there are fewer than k paths from the source to the destination. Returns
|
||||
// {{+inf, {}, {}}} if there is no path of finite length from the source to the
|
||||
// destination. Dies if `arcs_with_length` has a cycle.
|
||||
std::vector<PathWithLength> KShortestPathsOnDag(
|
||||
int num_nodes, absl::Span<const ArcWithLength> arcs_with_length, int source,
|
||||
int destination, int path_count);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Advanced API.
|
||||
// -----------------------------------------------------------------------------
|
||||
// This concept only enforces the standard graph API needed for all algorithms
|
||||
// on DAGs. One could add the requirement of being a DAG wihtin this concept
|
||||
// (which is done before running the algorithm).
|
||||
#if __cplusplus >= 202002L
|
||||
template <class GraphType>
|
||||
concept DagGraphType = requires(GraphType graph) {
|
||||
{ typename GraphType::NodeIndex{} };
|
||||
{ typename GraphType::ArcIndex{} };
|
||||
{ graph.num_nodes() } -> std::same_as<typename GraphType::NodeIndex>;
|
||||
{ graph.num_arcs() } -> std::same_as<typename GraphType::ArcIndex>;
|
||||
{ graph.OutgoingArcs(typename GraphType::NodeIndex{}) };
|
||||
{
|
||||
graph.Tail(typename GraphType::ArcIndex{})
|
||||
} -> std::same_as<typename GraphType::NodeIndex>;
|
||||
{
|
||||
graph.Head(typename GraphType::ArcIndex{})
|
||||
} -> std::same_as<typename GraphType::NodeIndex>;
|
||||
{ graph.Build() };
|
||||
};
|
||||
#endif
|
||||
|
||||
// A wrapper that holds the memory needed to run many shortest path computations
|
||||
// efficiently on the given DAG. One call of `RunShortestPathOnDag()` has time
|
||||
// complexity O(|E| + |V|) and space complexity O(|V|).
|
||||
// `GraphType` can use any of the interfaces defined in `util/graph/graph.h`.
|
||||
template <class GraphType>
|
||||
#if __cplusplus >= 202002L
|
||||
requires DagGraphType<GraphType>
|
||||
#endif
|
||||
class ShortestPathsOnDagWrapper {
|
||||
public:
|
||||
using NodeIndex = typename GraphType::NodeIndex;
|
||||
using ArcIndex = typename GraphType::ArcIndex;
|
||||
|
||||
// IMPORTANT: All arguments must outlive the class.
|
||||
//
|
||||
// The vector of `arc_lengths` *must* be of size `graph.num_arcs()` and
|
||||
// indexed the same way as in `graph`.
|
||||
//
|
||||
// You *must* provide a topological order. You can use
|
||||
// `util::graph::FastTopologicalSort(graph)` to compute one if you don't
|
||||
// already have one. An invalid topological order results in an upper bound
|
||||
// for all shortest path computations. For maximum performance, you can
|
||||
// further reindex the nodes under the topological order so that the memory
|
||||
// access pattern is generally forward instead of random. For example, if the
|
||||
// topological order for a graph with 4 nodes is [2,1,0,3], you can re-label
|
||||
// the nodes 2, 1, and 0 to 0, 1, and 2 (and updates arcs accordingly).
|
||||
//
|
||||
// Validity of arcs and topological order are CHECKed if compiled in DEBUG
|
||||
// mode.
|
||||
//
|
||||
// SUBTLE: You can modify the graph, the arc lengths or the topological order
|
||||
// between calls to the `RunShortestPathOnDag()` function. That's fine. Doing
|
||||
// so will obviously invalidate the result API of the last shortest path run,
|
||||
// which could return an upper bound, junk, or crash.
|
||||
ShortestPathsOnDagWrapper(const GraphType* graph,
|
||||
const std::vector<double>* arc_lengths,
|
||||
absl::Span<const NodeIndex> topological_order);
|
||||
|
||||
// Computes the shortest path to all reachable nodes from the given sources.
|
||||
// This must be called before any of the query functions below.
|
||||
void RunShortestPathOnDag(absl::Span<const NodeIndex> sources);
|
||||
|
||||
// Returns true if `node` is reachable from at least one source, i.e., the
|
||||
// length from at least one source is finite.
|
||||
bool IsReachable(NodeIndex node) const;
|
||||
const std::vector<NodeIndex>& reached_nodes() const { return reached_nodes_; }
|
||||
|
||||
// Returns the length of the shortest path from `node`'s source to `node`.
|
||||
double LengthTo(NodeIndex node) const { return length_from_sources_[node]; }
|
||||
std::vector<double> LengthTo() const { return length_from_sources_; }
|
||||
|
||||
// Returns the list of all the arcs in the shortest path from `node`'s
|
||||
// source to `node`. CHECKs if the node is reachable.
|
||||
std::vector<ArcIndex> ArcPathTo(NodeIndex node) const;
|
||||
|
||||
// Returns the list of all the nodes in the shortest path from `node`'s
|
||||
// source to `node` (including the source). CHECKs if the node is reachable.
|
||||
std::vector<NodeIndex> NodePathTo(NodeIndex node) const;
|
||||
|
||||
// Accessors to the underlying graph and arc lengths.
|
||||
const GraphType& graph() const { return *graph_; }
|
||||
const std::vector<double>& arc_lengths() const { return *arc_lengths_; }
|
||||
|
||||
private:
|
||||
static constexpr double kInf = std::numeric_limits<double>::infinity();
|
||||
const GraphType* const graph_;
|
||||
const std::vector<double>* const arc_lengths_;
|
||||
absl::Span<const NodeIndex> const topological_order_;
|
||||
|
||||
// Data about the last call of the RunShortestPathOnDag() function.
|
||||
std::vector<double> length_from_sources_;
|
||||
std::vector<ArcIndex> incoming_shortest_path_arc_;
|
||||
std::vector<NodeIndex> reached_nodes_;
|
||||
};
|
||||
|
||||
// A wrapper that holds the memory needed to run many k-shortest paths
|
||||
// computations efficiently on the given DAG. One call of
|
||||
// `RunKShortestPathOnDag()` has time complexity O(|E| + k|V|log(d)) where d is
|
||||
// the mean degree of the graph and space complexity O(k|V|).
|
||||
// `GraphType` can use any of the interfaces defined in `util/graph/graph.h`.
|
||||
// IMPORTANT: Only use if `path_count > 1` (k > 1) otherwise use
|
||||
// `ShortestPathsOnDagWrapper`.
|
||||
template <class GraphType>
|
||||
#if __cplusplus >= 202002L
|
||||
requires DagGraphType<GraphType>
|
||||
#endif
|
||||
class KShortestPathsOnDagWrapper {
|
||||
public:
|
||||
using NodeIndex = typename GraphType::NodeIndex;
|
||||
using ArcIndex = typename GraphType::ArcIndex;
|
||||
|
||||
// IMPORTANT: All arguments must outlive the class.
|
||||
//
|
||||
// The vector of `arc_lengths` *must* be of size `graph.num_arcs()` and
|
||||
// indexed the same way as in `graph`.
|
||||
//
|
||||
// You *must* provide a topological order. You can use
|
||||
// `util::graph::FastTopologicalSort(graph)` to compute one if you don't
|
||||
// already have one. An invalid topological order results in an upper bound
|
||||
// for all shortest path computations. For maximum performance, you can
|
||||
// further reindex the nodes under the topological order so that the memory
|
||||
// access pattern is generally forward instead of random. For example, if the
|
||||
// topological order for a graph with 4 nodes is [2,1,0,3], you can re-label
|
||||
// the nodes 2, 1, and 0 to 0, 1, and 2 (and updates arcs accordingly).
|
||||
//
|
||||
// Validity of arcs and topological order are CHECKed if compiled in DEBUG
|
||||
// mode.
|
||||
//
|
||||
// SUBTLE: You can modify the graph, the arc lengths or the topological order
|
||||
// between calls to the `RunKShortestPathOnDag()` function. That's fine. Doing
|
||||
// so will obviously invalidate the result API of the last shortest path run,
|
||||
// which could return an upper bound, junk, or crash.
|
||||
KShortestPathsOnDagWrapper(const GraphType* graph,
|
||||
const std::vector<double>* arc_lengths,
|
||||
absl::Span<const NodeIndex> topological_order,
|
||||
int path_count);
|
||||
|
||||
// Computes the shortest path to all reachable nodes from the given sources.
|
||||
// This must be called before any of the query functions below.
|
||||
void RunKShortestPathOnDag(absl::Span<const NodeIndex> sources);
|
||||
|
||||
// Returns true if `node` is reachable from at least one source, i.e., the
|
||||
// length of the shortest path from at least one source is finite.
|
||||
bool IsReachable(NodeIndex node) const;
|
||||
const std::vector<NodeIndex>& reached_nodes() const { return reached_nodes_; }
|
||||
|
||||
// Returns the lengths of the k-shortest paths from `node`'s source to `node`
|
||||
// in increasing order. If there are less than k paths, return all path
|
||||
// lengths.
|
||||
std::vector<double> LengthsTo(NodeIndex node) const;
|
||||
|
||||
// Returns the list of all the arcs of the k-shortest paths from `node`'s
|
||||
// source to `node`.
|
||||
std::vector<std::vector<ArcIndex>> ArcPathsTo(NodeIndex node) const;
|
||||
|
||||
// Returns the list of all the nodes of the k-shortest paths from `node`'s
|
||||
// source to `node` (including the source). CHECKs if the node is reachable.
|
||||
std::vector<std::vector<NodeIndex>> NodePathsTo(NodeIndex node) const;
|
||||
|
||||
// Accessors to the underlying graph and arc lengths.
|
||||
const GraphType& graph() const { return *graph_; }
|
||||
const std::vector<double>& arc_lengths() const { return *arc_lengths_; }
|
||||
int path_count() const { return path_count_; }
|
||||
|
||||
private:
|
||||
static constexpr double kInf = std::numeric_limits<double>::infinity();
|
||||
|
||||
const GraphType* const graph_;
|
||||
const std::vector<double>* const arc_lengths_;
|
||||
absl::Span<const NodeIndex> const topological_order_;
|
||||
const int path_count_;
|
||||
|
||||
GraphType reverse_graph_;
|
||||
// Maps reverse arc indices to indices in the original graph.
|
||||
std::vector<ArcIndex> arc_indices_;
|
||||
|
||||
// Data about the last call of the `RunKShortestPathOnDag()` function. The
|
||||
// first dimension is the index of the path (1st being the shortest). The
|
||||
// second dimension are nodes.
|
||||
std::vector<std::vector<double>> lengths_from_sources_;
|
||||
std::vector<std::vector<ArcIndex>> incoming_shortest_paths_arc_;
|
||||
std::vector<std::vector<int>> incoming_shortest_paths_index_;
|
||||
std::vector<bool> is_source_;
|
||||
std::vector<NodeIndex> reached_nodes_;
|
||||
};
|
||||
|
||||
template <class GraphType>
|
||||
#if __cplusplus >= 202002L
|
||||
requires DagGraphType<GraphType>
|
||||
#endif
|
||||
absl::Status TopologicalOrderIsValid(
|
||||
const GraphType& graph,
|
||||
absl::Span<const typename GraphType::NodeIndex> topological_order);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Implementations.
|
||||
// -----------------------------------------------------------------------------
|
||||
// TODO(b/332475804): If `ArcPathTo` and/or `NodePathTo` functions become
|
||||
// bottlenecks:
|
||||
// (1) have the class preallocate a buffer of size `num_nodes`
|
||||
// (2) assign into an index rather than with push_back
|
||||
// (3) return by absl::Span (or return a copy) with known size.
|
||||
template <typename GraphType>
|
||||
#if __cplusplus >= 202002L
|
||||
requires DagGraphType<GraphType>
|
||||
#endif
|
||||
std::vector<typename GraphType::NodeIndex> NodePathImpliedBy(
|
||||
absl::Span<const typename GraphType::ArcIndex> arc_path,
|
||||
const GraphType& graph) {
|
||||
CHECK(!arc_path.empty());
|
||||
std::vector<typename GraphType::NodeIndex> node_path;
|
||||
node_path.reserve(arc_path.size() + 1);
|
||||
for (const typename GraphType::ArcIndex arc_index : arc_path) {
|
||||
node_path.push_back(graph.Tail(arc_index));
|
||||
}
|
||||
node_path.push_back(graph.Head(arc_path.back()));
|
||||
return node_path;
|
||||
}
|
||||
|
||||
template <class GraphType>
|
||||
#if __cplusplus >= 202002L
|
||||
requires DagGraphType<GraphType>
|
||||
#endif
|
||||
void CheckNodeIsValid(typename GraphType::NodeIndex node,
|
||||
const GraphType& graph) {
|
||||
CHECK_GE(node, 0) << "Node must be nonnegative. Input value: " << node;
|
||||
CHECK_LT(node, graph.num_nodes())
|
||||
<< "Node must be a valid node. Input value: " << node
|
||||
<< ". Number of nodes in the input graph: " << graph.num_nodes();
|
||||
}
|
||||
|
||||
template <class GraphType>
|
||||
#if __cplusplus >= 202002L
|
||||
requires DagGraphType<GraphType>
|
||||
#endif
|
||||
absl::Status TopologicalOrderIsValid(
|
||||
const GraphType& graph,
|
||||
absl::Span<const typename GraphType::NodeIndex> topological_order) {
|
||||
using NodeIndex = typename GraphType::NodeIndex;
|
||||
using ArcIndex = typename GraphType::ArcIndex;
|
||||
const NodeIndex num_nodes = graph.num_nodes();
|
||||
if (topological_order.size() != num_nodes) {
|
||||
return absl::InvalidArgumentError(absl::StrFormat(
|
||||
"topological_order.size() = %i, != graph.num_nodes() = %i",
|
||||
topological_order.size(), num_nodes));
|
||||
}
|
||||
std::vector<NodeIndex> inverse_topology(num_nodes, -1);
|
||||
for (NodeIndex node = 0; node < topological_order.size(); ++node) {
|
||||
if (inverse_topology[topological_order[node]] >= 0) {
|
||||
return absl::InvalidArgumentError(
|
||||
absl::StrFormat("node % i appears twice in topological order",
|
||||
topological_order[node]));
|
||||
}
|
||||
inverse_topology[topological_order[node]] = node;
|
||||
}
|
||||
for (NodeIndex tail = 0; tail < num_nodes; ++tail) {
|
||||
for (const ArcIndex arc : graph.OutgoingArcs(tail)) {
|
||||
const NodeIndex head = graph.Head(arc);
|
||||
if (inverse_topology[tail] >= inverse_topology[head]) {
|
||||
return absl::InvalidArgumentError(absl::StrFormat(
|
||||
"arc (%i, %i) is inconsistent with topological order", tail, head));
|
||||
}
|
||||
}
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// ShortestPathsOnDagWrapper implementation.
|
||||
// -----------------------------------------------------------------------------
|
||||
template <class GraphType>
|
||||
#if __cplusplus >= 202002L
|
||||
requires DagGraphType<GraphType>
|
||||
#endif
|
||||
ShortestPathsOnDagWrapper<GraphType>::ShortestPathsOnDagWrapper(
|
||||
const GraphType* graph, const std::vector<double>* arc_lengths,
|
||||
absl::Span<const NodeIndex> topological_order)
|
||||
: graph_(graph),
|
||||
arc_lengths_(arc_lengths),
|
||||
topological_order_(topological_order) {
|
||||
CHECK(graph_ != nullptr);
|
||||
CHECK(arc_lengths_ != nullptr);
|
||||
CHECK_GT(graph_->num_nodes(), 0) << "The graph is empty: it has no nodes";
|
||||
CHECK_GT(graph_->num_arcs(), 0) << "The graph is empty: it has no arcs";
|
||||
#ifndef NDEBUG
|
||||
CHECK_EQ(arc_lengths_->size(), graph_->num_arcs());
|
||||
for (const double arc_length : *arc_lengths_) {
|
||||
CHECK(arc_length != -kInf && !std::isnan(arc_length))
|
||||
<< absl::StrFormat("length cannot be -inf nor NaN");
|
||||
}
|
||||
CHECK_OK(TopologicalOrderIsValid(*graph_, topological_order_))
|
||||
<< "Invalid topological order";
|
||||
#endif
|
||||
|
||||
// Memory allocation is done here and only once in order to avoid reallocation
|
||||
// at each call of `RunShortestPathOnDag()` for better performance.
|
||||
length_from_sources_.resize(graph_->num_nodes(), kInf);
|
||||
incoming_shortest_path_arc_.resize(graph_->num_nodes(), -1);
|
||||
reached_nodes_.reserve(graph_->num_nodes());
|
||||
}
|
||||
|
||||
template <class GraphType>
|
||||
#if __cplusplus >= 202002L
|
||||
requires DagGraphType<GraphType>
|
||||
#endif
|
||||
void ShortestPathsOnDagWrapper<GraphType>::RunShortestPathOnDag(
|
||||
absl::Span<const NodeIndex> sources) {
|
||||
// Caching the vector addresses allow to not fetch it on each access.
|
||||
const absl::Span<double> length_from_sources =
|
||||
absl::MakeSpan(length_from_sources_);
|
||||
const absl::Span<const double> arc_lengths = *arc_lengths_;
|
||||
|
||||
// Avoid reassigning `incoming_shortest_path_arc_` at every call for better
|
||||
// performance, so it only makes sense for nodes that are reachable from at
|
||||
// least one source, the other ones will contain junk.
|
||||
for (const NodeIndex node : reached_nodes_) {
|
||||
length_from_sources[node] = kInf;
|
||||
}
|
||||
DCHECK(std::all_of(length_from_sources.begin(), length_from_sources.end(),
|
||||
[](double l) { return l == kInf; }));
|
||||
reached_nodes_.clear();
|
||||
|
||||
for (const NodeIndex source : sources) {
|
||||
CheckNodeIsValid(source, *graph_);
|
||||
length_from_sources[source] = 0.0;
|
||||
}
|
||||
|
||||
for (const NodeIndex tail : topological_order_) {
|
||||
const double length_to_tail = length_from_sources[tail];
|
||||
// Stop exploring a node as soon as its length to all sources is +inf.
|
||||
if (length_to_tail == kInf) {
|
||||
continue;
|
||||
}
|
||||
reached_nodes_.push_back(tail);
|
||||
for (const ArcIndex arc : graph_->OutgoingArcs(tail)) {
|
||||
const NodeIndex head = graph_->Head(arc);
|
||||
DCHECK(arc_lengths[arc] != -kInf);
|
||||
const double length_to_head = arc_lengths[arc] + length_to_tail;
|
||||
if (length_to_head < length_from_sources[head]) {
|
||||
length_from_sources[head] = length_to_head;
|
||||
incoming_shortest_path_arc_[head] = arc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class GraphType>
|
||||
#if __cplusplus >= 202002L
|
||||
requires DagGraphType<GraphType>
|
||||
#endif
|
||||
bool ShortestPathsOnDagWrapper<GraphType>::IsReachable(NodeIndex node) const {
|
||||
CheckNodeIsValid(node, *graph_);
|
||||
return length_from_sources_[node] < kInf;
|
||||
}
|
||||
|
||||
template <class GraphType>
|
||||
#if __cplusplus >= 202002L
|
||||
requires DagGraphType<GraphType>
|
||||
#endif
|
||||
std::vector<typename GraphType::ArcIndex>
|
||||
ShortestPathsOnDagWrapper<GraphType>::ArcPathTo(NodeIndex node) const {
|
||||
CHECK(IsReachable(node));
|
||||
std::vector<ArcIndex> arc_path;
|
||||
NodeIndex current_node = node;
|
||||
for (int i = 0; i < graph_->num_nodes(); ++i) {
|
||||
ArcIndex current_arc = incoming_shortest_path_arc_[current_node];
|
||||
if (current_arc == -1) {
|
||||
break;
|
||||
}
|
||||
arc_path.push_back(current_arc);
|
||||
current_node = graph_->Tail(current_arc);
|
||||
}
|
||||
absl::c_reverse(arc_path);
|
||||
return arc_path;
|
||||
}
|
||||
|
||||
template <class GraphType>
|
||||
#if __cplusplus >= 202002L
|
||||
requires DagGraphType<GraphType>
|
||||
#endif
|
||||
std::vector<typename GraphType::NodeIndex>
|
||||
ShortestPathsOnDagWrapper<GraphType>::NodePathTo(NodeIndex node) const {
|
||||
const std::vector<typename GraphType::ArcIndex> arc_path = ArcPathTo(node);
|
||||
if (arc_path.empty()) {
|
||||
return {node};
|
||||
}
|
||||
return NodePathImpliedBy(ArcPathTo(node), *graph_);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// KShortestPathsOnDagWrapper implementation.
|
||||
// -----------------------------------------------------------------------------
|
||||
template <class GraphType>
|
||||
#if __cplusplus >= 202002L
|
||||
requires DagGraphType<GraphType>
|
||||
#endif
|
||||
KShortestPathsOnDagWrapper<GraphType>::KShortestPathsOnDagWrapper(
|
||||
const GraphType* graph, const std::vector<double>* arc_lengths,
|
||||
absl::Span<const NodeIndex> topological_order, const int path_count)
|
||||
: graph_(graph),
|
||||
arc_lengths_(arc_lengths),
|
||||
topological_order_(topological_order),
|
||||
path_count_(path_count) {
|
||||
CHECK(graph_ != nullptr);
|
||||
CHECK(arc_lengths_ != nullptr);
|
||||
CHECK_GT(graph_->num_nodes(), 0) << "The graph is empty: it has no nodes";
|
||||
CHECK_GT(graph_->num_arcs(), 0) << "The graph is empty: it has no arcs";
|
||||
CHECK_GT(path_count_, 0) << "path_count must be greater than 0";
|
||||
#ifndef NDEBUG
|
||||
CHECK_EQ(arc_lengths_->size(), graph_->num_arcs());
|
||||
for (const double arc_length : *arc_lengths_) {
|
||||
CHECK(arc_length != -kInf && !std::isnan(arc_length))
|
||||
<< absl::StrFormat("length cannot be -inf nor NaN");
|
||||
}
|
||||
CHECK_OK(TopologicalOrderIsValid(*graph_, topological_order_))
|
||||
<< "Invalid topological order";
|
||||
#endif
|
||||
|
||||
// TODO(b/332475713): Optimize if reverse graph is already provided in
|
||||
// `GraphType`.
|
||||
const int num_arcs = graph_->num_arcs();
|
||||
reverse_graph_ = GraphType(graph_->num_nodes(), num_arcs);
|
||||
for (ArcIndex arc_index = 0; arc_index < num_arcs; ++arc_index) {
|
||||
reverse_graph_.AddArc(graph->Head(arc_index), graph->Tail(arc_index));
|
||||
}
|
||||
std::vector<ArcIndex> permutation;
|
||||
reverse_graph_.Build(&permutation);
|
||||
arc_indices_.resize(permutation.size());
|
||||
if (!permutation.empty()) {
|
||||
for (int i = 0; i < permutation.size(); ++i) {
|
||||
arc_indices_[permutation[i]] = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Memory allocation is done here and only once in order to avoid reallocation
|
||||
// at each call of `RunKShortestPathOnDag()` for better performance.
|
||||
lengths_from_sources_.resize(path_count_);
|
||||
incoming_shortest_paths_arc_.resize(path_count_);
|
||||
incoming_shortest_paths_index_.resize(path_count_);
|
||||
for (int k = 0; k < path_count_; ++k) {
|
||||
lengths_from_sources_[k].resize(graph_->num_nodes(), kInf);
|
||||
incoming_shortest_paths_arc_[k].resize(graph_->num_nodes(), -1);
|
||||
incoming_shortest_paths_index_[k].resize(graph_->num_nodes(), -1);
|
||||
}
|
||||
is_source_.resize(graph_->num_nodes(), false);
|
||||
reached_nodes_.reserve(graph_->num_nodes());
|
||||
}
|
||||
|
||||
template <class GraphType>
|
||||
#if __cplusplus >= 202002L
|
||||
requires DagGraphType<GraphType>
|
||||
#endif
|
||||
void KShortestPathsOnDagWrapper<GraphType>::RunKShortestPathOnDag(
|
||||
absl::Span<const NodeIndex> sources) {
|
||||
// Caching the vector addresses allow to not fetch it on each access.
|
||||
const absl::Span<const double> arc_lengths = *arc_lengths_;
|
||||
const absl::Span<const ArcIndex> arc_indices = arc_indices_;
|
||||
|
||||
// Avoid reassigning `incoming_shortest_path_arc_` at every call for better
|
||||
// performance, so it only makes sense for nodes that are reachable from at
|
||||
// least one source, the other ones will contain junk.
|
||||
|
||||
for (const NodeIndex node : reached_nodes_) {
|
||||
is_source_[node] = false;
|
||||
for (int k = 0; k < path_count_; ++k) {
|
||||
lengths_from_sources_[k][node] = kInf;
|
||||
}
|
||||
}
|
||||
reached_nodes_.clear();
|
||||
#ifndef NDEBUG
|
||||
for (int k = 0; k < path_count_; ++k) {
|
||||
CHECK(std::all_of(lengths_from_sources_[k].begin(),
|
||||
lengths_from_sources_[k].end(),
|
||||
[](double l) { return l == kInf; }));
|
||||
}
|
||||
#endif
|
||||
|
||||
for (const NodeIndex source : sources) {
|
||||
CheckNodeIsValid(source, *graph_);
|
||||
is_source_[source] = true;
|
||||
}
|
||||
|
||||
struct IncomingArcPath {
|
||||
double path_length = 0.0;
|
||||
ArcIndex arc_index = 0;
|
||||
double arc_length = 0.0;
|
||||
NodeIndex from = 0;
|
||||
int path_index = 0;
|
||||
|
||||
bool operator<(const IncomingArcPath& other) const {
|
||||
return std::tie(path_length, from) <
|
||||
std::tie(other.path_length, other.from);
|
||||
}
|
||||
bool operator>(const IncomingArcPath& other) const { return other < *this; }
|
||||
};
|
||||
std::vector<IncomingArcPath> min_heap;
|
||||
auto comp = std::greater<IncomingArcPath>();
|
||||
for (const NodeIndex to : topological_order_) {
|
||||
min_heap.clear();
|
||||
if (is_source_[to]) {
|
||||
min_heap.push_back({.arc_index = -1});
|
||||
}
|
||||
for (const ArcIndex reverse_arc_index : reverse_graph_.OutgoingArcs(to)) {
|
||||
const ArcIndex arc_index = arc_indices.empty()
|
||||
? reverse_arc_index
|
||||
: arc_indices[reverse_arc_index];
|
||||
const NodeIndex from = graph_->Tail(arc_index);
|
||||
const double arc_length = arc_lengths[arc_index];
|
||||
DCHECK(arc_length != -kInf);
|
||||
const double path_length =
|
||||
lengths_from_sources_.front()[from] + arc_length;
|
||||
if (path_length == kInf) {
|
||||
continue;
|
||||
}
|
||||
min_heap.push_back({.path_length = path_length,
|
||||
.arc_index = arc_index,
|
||||
.arc_length = arc_length,
|
||||
.from = from});
|
||||
std::push_heap(min_heap.begin(), min_heap.end(), comp);
|
||||
}
|
||||
if (min_heap.empty()) {
|
||||
continue;
|
||||
}
|
||||
reached_nodes_.push_back(to);
|
||||
for (int k = 0; k < path_count_; ++k) {
|
||||
std::pop_heap(min_heap.begin(), min_heap.end(), comp);
|
||||
IncomingArcPath& incoming_arc_path = min_heap.back();
|
||||
lengths_from_sources_[k][to] = incoming_arc_path.path_length;
|
||||
incoming_shortest_paths_arc_[k][to] = incoming_arc_path.arc_index;
|
||||
incoming_shortest_paths_index_[k][to] = incoming_arc_path.path_index;
|
||||
if (incoming_arc_path.arc_index != -1 &&
|
||||
incoming_arc_path.path_index < path_count_ - 1 &&
|
||||
lengths_from_sources_[incoming_arc_path.path_index + 1]
|
||||
[incoming_arc_path.from] < kInf) {
|
||||
++incoming_arc_path.path_index;
|
||||
incoming_arc_path.path_length =
|
||||
lengths_from_sources_[incoming_arc_path.path_index]
|
||||
[incoming_arc_path.from] +
|
||||
incoming_arc_path.arc_length;
|
||||
std::push_heap(min_heap.begin(), min_heap.end(), comp);
|
||||
} else {
|
||||
min_heap.pop_back();
|
||||
if (min_heap.empty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class GraphType>
|
||||
#if __cplusplus >= 202002L
|
||||
requires DagGraphType<GraphType>
|
||||
#endif
|
||||
bool KShortestPathsOnDagWrapper<GraphType>::IsReachable(NodeIndex node) const {
|
||||
CheckNodeIsValid(node, *graph_);
|
||||
return lengths_from_sources_.front()[node] < kInf;
|
||||
}
|
||||
|
||||
template <class GraphType>
|
||||
#if __cplusplus >= 202002L
|
||||
requires DagGraphType<GraphType>
|
||||
#endif
|
||||
std::vector<double> KShortestPathsOnDagWrapper<GraphType>::LengthsTo(
|
||||
NodeIndex node) const {
|
||||
std::vector<double> lengths_to;
|
||||
lengths_to.reserve(path_count_);
|
||||
for (int k = 0; k < path_count_; ++k) {
|
||||
const double length_to = lengths_from_sources_[k][node];
|
||||
if (length_to == kInf) {
|
||||
break;
|
||||
}
|
||||
lengths_to.push_back(length_to);
|
||||
}
|
||||
return lengths_to;
|
||||
}
|
||||
|
||||
template <class GraphType>
|
||||
#if __cplusplus >= 202002L
|
||||
requires DagGraphType<GraphType>
|
||||
#endif
|
||||
std::vector<std::vector<typename GraphType::ArcIndex>>
|
||||
KShortestPathsOnDagWrapper<GraphType>::ArcPathsTo(NodeIndex node) const {
|
||||
std::vector<std::vector<ArcIndex>> arc_paths;
|
||||
arc_paths.reserve(path_count_);
|
||||
for (int k = 0; k < path_count_; ++k) {
|
||||
if (lengths_from_sources_[k][node] == kInf) {
|
||||
break;
|
||||
}
|
||||
std::vector<ArcIndex> arc_path;
|
||||
int current_path_index = k;
|
||||
NodeIndex current_node = node;
|
||||
for (int i = 0; i < graph_->num_nodes(); ++i) {
|
||||
ArcIndex current_arc =
|
||||
incoming_shortest_paths_arc_[current_path_index][current_node];
|
||||
if (current_arc == -1) {
|
||||
break;
|
||||
}
|
||||
arc_path.push_back(current_arc);
|
||||
current_path_index =
|
||||
incoming_shortest_paths_index_[current_path_index][current_node];
|
||||
current_node = graph_->Tail(current_arc);
|
||||
}
|
||||
absl::c_reverse(arc_path);
|
||||
arc_paths.push_back(arc_path);
|
||||
}
|
||||
return arc_paths;
|
||||
}
|
||||
|
||||
template <class GraphType>
|
||||
#if __cplusplus >= 202002L
|
||||
requires DagGraphType<GraphType>
|
||||
#endif
|
||||
std::vector<std::vector<typename GraphType::NodeIndex>>
|
||||
KShortestPathsOnDagWrapper<GraphType>::NodePathsTo(NodeIndex node) const {
|
||||
const std::vector<std::vector<ArcIndex>> arc_paths = ArcPathsTo(node);
|
||||
std::vector<std::vector<NodeIndex>> node_paths(arc_paths.size());
|
||||
for (int k = 0; k < arc_paths.size(); ++k) {
|
||||
if (arc_paths[k].empty()) {
|
||||
node_paths[k] = {node};
|
||||
} else {
|
||||
node_paths[k] = NodePathImpliedBy(arc_paths[k], *graph_);
|
||||
}
|
||||
}
|
||||
return node_paths;
|
||||
}
|
||||
|
||||
} // namespace operations_research
|
||||
#endif // OR_TOOLS_GRAPH_DAG_SHORTEST_PATH_H_
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,9 @@
|
||||
// time. Its design is based on the experience acquired by the Operations
|
||||
// Research team in their various graph algorithm implementations.
|
||||
//
|
||||
// Also see README.md#basegraph for a more graphical documentation of the
|
||||
// concepts presented here.
|
||||
//
|
||||
// The main ideas are:
|
||||
// - Graph nodes and arcs are represented by integers.
|
||||
// - Node or arc annotations (weight, cost, ...) are not part of the graph
|
||||
@@ -363,7 +366,8 @@ class ListGraph : public BaseGraph<NodeIndexType, ArcIndexType, false> {
|
||||
BeginEndWrapper<OutgoingArcIterator> OutgoingArcs(NodeIndexType node) const;
|
||||
|
||||
// Advanced usage. Same as OutgoingArcs(), but allows to restart the iteration
|
||||
// from an already known outgoing arc of the given node.
|
||||
// from an already known outgoing arc of the given node. If `from` is
|
||||
// `kNilArc`, an empty range is returned.
|
||||
BeginEndWrapper<OutgoingArcIterator> OutgoingArcsStartingFrom(
|
||||
NodeIndexType node, ArcIndexType from) const;
|
||||
|
||||
@@ -431,9 +435,16 @@ class StaticGraph : public BaseGraph<NodeIndexType, ArcIndexType, false> {
|
||||
NodeIndexType Head(ArcIndexType arc) const;
|
||||
NodeIndexType Tail(ArcIndexType arc) const;
|
||||
ArcIndexType OutDegree(NodeIndexType node) const; // Work in O(1).
|
||||
BeginEndWrapper<OutgoingArcIterator> OutgoingArcs(NodeIndexType node) const;
|
||||
BeginEndWrapper<OutgoingArcIterator> OutgoingArcsStartingFrom(
|
||||
NodeIndexType node, ArcIndexType from) const;
|
||||
IntegerRange<ArcIndexType> OutgoingArcs(NodeIndexType node) const {
|
||||
return IntegerRange<ArcIndexType>(start_[node], DirectArcLimit(node));
|
||||
}
|
||||
IntegerRange<ArcIndexType> OutgoingArcsStartingFrom(NodeIndexType node,
|
||||
ArcIndexType from) const {
|
||||
DCHECK_GE(from, start_[node]);
|
||||
const ArcIndexType limit = DirectArcLimit(node);
|
||||
return IntegerRange<ArcIndexType>(from == Base::kNilArc ? limit : from,
|
||||
limit);
|
||||
}
|
||||
|
||||
// This loops over the heads of the OutgoingArcs(node). It is just a more
|
||||
// convenient way to achieve this. Moreover this interface is used by some
|
||||
@@ -598,14 +609,21 @@ class ReverseArcStaticGraph
|
||||
ArcIndexType OutDegree(NodeIndexType node) const;
|
||||
ArcIndexType InDegree(NodeIndexType node) const;
|
||||
|
||||
BeginEndWrapper<OutgoingArcIterator> OutgoingArcs(NodeIndexType node) const;
|
||||
IntegerRange<ArcIndexType> OutgoingArcs(NodeIndexType node) const {
|
||||
return IntegerRange<ArcIndexType>(start_[node], DirectArcLimit(node));
|
||||
}
|
||||
IntegerRange<ArcIndexType> OutgoingArcsStartingFrom(NodeIndexType node,
|
||||
ArcIndexType from) const {
|
||||
DCHECK_GE(from, start_[node]);
|
||||
const ArcIndexType limit = DirectArcLimit(node);
|
||||
return IntegerRange<ArcIndexType>(from == Base::kNilArc ? limit : from,
|
||||
limit);
|
||||
}
|
||||
BeginEndWrapper<IncomingArcIterator> IncomingArcs(NodeIndexType node) const;
|
||||
BeginEndWrapper<OutgoingOrOppositeIncomingArcIterator>
|
||||
OutgoingOrOppositeIncomingArcs(NodeIndexType node) const;
|
||||
BeginEndWrapper<OppositeIncomingArcIterator> OppositeIncomingArcs(
|
||||
NodeIndexType node) const;
|
||||
BeginEndWrapper<OutgoingArcIterator> OutgoingArcsStartingFrom(
|
||||
NodeIndexType node, ArcIndexType from) const;
|
||||
BeginEndWrapper<IncomingArcIterator> IncomingArcsStartingFrom(
|
||||
NodeIndexType node, ArcIndexType from) const;
|
||||
BeginEndWrapper<OutgoingOrOppositeIncomingArcIterator>
|
||||
@@ -1228,8 +1246,6 @@ StaticGraph<NodeIndexType, ArcIndexType>::FromArcs(NodeIndexType num_nodes,
|
||||
return g;
|
||||
}
|
||||
|
||||
DEFINE_RANGE_BASED_ARC_ITERATION(StaticGraph, Outgoing);
|
||||
|
||||
template <typename NodeIndexType, typename ArcIndexType>
|
||||
absl::Span<const NodeIndexType>
|
||||
StaticGraph<NodeIndexType, ArcIndexType>::operator[](NodeIndexType node) const {
|
||||
@@ -1377,6 +1393,7 @@ void StaticGraph<NodeIndexType, ArcIndexType>::Build(
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(b/385094969): Remove this class.
|
||||
template <typename NodeIndexType, typename ArcIndexType>
|
||||
class StaticGraph<NodeIndexType, ArcIndexType>::OutgoingArcIterator {
|
||||
public:
|
||||
@@ -1398,15 +1415,6 @@ class StaticGraph<NodeIndexType, ArcIndexType>::OutgoingArcIterator {
|
||||
index_++;
|
||||
}
|
||||
|
||||
// Note(user): we lose a bit by returning a BeginEndWrapper<> on top of
|
||||
// this iterator rather than a simple IntegerRange<> on the arc indices.
|
||||
// On my computer: around 420M arcs/sec instead of 440M arcs/sec.
|
||||
//
|
||||
// However, it is slightly more consistent to do it this way, and we don't
|
||||
// have two different codes depending on the way a client iterates on the
|
||||
// arcs.
|
||||
DEFINE_STL_ITERATOR_FUNCTIONS(OutgoingArcIterator);
|
||||
|
||||
private:
|
||||
ArcIndexType index_;
|
||||
ArcIndexType limit_;
|
||||
@@ -1669,7 +1677,6 @@ class ReverseArcListGraph<NodeIndexType, ArcIndexType>::OutgoingHeadIterator {
|
||||
|
||||
// ReverseArcStaticGraph implementation ----------------------------------------
|
||||
|
||||
DEFINE_RANGE_BASED_ARC_ITERATION(ReverseArcStaticGraph, Outgoing);
|
||||
DEFINE_RANGE_BASED_ARC_ITERATION(ReverseArcStaticGraph, Incoming);
|
||||
DEFINE_RANGE_BASED_ARC_ITERATION(ReverseArcStaticGraph,
|
||||
OutgoingOrOppositeIncoming);
|
||||
@@ -1798,6 +1805,7 @@ void ReverseArcStaticGraph<NodeIndexType, ArcIndexType>::Build(
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(b/385094969): Remove this class.
|
||||
template <typename NodeIndexType, typename ArcIndexType>
|
||||
class ReverseArcStaticGraph<NodeIndexType, ArcIndexType>::OutgoingArcIterator {
|
||||
public:
|
||||
@@ -1817,10 +1825,6 @@ class ReverseArcStaticGraph<NodeIndexType, ArcIndexType>::OutgoingArcIterator {
|
||||
index_++;
|
||||
}
|
||||
|
||||
// TODO(user): we lose a bit by returning a BeginEndWrapper<> on top of this
|
||||
// iterator rather than a simple IntegerRange on the arc indices.
|
||||
DEFINE_STL_ITERATOR_FUNCTIONS(OutgoingArcIterator);
|
||||
|
||||
private:
|
||||
ArcIndexType index_;
|
||||
const ArcIndexType limit_;
|
||||
|
||||
@@ -227,28 +227,6 @@ class IntegerRange : public BeginEndWrapper<IntegerRangeIterator<IntegerType>> {
|
||||
}
|
||||
};
|
||||
|
||||
// Allow iterating over a vector<T> as a mutable vector<T*>.
|
||||
template <class T>
|
||||
struct MutableVectorIteration {
|
||||
explicit MutableVectorIteration(std::vector<T>* v) : v_(v) {}
|
||||
struct Iterator {
|
||||
explicit Iterator(typename std::vector<T>::iterator it) : it_(it) {}
|
||||
T* operator*() { return &*it_; }
|
||||
Iterator& operator++() {
|
||||
it_++;
|
||||
return *this;
|
||||
}
|
||||
bool operator!=(const Iterator& other) const { return other.it_ != it_; }
|
||||
|
||||
private:
|
||||
typename std::vector<T>::iterator it_;
|
||||
};
|
||||
Iterator begin() { return Iterator(v_->begin()); }
|
||||
Iterator end() { return Iterator(v_->end()); }
|
||||
|
||||
private:
|
||||
std::vector<T>* const v_;
|
||||
};
|
||||
} // namespace util
|
||||
|
||||
#endif // UTIL_GRAPH_ITERATORS_H_
|
||||
|
||||
@@ -90,10 +90,7 @@ TYPED_TEST(LineGraphTest, LineGraph) {
|
||||
const typename TypeParam::NodeIndex expected_tail = kExpectedLineArcs[i][0];
|
||||
const typename TypeParam::NodeIndex expected_head = kExpectedLineArcs[i][1];
|
||||
bool found = false;
|
||||
for (typename TypeParam::OutgoingArcIterator out_iterator(line_graph,
|
||||
expected_tail);
|
||||
out_iterator.Ok(); out_iterator.Next()) {
|
||||
const typename TypeParam::ArcIndex arc = out_iterator.Index();
|
||||
for (const auto arc : line_graph.OutgoingArcs(expected_tail)) {
|
||||
if (line_graph.Head(arc) == expected_head) {
|
||||
found = true;
|
||||
break;
|
||||
|
||||
@@ -1227,8 +1227,10 @@ LinearSumAssignment<GraphType, CostValue>::BestArcAndGap(
|
||||
DCHECK(IsActive(left_node))
|
||||
<< "Node " << left_node << " must be active (unmatched)!";
|
||||
DCHECK_GT(epsilon_, 0);
|
||||
typename GraphType::OutgoingArcIterator arc_it(*graph_, left_node);
|
||||
ArcIndex best_arc = arc_it.Index();
|
||||
const auto arcs = graph_->OutgoingArcs(left_node);
|
||||
auto arc_it = arcs.begin();
|
||||
DCHECK(!arcs.empty());
|
||||
ArcIndex best_arc = *arc_it;
|
||||
CostValue min_partial_reduced_cost = PartialReducedCost(best_arc);
|
||||
// We choose second_min_partial_reduced_cost so that in the case of
|
||||
// the largest possible gap (which results from a left-side node
|
||||
@@ -1238,8 +1240,8 @@ LinearSumAssignment<GraphType, CostValue>::BestArcAndGap(
|
||||
const CostValue max_gap = slack_relabeling_price_ - epsilon_;
|
||||
CostValue second_min_partial_reduced_cost =
|
||||
min_partial_reduced_cost + max_gap;
|
||||
for (arc_it.Next(); arc_it.Ok(); arc_it.Next()) {
|
||||
const ArcIndex arc = arc_it.Index();
|
||||
for (++arc_it; arc_it != arcs.end(); ++arc_it) {
|
||||
const ArcIndex arc = *arc_it;
|
||||
const CostValue partial_reduced_cost = PartialReducedCost(arc);
|
||||
if (partial_reduced_cost < second_min_partial_reduced_cost) {
|
||||
if (partial_reduced_cost < min_partial_reduced_cost) {
|
||||
@@ -1266,26 +1268,27 @@ inline CostValue LinearSumAssignment<GraphType, CostValue>::ImplicitPrice(
|
||||
NodeIndex left_node) const {
|
||||
DCHECK_GT(num_left_nodes_, left_node);
|
||||
DCHECK_GT(epsilon_, 0);
|
||||
typename GraphType::OutgoingArcIterator arc_it(*graph_, left_node);
|
||||
const auto arcs = graph_->OutgoingArcs(left_node);
|
||||
// We must not execute this method if left_node has no incident arc.
|
||||
DCHECK(arc_it.Ok());
|
||||
ArcIndex best_arc = arc_it.Index();
|
||||
DCHECK(!arcs.empty());
|
||||
auto arc_it = arcs.begin();
|
||||
ArcIndex best_arc = *arc_it;
|
||||
if (best_arc == matched_arc_[left_node]) {
|
||||
arc_it.Next();
|
||||
if (arc_it.Ok()) {
|
||||
best_arc = arc_it.Index();
|
||||
++arc_it;
|
||||
if (arc_it != arcs.end()) {
|
||||
best_arc = *arc_it;
|
||||
}
|
||||
}
|
||||
CostValue min_partial_reduced_cost = PartialReducedCost(best_arc);
|
||||
if (!arc_it.Ok()) {
|
||||
if (arc_it == arcs.end()) {
|
||||
// Only one arc is incident to left_node, and the node is
|
||||
// currently matched along that arc, which must be the case in any
|
||||
// feasible solution. Therefore we implicitly price this node so
|
||||
// low that we will never consider unmatching it.
|
||||
return -(min_partial_reduced_cost + slack_relabeling_price_);
|
||||
}
|
||||
for (arc_it.Next(); arc_it.Ok(); arc_it.Next()) {
|
||||
const ArcIndex arc = arc_it.Index();
|
||||
for (++arc_it; arc_it != arcs.end(); ++arc_it) {
|
||||
const ArcIndex arc = *arc_it;
|
||||
if (arc != matched_arc_[left_node]) {
|
||||
const CostValue partial_reduced_cost = PartialReducedCost(arc);
|
||||
if (partial_reduced_cost < min_partial_reduced_cost) {
|
||||
@@ -1314,9 +1317,7 @@ bool LinearSumAssignment<GraphType, CostValue>::EpsilonOptimal() const {
|
||||
// Get the implicit price of left_node and make sure the reduced
|
||||
// costs of left_node's incident arcs are in bounds.
|
||||
CostValue left_node_price = ImplicitPrice(left_node);
|
||||
for (typename GraphType::OutgoingArcIterator arc_it(*graph_, left_node);
|
||||
arc_it.Ok(); arc_it.Next()) {
|
||||
const ArcIndex arc = arc_it.Index();
|
||||
for (const ArcIndex arc : graph_->OutgoingArcs(left_node)) {
|
||||
const CostValue reduced_cost = left_node_price + PartialReducedCost(arc);
|
||||
// Note the asymmetric definition of epsilon-optimality that we
|
||||
// use because it means we can saturate all admissible arcs in
|
||||
@@ -1354,8 +1355,7 @@ bool LinearSumAssignment<GraphType, CostValue>::FinalizeSetup() {
|
||||
// precondition.
|
||||
for (NodeIndex node = 0; node < num_left_nodes_; ++node) {
|
||||
matched_arc_[node] = GraphType::kNilArc;
|
||||
typename GraphType::OutgoingArcIterator arc_it(*graph_, node);
|
||||
if (!arc_it.Ok()) {
|
||||
if (graph_->OutgoingArcs(node).empty()) {
|
||||
incidence_precondition_satisfied_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,8 +188,7 @@ bool GenericMinCostFlow<Graph, ArcFlowType,
|
||||
|
||||
// Adjust and recompute min_node_excess[node].
|
||||
min_node_excess[node] = node_excess_[node];
|
||||
for (OutgoingArcIterator it(*graph_, node); it.Ok(); it.Next()) {
|
||||
const int arc = it.Index();
|
||||
for (const ArcIndex arc : graph_->OutgoingArcs(node)) {
|
||||
residual_arc_capacity_[arc] =
|
||||
std::min(residual_arc_capacity_[arc], upper_bound);
|
||||
min_node_excess[node] =
|
||||
@@ -204,8 +203,7 @@ bool GenericMinCostFlow<Graph, ArcFlowType,
|
||||
|
||||
// Adjust and recompute max_node_excess[node].
|
||||
max_node_excess[node] = node_excess_[node];
|
||||
for (IncomingArcIterator it(*graph_, node); it.Ok(); it.Next()) {
|
||||
const int arc = it.Index();
|
||||
for (const ArcIndex arc : graph_->IncomingArcs(node)) {
|
||||
residual_arc_capacity_[arc] =
|
||||
std::min(residual_arc_capacity_[arc], upper_bound);
|
||||
max_node_excess[node] =
|
||||
|
||||
@@ -408,8 +408,6 @@ class GenericMinCostFlow : public MinCostFlowBase {
|
||||
typedef typename Graph::ArcIndex ArcIndex;
|
||||
typedef int64_t CostValue;
|
||||
typedef int64_t FlowQuantity;
|
||||
typedef typename Graph::IncomingArcIterator IncomingArcIterator;
|
||||
typedef typename Graph::OutgoingArcIterator OutgoingArcIterator;
|
||||
typedef typename Graph::OutgoingOrOppositeIncomingArcIterator
|
||||
OutgoingOrOppositeIncomingArcIterator;
|
||||
typedef ZVector<ArcIndex> ArcIndexArray;
|
||||
|
||||
@@ -646,8 +646,8 @@ bool CheckAssignmentFeasibility(const Graph& graph,
|
||||
absl::Span<const int64_t> supply) {
|
||||
for (typename Graph::NodeIndex node = 0; node < graph.num_nodes(); ++node) {
|
||||
if (supply[node] != 0) {
|
||||
typename Graph::OutgoingOrOppositeIncomingArcIterator it(graph, node);
|
||||
EXPECT_TRUE(it.Ok()) << node << " has no incident arc";
|
||||
EXPECT_FALSE(graph.OutgoingOrOppositeIncomingArcs(node).empty())
|
||||
<< node << " has no incident arc";
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -31,22 +31,6 @@ 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,9 +29,6 @@ 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:ebert_graph",
|
||||
"//ortools/graph:linear_assignment",
|
||||
"//ortools/graph:max_flow",
|
||||
"//ortools/graph:min_cost_flow",
|
||||
@@ -52,9 +49,6 @@ 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:ebert_graph",
|
||||
"//ortools/graph:linear_assignment",
|
||||
"//ortools/graph:max_flow",
|
||||
"//ortools/graph:min_cost_flow",
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
// 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/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.
|
||||
//
|
||||
// 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::PathWithLength 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::PathWithLength 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;
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -67,9 +67,7 @@ void CheckPathDataRow(const GraphType& graph,
|
||||
const PathDistance expected_distances[],
|
||||
typename GraphType::NodeIndex tail) {
|
||||
int index = tail * graph.num_nodes();
|
||||
for (typename GraphType::NodeIterator iterator(graph); iterator.Ok();
|
||||
iterator.Next()) {
|
||||
const typename GraphType::NodeIndex head(iterator.Index());
|
||||
for (const typename GraphType::NodeIndex head : graph.AllNodes()) {
|
||||
CheckPathDataPair(container, distance_container, expected_distances[index],
|
||||
expected_paths[index], tail, head);
|
||||
++index;
|
||||
@@ -97,9 +95,7 @@ void CheckPathData(const GraphType& graph,
|
||||
const GenericPathContainer<GraphType>& distance_container,
|
||||
const typename GraphType::NodeIndex expected_paths[],
|
||||
const PathDistance expected_distances[]) {
|
||||
for (typename GraphType::NodeIterator iterator(graph); iterator.Ok();
|
||||
iterator.Next()) {
|
||||
const typename GraphType::NodeIndex tail(iterator.Index());
|
||||
for (const typename GraphType::NodeIndex tail : graph.AllNodes()) {
|
||||
CheckPathDataRow(graph, container, distance_container, expected_paths,
|
||||
expected_distances, tail);
|
||||
}
|
||||
|
||||
@@ -158,10 +158,10 @@ def create_section_data():
|
||||
'input_files':
|
||||
'ortools/graph/christofides.h ' + 'ortools/graph/cliques.h ' +
|
||||
'ortools/graph/connected_components.h ' +
|
||||
'ortools/graph/connectivity.h ' + 'ortools/graph/ebert_graph.h ' +
|
||||
'ortools/graph/connectivity.h ' +
|
||||
'ortools/graph/eulerian_path.h ' + 'ortools/graph/graph.h ' +
|
||||
'ortools/graph/graphs.h ' + 'ortools/graph/hamiltonian_path.h ' +
|
||||
'ortools/graph/graph_io.h ' + 'ortools/graph/iterators.h ' +
|
||||
'ortools/graph/io.h ' + 'ortools/graph/iterators.h ' +
|
||||
'ortools/graph/linear_assignment.h ' + 'ortools/graph/max_flow.h ' +
|
||||
'ortools/graph/min_cost_flow.h ' +
|
||||
'ortools/graph/minimum_spanning_tree.h ' +
|
||||
|
||||
Reference in New Issue
Block a user