diff --git a/ortools/graph/BUILD.bazel b/ortools/graph/BUILD.bazel index 1623edd17e..46366315bf 100644 --- a/ortools/graph/BUILD.bazel +++ b/ortools/graph/BUILD.bazel @@ -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", diff --git a/ortools/graph/README.md b/ortools/graph/README.md index e8940279a7..a771613004 100644 --- a/ortools/graph/README.md +++ b/ortools/graph/README.md @@ -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]. -[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 diff --git a/ortools/graph/dag_constrained_shortest_path.cc b/ortools/graph/dag_constrained_shortest_path.cc deleted file mode 100644 index 099e4ba8bb..0000000000 --- a/ortools/graph/dag_constrained_shortest_path.cc +++ /dev/null @@ -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 - -#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 mapping, std::vector& 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 arcs_with_length_and_resources, - int source, int destination, const std::vector& 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 arc_lengths; - arc_lengths.reserve(num_arcs); - std::vector> 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 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 inverse_permutation = - GetInversePermutation(permutation); - - const absl::StatusOr> topological_order = - util::graph::FastTopologicalSort(graph); - CHECK_OK(topological_order) << "arcs_with_length form a cycle."; - - std::vector sources = {source}; - std::vector destinations = {destination}; - ConstrainedShortestPathsOnDagWrapper> - 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 GetInversePermutation(absl::Span permutation) { - std::vector 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 diff --git a/ortools/graph/dag_constrained_shortest_path.h b/ortools/graph/dag_constrained_shortest_path.h deleted file mode 100644 index 4091d79821..0000000000 --- a/ortools/graph/dag_constrained_shortest_path.h +++ /dev/null @@ -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 -#include -#include - -#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 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 arcs_with_length_and_resources, - int source, int destination, const std::vector& 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 -#if __cplusplus >= 202002L - requires DagGraphType -#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* arc_lengths, - const std::vector>* arc_resources, - absl::Span topological_order, - absl::Span sources, - absl::Span destinations, - const std::vector* 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 arc_lengths, - absl::Span> arc_resources, - absl::Span> min_arc_resources, - absl::Span max_resources, int max_num_created_labels, - std::vector& lengths_from_sources, - std::vector>& resources_from_sources, - std::vector& incoming_arc_indices_from_sources, - std::vector& incoming_label_indices_from_sources, - std::vector& first_label, std::vector& 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 arc_lengths, - absl::Span> arc_resources, - absl::Span max_resources, - const std::vector sub_node_indices[2], - const std::vector lengths_from_sources[2], - const std::vector> resources_from_sources[2], - const std::vector first_label[2], - const std::vector 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 ArcPathTo( - int best_label_index, - absl::Span incoming_arc_indices_from_sources, - absl::Span incoming_label_indices_from_sources) const; - - // Returns the list of all the nodes implied by a given `arc_path`. - std::vector NodePathImpliedBy(absl::Span arc_path, - const GraphType& graph) const; - - static constexpr double kTolerance = 1e-6; - - const GraphType* const graph_; - const std::vector* const arc_lengths_; - const std::vector>* const arc_resources_; - const std::vector* const max_resources_; - absl::Span sources_; - absl::Span 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> 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 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 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 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> 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 lengths_from_sources_[2]; - std::vector> resources_from_sources_[2]; - std::vector incoming_arc_indices_from_sources_[2]; - std::vector incoming_label_indices_from_sources_[2]; - std::vector node_first_label_[2]; - std::vector node_num_labels_[2]; -}; - -std::vector GetInversePermutation(absl::Span permutation); - -// ----------------------------------------------------------------------------- -// Implementation. -// ----------------------------------------------------------------------------- - -template -#if __cplusplus >= 202002L - requires DagGraphType -#endif -ConstrainedShortestPathsOnDagWrapper:: - ConstrainedShortestPathsOnDagWrapper( - const GraphType* graph, const std::vector* arc_lengths, - const std::vector>* arc_resources, - absl::Span topological_order, - absl::Span sources, - absl::Span destinations, - const std::vector* 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 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::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::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::infinity() && - !std::isnan(max_resource)) - << absl::StrFormat( - "max_resource cannot be negative not +inf nor NaN"); - } - std::vector 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>* full_arc_resources[2]; - absl::Span full_topological_order[2]; - absl::Span 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 full_permutation; - full_backward_graph.Build(&full_permutation); - const std::vector full_inverse_arc_indices = - GetInversePermutation(full_permutation); - std::vector> 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 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> full_min_arc_resources[2]; - for (const Direction dir : {FORWARD, BACKWARD}) { - full_min_arc_resources[dir].reserve(num_resources_); - std::vector full_arc_resource = full_arc_resources[dir]->front(); - ShortestPathsOnDagWrapper 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 is_reachable(num_nodes, true); - std::vector 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 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 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 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 -#if __cplusplus >= 202002L - requires DagGraphType -#endif -PathWithLength ConstrainedShortestPathsOnDagWrapper< - GraphType>::RunConstrainedShortestPathOnDag() { - // Assign lengths on sub-relevant graphs. - std::vector 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::infinity(), - .label_index = {-1, -1}}; - for (const Direction dir : {FORWARD, BACKWARD}) { - absl::Span 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 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 -#if __cplusplus >= 202002L - requires DagGraphType -#endif -void ConstrainedShortestPathsOnDagWrapper:: - RunHalfConstrainedShortestPathOnDag( - const GraphType& reverse_graph, absl::Span arc_lengths, - absl::Span> arc_resources, - absl::Span> min_arc_resources, - absl::Span max_resources, - const int max_num_created_labels, - std::vector& lengths_from_sources, - std::vector>& resources_from_sources, - std::vector& incoming_arc_indices_from_sources, - std::vector& incoming_label_indices_from_sources, - std::vector& first_label, std::vector& 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 lengths_to; - std::vector> resources_to(num_resources_); - std::vector incoming_arc_indices_to; - std::vector incoming_label_indices_to; - std::vector label_indices_to; - std::vector 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::infinity()); - if (arc_length == std::numeric_limits::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 -#if __cplusplus >= 202002L - requires DagGraphType -#endif -typename GraphType::ArcIndex -ConstrainedShortestPathsOnDagWrapper::MergeHalfRuns( - const GraphType& graph, absl::Span arc_lengths, - absl::Span> arc_resources, - absl::Span max_resources, - const std::vector sub_node_indices[2], - const std::vector lengths_from_sources[2], - const std::vector> resources_from_sources[2], - const std::vector first_label[2], const std::vector num_labels[2], - LabelPair& best_label_pair) { - const std::vector& forward_sub_node_indices = - sub_node_indices[FORWARD]; - absl::Span forward_lengths = lengths_from_sources[FORWARD]; - const std::vector>& forward_resources = - resources_from_sources[FORWARD]; - absl::Span forward_first_label = first_label[FORWARD]; - absl::Span forward_num_labels = num_labels[FORWARD]; - const std::vector& backward_sub_node_indices = - sub_node_indices[BACKWARD]; - absl::Span backward_lengths = lengths_from_sources[BACKWARD]; - const std::vector>& backward_resources = - resources_from_sources[BACKWARD]; - absl::Span backward_first_label = first_label[BACKWARD]; - absl::Span 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::infinity()); - if (arc_length == std::numeric_limits::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 -#if __cplusplus >= 202002L - requires DagGraphType -#endif -std::vector -ConstrainedShortestPathsOnDagWrapper::ArcPathTo( - const int best_label_index, - absl::Span incoming_arc_indices_from_sources, - absl::Span incoming_label_indices_from_sources) const { - int current_label_index = best_label_index; - std::vector 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 -#if __cplusplus >= 202002L - requires DagGraphType -#endif -std::vector -ConstrainedShortestPathsOnDagWrapper::NodePathImpliedBy( - absl::Span arc_path, const GraphType& graph) const { - if (arc_path.empty()) { - return {}; - } - std::vector 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_ diff --git a/ortools/graph/dag_constrained_shortest_path_test.cc b/ortools/graph/dag_constrained_shortest_path_test.cc deleted file mode 100644 index eeb7c284b3..0000000000 --- a/ortools/graph/dag_constrained_shortest_path_test.cc +++ /dev/null @@ -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 -#include -#include -#include -#include - -#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::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 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 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 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 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 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::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 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 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 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 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 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 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 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 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 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 arc_lengths; - std::vector> 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 topological_order = {source_1, source_2, destination}; - const std::vector sources = {source_1, source_2}; - const std::vector destinations = {destination}; - const std::vector max_resources = {6.0}; - ConstrainedShortestPathsOnDagWrapper> - 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 arc_lengths; - std::vector> 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 sources = {source}; - const std::vector destinations = {destination_1, destination_2, - destination_3}; - const std::vector topological_order = {source, destination_3, - destination_1, destination_2}; - const std::vector max_resources = {6.0}; - ConstrainedShortestPathsOnDagWrapper> - 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 arc_lengths; - std::vector> 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 topological_order = {source, a, b, destination}; - const std::vector sources = {source}; - const std::vector destinations = {destination}; - const std::vector max_resources = {6.0, 12.0}; - ConstrainedShortestPathsOnDagWrapper> - 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 arc_lengths; - std::vector> 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 topological_order = {source, a, destination}; - const std::vector sources = {source}; - const std::vector destinations = {destination}; - const std::vector max_resources = {6.0}; - ConstrainedShortestPathsOnDagWrapper> - 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> - 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, std::vector> 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 topological_order(num_nodes); - topological_order.back() = num_nodes - 1; - absl::Span 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 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 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(absl::Uniform(bit_gen, min_value, max_value))); - } - return arc_values; -} - -double SolveConstrainedShortestPathUsingIntegerProgramming( - const util::StaticGraph<>& graph, absl::Span arc_lengths, - absl::Span> arc_resources, - absl::Span max_resources, - absl::Span::NodeIndex> sources, - absl::Span::NodeIndex> destinations) { - using NodeIndex = util::StaticGraph<>::NodeIndex; - using ArcIndex = util::StaticGraph<>::ArcIndex; - - math_opt::Model model; - std::vector arc_variables; - std::vector 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 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 arc_lengths(num_arcs); - std::vector> 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 sources = {0}; - const std::vector destinations = {num_nodes - 1}; - const std::vector max_resources = {15.0}; - ConstrainedShortestPathsOnDagWrapper> - 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> arc_lengths_scenarios; - for (int _ = 0; _ < num_scenarios; ++_) { - arc_lengths_scenarios.push_back(GenerateRandomIntegerValues(graph)); - } - std::vector arc_lengths(num_arcs); - std::vector> 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 sources = {0}; - const std::vector destinations = {num_nodes - 1}; - const std::vector max_resources = {num_nodes * 0.2}; - ConstrainedShortestPathsOnDagWrapper> - 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 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> arc_lengths_scenarios; - for (int unused = 0; unused < kNumScenarios; ++unused) { - std::vector arc_lengths(graph.num_arcs()); - for (int i = 0; i < graph.num_arcs(); ++i) { - arc_lengths[i] = absl::Uniform(bit_gen, 0, 1); - } - arc_lengths_scenarios.push_back(arc_lengths); - } - - std::vector> 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(bit_gen, 0, 1); - } - } - - std::vector arc_lengths(num_arcs); - const std::vector sources = {0}; - const std::vector destinations = {num_nodes - 1}; - std::vector 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> - 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, 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 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 arcs_with_length_and_resources = - {{source, destination, std::numeric_limits::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 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 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 arc_lengths; - std::vector> arc_resources(1); - graph.AddArc(source, destination); - arc_lengths.push_back(1.0); - arc_resources[0].push_back({1.0}); - const std::vector topological_order = {source}; - const std::vector sources = {source}; - const std::vector destinations = {destination}; - const std::vector max_resources = {0.0}; - - EXPECT_DEATH(ConstrainedShortestPathsOnDagWrapper>( - &graph, &arc_lengths, &arc_resources, topological_order, - sources, destinations, &max_resources), - "Invalid topological order"); -} -#endif // NDEBUG - -} // namespace -} // namespace operations_research diff --git a/ortools/graph/dag_shortest_path.cc b/ortools/graph/dag_shortest_path.cc deleted file mode 100644 index f8c15379b4..0000000000 --- a/ortools/graph/dag_shortest_path.cc +++ /dev/null @@ -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 -#include -#include - -#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 arc_lengths; - std::vector original_arc_indices; - std::vector topological_order; -}; - -ShortestPathOnDagProblem ReadProblem( - const int num_nodes, absl::Span arcs_with_length) { - GraphType graph(num_nodes, arcs_with_length.size()); - std::vector 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 permutation; - graph.Build(&permutation); - util::Permute(permutation, &arc_lengths); - - std::vector original_arc_indices(permutation.size()); - if (!permutation.empty()) { - for (ArcIndex i = 0; i < permutation.size(); ++i) { - original_arc_indices[permutation[i]] = i; - } - } - - absl::StatusOr> 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 original_arc_indices, - std::vector& 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 arcs_with_length, - const int source, const int destination) { - const ShortestPathOnDagProblem problem = - ReadProblem(num_nodes, arcs_with_length); - - ShortestPathsOnDagWrapper> 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::infinity()}; - } - - std::vector 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 KShortestPathsOnDag( - const int num_nodes, absl::Span arcs_with_length, - const int source, const int destination, const int path_count) { - const ShortestPathOnDagProblem problem = - ReadProblem(num_nodes, arcs_with_length); - - KShortestPathsOnDagWrapper 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::infinity()}}; - } - - std::vector lengths = shortest_paths_on_dag.LengthsTo(destination); - std::vector> arc_paths = - shortest_paths_on_dag.ArcPathsTo(destination); - std::vector> node_paths = - shortest_paths_on_dag.NodePathsTo(destination); - std::vector 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 diff --git a/ortools/graph/dag_shortest_path.h b/ortools/graph/dag_shortest_path.h deleted file mode 100644 index 91d11bd8d9..0000000000 --- a/ortools/graph/dag_shortest_path.h +++ /dev/null @@ -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 -#if __cplusplus >= 202002L -#include -#endif -#include -#include -#include - -#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 arc_path; - std::vector 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 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 KShortestPathsOnDag( - int num_nodes, absl::Span 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 -concept DagGraphType = requires(GraphType graph) { - { typename GraphType::NodeIndex{} }; - { typename GraphType::ArcIndex{} }; - { graph.num_nodes() } -> std::same_as; - { graph.num_arcs() } -> std::same_as; - { graph.OutgoingArcs(typename GraphType::NodeIndex{}) }; - { - graph.Tail(typename GraphType::ArcIndex{}) - } -> std::same_as; - { - graph.Head(typename GraphType::ArcIndex{}) - } -> std::same_as; - { 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 -#if __cplusplus >= 202002L - requires DagGraphType -#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* arc_lengths, - absl::Span 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 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& 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 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 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 NodePathTo(NodeIndex node) const; - - // Accessors to the underlying graph and arc lengths. - const GraphType& graph() const { return *graph_; } - const std::vector& arc_lengths() const { return *arc_lengths_; } - - private: - static constexpr double kInf = std::numeric_limits::infinity(); - const GraphType* const graph_; - const std::vector* const arc_lengths_; - absl::Span const topological_order_; - - // Data about the last call of the RunShortestPathOnDag() function. - std::vector length_from_sources_; - std::vector incoming_shortest_path_arc_; - std::vector 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 -#if __cplusplus >= 202002L - requires DagGraphType -#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* arc_lengths, - absl::Span 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 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& 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 LengthsTo(NodeIndex node) const; - - // Returns the list of all the arcs of the k-shortest paths from `node`'s - // source to `node`. - std::vector> 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> NodePathsTo(NodeIndex node) const; - - // Accessors to the underlying graph and arc lengths. - const GraphType& graph() const { return *graph_; } - const std::vector& arc_lengths() const { return *arc_lengths_; } - int path_count() const { return path_count_; } - - private: - static constexpr double kInf = std::numeric_limits::infinity(); - - const GraphType* const graph_; - const std::vector* const arc_lengths_; - absl::Span const topological_order_; - const int path_count_; - - GraphType reverse_graph_; - // Maps reverse arc indices to indices in the original graph. - std::vector 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> lengths_from_sources_; - std::vector> incoming_shortest_paths_arc_; - std::vector> incoming_shortest_paths_index_; - std::vector is_source_; - std::vector reached_nodes_; -}; - -template -#if __cplusplus >= 202002L - requires DagGraphType -#endif -absl::Status TopologicalOrderIsValid( - const GraphType& graph, - absl::Span 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 -#if __cplusplus >= 202002L - requires DagGraphType -#endif -std::vector NodePathImpliedBy( - absl::Span arc_path, - const GraphType& graph) { - CHECK(!arc_path.empty()); - std::vector 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 -#if __cplusplus >= 202002L - requires DagGraphType -#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 -#if __cplusplus >= 202002L - requires DagGraphType -#endif -absl::Status TopologicalOrderIsValid( - const GraphType& graph, - absl::Span 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 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 -#if __cplusplus >= 202002L - requires DagGraphType -#endif -ShortestPathsOnDagWrapper::ShortestPathsOnDagWrapper( - const GraphType* graph, const std::vector* arc_lengths, - absl::Span 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 -#if __cplusplus >= 202002L - requires DagGraphType -#endif -void ShortestPathsOnDagWrapper::RunShortestPathOnDag( - absl::Span sources) { - // Caching the vector addresses allow to not fetch it on each access. - const absl::Span length_from_sources = - absl::MakeSpan(length_from_sources_); - const absl::Span 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 -#if __cplusplus >= 202002L - requires DagGraphType -#endif -bool ShortestPathsOnDagWrapper::IsReachable(NodeIndex node) const { - CheckNodeIsValid(node, *graph_); - return length_from_sources_[node] < kInf; -} - -template -#if __cplusplus >= 202002L - requires DagGraphType -#endif -std::vector -ShortestPathsOnDagWrapper::ArcPathTo(NodeIndex node) const { - CHECK(IsReachable(node)); - std::vector 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 -#if __cplusplus >= 202002L - requires DagGraphType -#endif -std::vector -ShortestPathsOnDagWrapper::NodePathTo(NodeIndex node) const { - const std::vector arc_path = ArcPathTo(node); - if (arc_path.empty()) { - return {node}; - } - return NodePathImpliedBy(ArcPathTo(node), *graph_); -} - -// ----------------------------------------------------------------------------- -// KShortestPathsOnDagWrapper implementation. -// ----------------------------------------------------------------------------- -template -#if __cplusplus >= 202002L - requires DagGraphType -#endif -KShortestPathsOnDagWrapper::KShortestPathsOnDagWrapper( - const GraphType* graph, const std::vector* arc_lengths, - absl::Span 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 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 -#if __cplusplus >= 202002L - requires DagGraphType -#endif -void KShortestPathsOnDagWrapper::RunKShortestPathOnDag( - absl::Span sources) { - // Caching the vector addresses allow to not fetch it on each access. - const absl::Span arc_lengths = *arc_lengths_; - const absl::Span 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 min_heap; - auto comp = std::greater(); - 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 -#if __cplusplus >= 202002L - requires DagGraphType -#endif -bool KShortestPathsOnDagWrapper::IsReachable(NodeIndex node) const { - CheckNodeIsValid(node, *graph_); - return lengths_from_sources_.front()[node] < kInf; -} - -template -#if __cplusplus >= 202002L - requires DagGraphType -#endif -std::vector KShortestPathsOnDagWrapper::LengthsTo( - NodeIndex node) const { - std::vector 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 -#if __cplusplus >= 202002L - requires DagGraphType -#endif -std::vector> -KShortestPathsOnDagWrapper::ArcPathsTo(NodeIndex node) const { - std::vector> 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 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 -#if __cplusplus >= 202002L - requires DagGraphType -#endif -std::vector> -KShortestPathsOnDagWrapper::NodePathsTo(NodeIndex node) const { - const std::vector> arc_paths = ArcPathsTo(node); - std::vector> 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_ diff --git a/ortools/graph/dag_shortest_path_test.cc b/ortools/graph/dag_shortest_path_test.cc deleted file mode 100644 index 3ae65801e5..0000000000 --- a/ortools/graph/dag_shortest_path_test.cc +++ /dev/null @@ -1,1158 +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 -#include -#include -#include -#include -#include - -#include "absl/algorithm/container.h" -#include "absl/log/check.h" -#include "absl/random/random.h" -#include "absl/status/status.h" -#include "absl/types/span.h" -#include "benchmark/benchmark.h" -#include "gtest/gtest.h" -#include "ortools/base/dump_vars.h" -#include "ortools/base/gmock.h" -#include "ortools/graph/graph.h" -#include "ortools/graph/graph_io.h" -#include "ortools/util/flat_matrix.h" - -namespace operations_research { -namespace { - -constexpr double kInf = std::numeric_limits::infinity(); - -using ::testing::ElementsAre; -using ::testing::FieldsAre; -using ::testing::HasSubstr; -using ::testing::IsEmpty; -using ::testing::status::StatusIs; - -TEST(TopologicalOrderIsValidTest, ValidateTopologicalOrder) { - const int source = 0; - const int destination = 1; - const int num_nodes = 2; - util::ListGraph<> graph(num_nodes, /*arc_capacity=*/2); - graph.AddArc(source, destination); - - EXPECT_OK(TopologicalOrderIsValid(graph, {source, destination})); - EXPECT_THAT(TopologicalOrderIsValid(graph, {source}), - StatusIs(absl::StatusCode::kInvalidArgument, - HasSubstr("topological_order.size() = 1"))); - EXPECT_THAT(TopologicalOrderIsValid(graph, {source, source}), - StatusIs(absl::StatusCode::kInvalidArgument, - HasSubstr("0 appears twice"))); - EXPECT_THAT(TopologicalOrderIsValid(graph, {destination, source}), - StatusIs(absl::StatusCode::kInvalidArgument, - HasSubstr("arc (0, 1) is inconsistent"))); - - graph.AddArc(source, source); - - EXPECT_THAT(TopologicalOrderIsValid(graph, {source, destination}), - StatusIs(absl::StatusCode::kInvalidArgument, - HasSubstr("arc (0, 0) is inconsistent"))); -} - -// ----------------------------------------------------------------------------- -// ShortestPathOnDagTest and ShortestPathsOnDagWrapperTest. -// ----------------------------------------------------------------------------- -TEST(ShortestPathOnDagTest, EmptyGraph) { - EXPECT_DEATH(ShortestPathsOnDag(/*num_nodes=*/0, /*arcs_with_length=*/{}, - /*source=*/0, /*destination=*/0), - "num_nodes\\(\\) > 0"); -} - -TEST(ShortestPathOnDagTest, NoArcGraph) { - EXPECT_DEATH(ShortestPathsOnDag(/*num_nodes=*/1, /*arcs_with_length=*/{}, - /*source=*/0, /*destination=*/0), - "num_arcs\\(\\) > 0"); -} - -TEST(ShortestPathOnDagTest, NonExistingSourceBecauseNegative) { - EXPECT_DEATH( - ShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, 0.0}}, - /*source=*/-1, /*destination=*/1), - "Node must be nonnegative"); -} - -TEST(ShortestPathOnDagTest, NonExistingSourceBecauseTooLarge) { - EXPECT_DEATH( - ShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, 0.0}}, - /*source=*/3, /*destination=*/1), - "num_nodes\\(\\)"); -} - -TEST(ShortestPathOnDagTest, NonExistingDestinationBecauseNegative) { - EXPECT_DEATH( - ShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, 0.0}}, - /*source=*/0, /*destination=*/-1), - "Node must be nonnegative"); -} - -TEST(ShortestPathOnDagTest, NonExistingDestinationBecauseTooLarge) { - EXPECT_DEATH( - ShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, 0.0}}, - /*source=*/0, /*destination=*/3), - "num_nodes\\(\\)"); -} - -TEST(ShortestPathOnDagTest, Cycle) { - EXPECT_DEATH( - ShortestPathsOnDag(/*num_nodes=*/2, - /*arcs_with_length=*/{{0, 1, 0.0}, {1, 0, 0.0}}, - /*source=*/0, /*destination=*/1), - "cycle"); -} - -TEST(ShortestPathOnDagTest, SimpleGraph) { - const int source = 0; - const int destination = 1; - const int a = 2; - const int b = 3; - const int num_nodes = 4; - const std::vector arcs_with_length = {{source, a, 5.0}, - {source, b, 2.0}, - {a, destination, 3.0}, - {b, destination, 20.0}}; - - EXPECT_THAT( - ShortestPathsOnDag(num_nodes, arcs_with_length, source, destination), - FieldsAre(/*length=*/8.0, /*arc_path=*/ElementsAre(0, 2), - /*node_path=*/ElementsAre(source, a, destination))); -} - -TEST(ShortestPathOnDagTest, SourceIsDestination) { - const int source = 0; - const int destination = 1; - const int num_nodes = 2; - const std::vector arcs_with_length = { - {source, destination, 1.0}}; - - EXPECT_THAT(ShortestPathsOnDag(num_nodes, arcs_with_length, source, source), - FieldsAre( - /*length=*/0.0, /*arc_path=*/IsEmpty(), - /*node_path=*/ElementsAre(source))); -} - -TEST(ShortestPathOnDagTest, LargerGraphWithNegativeCost) { - const int source = 0; - const int a = 3; - const int b = 2; - const int c = 1; - const int destination = 4; - const int num_nodes = 5; - const std::vector arcs_with_length = { - {a, c, 5.0}, {source, b, 7.0}, {a, b, 1.0}, - {source, a, 3.0}, {c, destination, 5.0}, {b, destination, -2.0}}; - - EXPECT_THAT( - ShortestPathsOnDag(num_nodes, arcs_with_length, source, destination), - FieldsAre(/*length=*/2.0, /*arc_path=*/ElementsAre(3, 2, 5), - /*node_path=*/ElementsAre(source, a, b, destination))); -} - -TEST(ShortestPathOnDagTest, SetsNotConnected) { - const int source = 0; - const int destination = 1; - const int a = 2; - const int num_nodes = 3; - const std::vector arcs_with_length = {{source, a, 1.0}}; - - EXPECT_THAT( - ShortestPathsOnDag(num_nodes, arcs_with_length, source, destination), - FieldsAre(/*length=*/kInf, /*arc_path=*/IsEmpty(), - /*node_path=*/IsEmpty())); -} - -TEST(ShortestPathOnDagTest, TwoConnectedComponents) { - const int a = 0; - const int b = 1; - const int c = 2; - const int d = 3; - const int e = 4; - const int num_nodes = 5; - const std::vector arcs_with_length = { - {a, b, 0.0}, {b, c, 0.0}, {d, e, 0.0}}; - - EXPECT_THAT(ShortestPathsOnDag(num_nodes, arcs_with_length, /*source=*/a, - /*destination=*/e), - FieldsAre(/*length=*/kInf, /*arc_path=*/IsEmpty(), - /*node_path=*/IsEmpty())); - EXPECT_THAT(ShortestPathsOnDag(num_nodes, arcs_with_length, /*source=*/b, - /*destination=*/d), - FieldsAre(/*length=*/kInf, /*arc_path=*/IsEmpty(), - /*node_path=*/IsEmpty())); -} - -TEST(ShortestPathOnDagTest, SetsNotConnectedDueToInfiniteCost) { - const int source = 0; - const int destination = 1; - const int a = 2; - const int num_nodes = 3; - const std::vector arcs_with_length = {{a, destination, 1.0}, - {source, a, kInf}}; - - EXPECT_THAT( - ShortestPathsOnDag(num_nodes, arcs_with_length, source, destination), - FieldsAre(/*length=*/kInf, /*arc_path=*/IsEmpty(), - /*node_path=*/IsEmpty())); -} - -TEST(ShortestPathOnDagTest, AvoidInfiniteCost) { - const int source = 0; - const int destination = 1; - const int a = 2; - const int b = 3; - const int num_nodes = 4; - const std::vector arcs_with_length = {{a, destination, 1.0}, - {b, destination, 1.0}, - {source, a, kInf}, - {source, b, 3.0}}; - - EXPECT_THAT( - ShortestPathsOnDag(num_nodes, arcs_with_length, source, destination), - FieldsAre(/*length=*/4.0, /*arc_path=*/ElementsAre(3, 1), - /*node_path=*/ElementsAre(source, b, destination))); -} - -TEST(ShortestPathOnDagTest, SourceNotFirst) { - const int destination = 0; - const int source = 1; - const int num_nodes = 2; - const std::vector arcs_with_length = { - {source, destination, 1.0}}; - - EXPECT_THAT( - ShortestPathsOnDag(num_nodes, arcs_with_length, source, destination), - FieldsAre(/*length=*/1.0, /*arc_path=*/ElementsAre(0), - /*node_path=*/ElementsAre(source, destination))); -} - -TEST(ShortestPathOnDagTest, MultipleArcs) { - const int source = 0; - const int destination = 1; - const int num_nodes = 2; - const std::vector arcs_with_length = { - {source, destination, 4.0}, {source, destination, 2.0}}; - - EXPECT_THAT( - ShortestPathsOnDag(num_nodes, arcs_with_length, source, destination), - FieldsAre(/*length=*/2.0, /*arc_path=*/ElementsAre(1), - /*node_path=*/ElementsAre(source, destination))); -} - -TEST(ShortestPathOnDagTest, UpdateCost) { - const int source = 0; - const int destination = 1; - const int a = 2; - const int b = 3; - const int num_nodes = 4; - std::vector arcs_with_length = {{source, a, 5.0}, - {source, b, 2.0}, - {a, destination, 3.0}, - {b, destination, 20.0}}; - - EXPECT_THAT( - ShortestPathsOnDag(num_nodes, arcs_with_length, source, destination), - FieldsAre(/*length=*/8.0, /*arc_path=*/ElementsAre(0, 2), - /*node_path=*/ElementsAre(source, a, destination))); - - // Update the length of arc b -> destination from 20.0 to -1.0. - arcs_with_length[3].length = -1.0; - - EXPECT_THAT( - ShortestPathsOnDag(num_nodes, arcs_with_length, source, destination), - FieldsAre(/*length=*/1.0, /*arc_path=*/ElementsAre(1, 3), - /*node_path=*/ElementsAre(source, b, destination))); -} - -TEST(ShortestPathsOnDagWrapperTest, MultipleSources) { - const int source_1 = 0; - const int source_2 = 1; - const int destination = 2; - const int num_nodes = 3; - util::ListGraph<> graph(num_nodes, /*arc_capacity=*/2); - std::vector arc_lengths; - graph.AddArc(source_1, destination); - arc_lengths.push_back(-6.0); - graph.AddArc(source_2, destination); - arc_lengths.push_back(3.0); - const std::vector topological_order = {source_2, source_1, destination}; - ShortestPathsOnDagWrapper> shortest_path_on_dag( - &graph, &arc_lengths, topological_order); - shortest_path_on_dag.RunShortestPathOnDag({source_1, source_2}); - - EXPECT_TRUE(shortest_path_on_dag.IsReachable(destination)); - EXPECT_THAT(shortest_path_on_dag.LengthTo(destination), -6.0); - EXPECT_THAT(shortest_path_on_dag.ArcPathTo(destination), ElementsAre(0)); - EXPECT_THAT(shortest_path_on_dag.NodePathTo(destination), - ElementsAre(source_1, destination)); -} - -TEST(ShortestPathsOnDagWrapperTest, ShortestPathGoesThroughMultipleSources) { - const int source_1 = 0; - const int source_2 = 1; - const int destination = 2; - const int num_nodes = 3; - util::ListGraph<> graph(num_nodes, /*arc_capacity=*/2); - std::vector arc_lengths; - graph.AddArc(source_1, source_2); - arc_lengths.push_back(-7.0); - graph.AddArc(source_2, destination); - arc_lengths.push_back(3.0); - const std::vector topological_order = {source_1, source_2, destination}; - ShortestPathsOnDagWrapper> shortest_path_on_dag( - &graph, &arc_lengths, topological_order); - shortest_path_on_dag.RunShortestPathOnDag({source_1, source_2}); - - EXPECT_TRUE(shortest_path_on_dag.IsReachable(destination)); - EXPECT_THAT(shortest_path_on_dag.LengthTo(destination), -4.0); - EXPECT_THAT(shortest_path_on_dag.ArcPathTo(destination), ElementsAre(0, 1)); - EXPECT_THAT(shortest_path_on_dag.NodePathTo(destination), - ElementsAre(source_1, source_2, destination)); -} - -TEST(ShortestPathsOnDagWrapperTest, MultipleDestinations) { - const int source = 0; - const int destination_1 = 1; - const int destination_2 = 2; - const int destination_3 = 3; - const int num_nodes = 4; - util::ListGraph<> graph(num_nodes, /*arc_capacity=*/3); - std::vector arc_lengths; - graph.AddArc(source, destination_1); - arc_lengths.push_back(3.0); - graph.AddArc(source, destination_2); - arc_lengths.push_back(1.0); - graph.AddArc(source, destination_3); - arc_lengths.push_back(2.0); - const std::vector topological_order = {source, destination_3, - destination_1, destination_2}; - ShortestPathsOnDagWrapper> shortest_path_on_dag( - &graph, &arc_lengths, topological_order); - shortest_path_on_dag.RunShortestPathOnDag({source}); - - EXPECT_TRUE(shortest_path_on_dag.IsReachable(destination_1)); - EXPECT_THAT(shortest_path_on_dag.LengthTo(destination_1), 3.0); - EXPECT_THAT(shortest_path_on_dag.ArcPathTo(destination_1), ElementsAre(0)); - EXPECT_THAT(shortest_path_on_dag.NodePathTo(destination_1), - ElementsAre(source, destination_1)); - - EXPECT_TRUE(shortest_path_on_dag.IsReachable(destination_2)); - EXPECT_THAT(shortest_path_on_dag.LengthTo(destination_2), 1.0); - EXPECT_THAT(shortest_path_on_dag.ArcPathTo(destination_2), ElementsAre(1)); - EXPECT_THAT(shortest_path_on_dag.NodePathTo(destination_2), - ElementsAre(source, destination_2)); - - EXPECT_TRUE(shortest_path_on_dag.IsReachable(destination_3)); - EXPECT_THAT(shortest_path_on_dag.LengthTo(destination_3), 2.0); - EXPECT_THAT(shortest_path_on_dag.ArcPathTo(destination_3), ElementsAre(2)); - EXPECT_THAT(shortest_path_on_dag.NodePathTo(destination_3), - ElementsAre(source, destination_3)); -} - -TEST(ShortestPathsOnDagWrapperTest, UpdateCost) { - const int source = 0; - const int destination = 1; - const int a = 2; - const int b = 3; - const int num_nodes = 4; - util::ListGraph<> graph(num_nodes, /*arc_capacity=*/4); - std::vector arc_lengths; - graph.AddArc(source, a); - arc_lengths.push_back(5.0); - graph.AddArc(source, b); - arc_lengths.push_back(2.0); - graph.AddArc(a, destination); - arc_lengths.push_back(3.0); - graph.AddArc(b, destination); - arc_lengths.push_back(20.0); - const std::vector topological_order = {source, a, b, destination}; - ShortestPathsOnDagWrapper> shortest_path_on_dag( - &graph, &arc_lengths, topological_order); - shortest_path_on_dag.RunShortestPathOnDag({source}); - - EXPECT_TRUE(shortest_path_on_dag.IsReachable(destination)); - EXPECT_THAT(shortest_path_on_dag.LengthTo(destination), 8.0); - EXPECT_THAT(shortest_path_on_dag.ArcPathTo(destination), ElementsAre(0, 2)); - EXPECT_THAT(shortest_path_on_dag.NodePathTo(destination), - ElementsAre(source, a, destination)); - - // Update the length of arc b -> destination from 20.0 to -1.0. - arc_lengths[3] = -1.0; - shortest_path_on_dag.RunShortestPathOnDag({source}); - - EXPECT_TRUE(shortest_path_on_dag.IsReachable(destination)); - EXPECT_THAT(shortest_path_on_dag.LengthTo(destination), 1.0); - EXPECT_THAT(shortest_path_on_dag.ArcPathTo(destination), ElementsAre(1, 3)); - EXPECT_THAT(shortest_path_on_dag.NodePathTo(destination), - ElementsAre(source, b, destination)); -} - -// Builds a random DAG with a given number of nodes and arcs where 0 is always -// the first and num_nodes-1 the last element in the topological order. Note -// that the graph always include at least one arc from 0 to num_nodes-1. -std::pair, std::vector> 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 topological_order(num_nodes); - topological_order.back() = num_nodes - 1; - absl::Span non_start_end = - absl::MakeSpan(topological_order).subspan(1, num_nodes - 2); - absl::c_iota(non_start_end, 1); - absl::c_shuffle(non_start_end, bit_gen); - int edges_added = 0; - util::StaticGraph<> graph(num_nodes, num_arcs); - graph.AddArc(0, num_nodes - 1); - while (edges_added < num_arcs - 1) { - int start_index = absl::Uniform(bit_gen, 0, num_nodes - 1); - int end_index = absl::Uniform(bit_gen, start_index + 1, num_nodes); - graph.AddArc(topological_order[start_index], topological_order[end_index]); - edges_added++; - } - graph.Build(); - return {graph, topological_order}; -} - -// The length of each arc is drawn uniformly at random within a given interval -// except if the first arc from 0 to num_nodes-1 where it is set to a large -// length. -std::vector GenerateRandomLengths(const util::StaticGraph<>& graph, - const double min_length = 0.0, - const double max_length = 10.0, - const double large_length = 10000.0) { - absl::BitGen bit_gen; - std::vector arc_lengths; - arc_lengths.reserve(graph.num_arcs()); - bool large_length_set = false; - for (util::StaticGraph<>::ArcIndex arc = 0; arc < graph.num_arcs(); ++arc) { - if (!large_length_set && graph.Tail(arc) == 0 && - graph.Head(arc) == graph.num_nodes() - 1) { - arc_lengths.push_back(large_length); - large_length_set = true; - continue; - } - arc_lengths.push_back(static_cast( - absl::Uniform(bit_gen, min_length, max_length))); - } - return arc_lengths; -} - -TEST(ShortestPathsOnDagWrapperTest, RandomizedStressTest) { - absl::BitGen bit_gen; - const int kNumTests = 10000; - for (int test = 0; test < kNumTests; ++test) { - const int num_nodes = absl::Uniform(bit_gen, 2, 12); - const int num_arcs = absl::Uniform( - bit_gen, 1, std::min(num_nodes * (num_nodes - 1) / 2, 15)); - // Generate a random DAG with random lengths. - const auto [graph, topological_order] = BuildRandomDag(num_nodes, num_arcs); - const std::vector arc_lengths = GenerateRandomLengths(graph); - - // Run Floyd-Warshall as a 'reference' shortest path algorithm. - FlatMatrix ref_dist(num_nodes, num_nodes, kInf); - for (int a = 0; a < num_arcs; ++a) { - double& d = ref_dist[graph.Tail(a)][graph.Head(a)]; - if (arc_lengths[a] < d) d = arc_lengths[a]; - } - for (int node = 0; node < num_nodes; ++node) { - ref_dist[node][node] = 0; - } - for (int k = 0; k < num_nodes; ++k) { - for (int i = 0; i < num_nodes; ++i) { - for (int j = 0; j < num_nodes; ++j) { - const double dist_through_k = ref_dist[i][k] + ref_dist[k][j]; - if (dist_through_k < ref_dist[i][j]) ref_dist[i][j] = dist_through_k; - } - } - } - - // Now, run some shortest paths and verify that they match. To balance out - // the FW (Floyd-Warshall) which is O(N³), we run more than one shortest - // path per FW. - ShortestPathsOnDagWrapper> shortest_path_on_dag( - &graph, &arc_lengths, topological_order); - for (int _ = 0; _ < 20; ++_) { - // Draw sources (*with* repetition) with initial distances. - const int num_sources = absl::Uniform(bit_gen, 1, 5); - std::vector sources(num_sources); - for (int& source : sources) { - source = absl::Uniform(bit_gen, 0, num_nodes); - } - // Precompute the reference minimum distance to each node (using any of - // the sources), and the expected reached nodes: any node whose distance - // is < kInf. - std::vector node_min_dist(num_nodes, kInf); - std::vector expected_reached_nodes; - for (int node = 0; node < num_nodes; ++node) { - double min_dist = kInf; - for (const int source : sources) { - min_dist = std::min(min_dist, ref_dist[source][node]); - } - node_min_dist[node] = min_dist; - if (min_dist < kInf) expected_reached_nodes.push_back(node); - } - shortest_path_on_dag.RunShortestPathOnDag(sources); - for (const int node : expected_reached_nodes) { - EXPECT_TRUE(shortest_path_on_dag.IsReachable(node)); - EXPECT_EQ(shortest_path_on_dag.LengthTo(node), node_min_dist[node]) - << node; - } - ASSERT_FALSE(HasFailure()) - << DUMP_VARS(num_nodes, num_arcs, num_sources, sources, arc_lengths) - << "\n With graph:\n" - << util::GraphToString(graph, util::PRINT_GRAPH_ARCS); - } - } -} - -// Debug tests. -#ifndef NDEBUG -TEST(ShortestPathOnDagTest, MinusInfWeight) { - EXPECT_DEATH( - ShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, -kInf}}, - /*source=*/0, /*destination=*/1), - "-inf"); -} - -TEST(ShortestPathOnDagTest, NaNWeight) { - EXPECT_DEATH( - ShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/ - {{0, 1, std::numeric_limits::quiet_NaN()}}, - /*source=*/0, /*destination=*/1), - "NaN"); -} - -TEST(ShortestPathsOnDagWrapperTest, ValidateTopologicalOrder) { - const int source = 0; - const int destination = 1; - const int num_nodes = 2; - util::ListGraph<> graph(num_nodes, /*arc_capacity=*/1); - std::vector arc_lengths; - graph.AddArc(source, destination); - arc_lengths.push_back(1.0); - const std::vector topological_order = {source}; - - EXPECT_DEATH(ShortestPathsOnDagWrapper>( - &graph, &arc_lengths, topological_order), - "Invalid topological order"); -} -#endif // NDEBUG - -// ----------------------------------------------------------------------------- -// ShortestPathsOnDagWrapper benchmarks. -// ----------------------------------------------------------------------------- -void BM_RandomDag(benchmark::State& state) { - absl::BitGen bit_gen; - // Generate a fixed random DAG. - const int num_nodes = state.range(0); - const int num_arcs = num_nodes * state.range(1); - const auto [graph, topological_order] = BuildRandomDag(num_nodes, num_arcs); - // Generate at most 20 scenarios of random arc lengths. - const int num_scenarios = std::min(20, (int)state.iterations()); - std::vector> arc_lengths_scenarios; - for (int _ = 0; _ < num_scenarios; ++_) { - arc_lengths_scenarios.push_back(GenerateRandomLengths(graph)); - } - std::vector arc_lengths = arc_lengths_scenarios.front(); - ShortestPathsOnDagWrapper> shortest_path_on_dag( - &graph, &arc_lengths, topological_order); - for (auto _ : state) { - // Pick a arc lengths scenario at random. - arc_lengths = - arc_lengths_scenarios[absl::Uniform(bit_gen, 0, num_scenarios)]; - shortest_path_on_dag.RunShortestPathOnDag({0}); - CHECK(shortest_path_on_dag.IsReachable(num_nodes - 1)); - const double minimum_length = shortest_path_on_dag.LengthTo(num_nodes - 1); - CHECK_GE(minimum_length, 0.0); - CHECK_LE(minimum_length, 10000.0); - } - state.SetItemsProcessed(state.iterations() * (num_nodes + num_arcs)); -} - -BENCHMARK(BM_RandomDag) - ->ArgPair(1000, 10) - ->ArgPair(1 << 16, 4) - ->ArgPair(1 << 16, 16) - ->ArgPair(1 << 22, 4) - ->ArgPair(1 << 22, 16); - -void BM_LineDag(benchmark::State& state) { - const int num_nodes = state.range(0); - const int num_edges = num_nodes - 1; - std::vector topological_order(num_nodes); - util::StaticGraph<> graph(num_nodes, num_edges); - absl::c_iota(topological_order, 0); - for (int i = 0; i < num_nodes - 1; ++i) { - graph.AddArc(i, i + 1); - } - graph.Build(); - std::vector arc_lengths(num_edges, 1); - ShortestPathsOnDagWrapper> shortest_path_on_dag( - &graph, &arc_lengths, topological_order); - for (auto _ : state) { - shortest_path_on_dag.RunShortestPathOnDag({0}); - CHECK(shortest_path_on_dag.IsReachable(num_nodes - 1)); - CHECK_EQ(shortest_path_on_dag.LengthTo(num_nodes - 1), num_nodes - 1); - CHECK_EQ(shortest_path_on_dag.ArcPathTo(num_nodes - 1).size(), - num_nodes - 1); - CHECK_EQ(shortest_path_on_dag.NodePathTo(num_nodes - 1).size(), num_nodes); - } - state.SetItemsProcessed(state.iterations() * num_nodes); -} - -BENCHMARK(BM_LineDag)->Arg(1 << 16)->Arg(1 << 22)->Arg(1 << 24); - -// ----------------------------------------------------------------------------- -// KShortestPathOnDagTest and KShortestPathsOnDagWrapperTest. -// ----------------------------------------------------------------------------- -TEST(KShortestPathOnDagTest, EmptyGraph) { - EXPECT_DEATH( - KShortestPathsOnDag(/*num_nodes=*/0, /*arcs_with_length=*/{}, - /*source=*/0, /*destination=*/0, /*path_count=*/2), - "num_nodes\\(\\) > 0"); -} - -TEST(KShortestPathOnDagTest, NoArcGraph) { - EXPECT_DEATH( - KShortestPathsOnDag(/*num_nodes=*/1, /*arcs_with_length=*/{}, - /*source=*/0, /*destination=*/0, /*path_count=*/2), - "num_arcs\\(\\) > 0"); -} - -TEST(KShortestPathOnDagTest, NonExistingSourceBecauseNegative) { - EXPECT_DEATH( - KShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, 0.0}}, - /*source=*/-1, /*destination=*/1, /*path_count=*/2), - "Node must be nonnegative"); -} - -TEST(KShortestPathOnDagTest, NonExistingSourceBecauseTooLarge) { - EXPECT_DEATH( - KShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, 0.0}}, - /*source=*/3, /*destination=*/1, /*path_count=*/2), - "num_nodes\\(\\)"); -} - -TEST(KShortestPathOnDagTest, NonExistingDestinationBecauseNegative) { - EXPECT_DEATH( - KShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, 0.0}}, - /*source=*/0, /*destination=*/-1, /*path_count=*/2), - "Node must be nonnegative"); -} - -TEST(KShortestPathOnDagTest, NonExistingDestinationBecauseTooLarge) { - EXPECT_DEATH( - KShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, 0.0}}, - /*source=*/0, /*destination=*/3, /*path_count=*/2), - "num_nodes\\(\\)"); -} - -TEST(KShortestPathOnDagTest, KEqualsZero) { - EXPECT_DEATH( - KShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, 0.0}}, - /*source=*/0, /*destination=*/1, /*path_count=*/0), - "path_count must be greater than 0"); -} - -TEST(KShortestPathOnDagTest, OnlyHasOnePath) { - const int source = 0; - const int destination = 1; - const int a = 2; - const int num_nodes = 3; - const std::vector arcs_with_length = {{source, a, 1.0}, - {a, destination, 1.0}}; - - EXPECT_THAT(KShortestPathsOnDag(num_nodes, arcs_with_length, source, - destination, /*path_count=*/2), - ElementsAre(FieldsAre( - /*length=*/2.0, /*arc_path=*/ElementsAre(0, 1), - /*node_path=*/ElementsAre(source, a, destination)))); -} - -TEST(KShortestPathOnDagTest, SourceIsDestination) { - const int source = 0; - const int destination = 1; - const int num_nodes = 2; - const std::vector arcs_with_length = { - {source, destination, 1.0}}; - - EXPECT_THAT(KShortestPathsOnDag(num_nodes, arcs_with_length, source, source, - /*path_count=*/2), - ElementsAre(FieldsAre( - /*length=*/0.0, /*arc_path=*/IsEmpty(), - /*node_path=*/ElementsAre(source)))); -} - -TEST(KShortestPathOnDagTest, HasTwoPaths) { - const int source = 0; - const int a = 1; - const int destination = 2; - const int num_nodes = 3; - const std::vector arcs_with_length = { - {source, a, 1.0}, {source, destination, 30.0}, {a, destination, 1.0}}; - - EXPECT_THAT( - KShortestPathsOnDag(num_nodes, arcs_with_length, source, destination, - /*path_count=*/3), - ElementsAre(FieldsAre( - /*length=*/2.0, /*arc_path=*/ElementsAre(0, 2), - /*node_path=*/ElementsAre(source, a, destination)), - FieldsAre( - /*length=*/30.0, /*arc_path=*/ElementsAre(1), - /*node_path=*/ElementsAre(source, destination)))); -} - -TEST(KShortestPathOnDagTest, HasTwoPathsWithLongerPath) { - const int source = 0; - const int a = 1; - const int b = 2; - const int c = 3; - const int destination = 4; - const int num_nodes = 5; - const std::vector arcs_with_length = { - {source, a, 1.0}, - {source, destination, 30.0}, - {a, b, 1.0}, - {b, c, 1.0}, - {c, destination, 1.0}}; - - EXPECT_THAT( - KShortestPathsOnDag(num_nodes, arcs_with_length, source, destination, - /*path_count=*/3), - ElementsAre(FieldsAre( - /*length=*/4.0, /*arc_path=*/ElementsAre(0, 2, 3, 4), - /*node_path=*/ElementsAre(source, a, b, c, destination)), - FieldsAre( - /*length=*/30.0, /*arc_path=*/ElementsAre(1), - /*node_path=*/ElementsAre(source, destination)))); -} - -TEST(KShortestPathOnDagTest, HeapSizeMustBeLargerThanPathCount) { - const int source = 0; - const int destination = 1; - const int num_nodes = 2; - const std::vector arcs_with_length = { - {source, destination, 2.0}, - {source, destination, 3.0}, - {source, destination, 1.0}}; - - EXPECT_THAT( - KShortestPathsOnDag(num_nodes, arcs_with_length, source, destination, - /*path_count=*/2), - ElementsAre(FieldsAre( - /*length=*/1.0, /*arc_path=*/ElementsAre(2), - /*node_path=*/ElementsAre(source, destination)), - FieldsAre( - /*length=*/2.0, /*arc_path=*/ElementsAre(0), - /*node_path=*/ElementsAre(source, destination)))); -} - -TEST(KShortestPathOnDagTest, LargerGraphWithNegativeCost) { - const int source = 0; - const int a = 3; - const int b = 2; - const int c = 1; - const int destination = 4; - const int num_nodes = 5; - const std::vector arcs_with_length = { - {a, c, 5.0}, {source, b, 7.0}, {a, b, 1.0}, - {source, a, 3.0}, {c, destination, 5.0}, {b, destination, -2.0}}; - - EXPECT_THAT( - KShortestPathsOnDag(num_nodes, arcs_with_length, source, destination, - /*path_count=*/2), - ElementsAre( - FieldsAre(/*length=*/2.0, /*arc_path=*/ElementsAre(3, 2, 5), - /*node_path=*/ElementsAre(source, a, b, destination)), - FieldsAre(/*length=*/5.0, /*arc_path=*/ElementsAre(1, 5), - /*node_path=*/ElementsAre(source, b, destination)))); -} - -TEST(KShortestPathOnDagTest, SetsNotConnected) { - const int source = 0; - const int destination = 1; - const int a = 2; - const int num_nodes = 3; - const std::vector arcs_with_length = {{source, a, 1.0}}; - - EXPECT_THAT(KShortestPathsOnDag(num_nodes, arcs_with_length, source, - destination, /*path_count=*/2), - ElementsAre(FieldsAre(/*length=*/kInf, /*arc_path=*/IsEmpty(), - /*node_path=*/IsEmpty()))); -} - -TEST(KShortestPathOnDagTest, TwoConnectedComponents) { - const int a = 0; - const int b = 1; - const int c = 2; - const int d = 3; - const int e = 4; - const int num_nodes = 5; - const std::vector arcs_with_length = { - {a, b, 0.0}, {b, c, 0.0}, {d, e, 0.0}}; - - EXPECT_THAT(KShortestPathsOnDag(num_nodes, arcs_with_length, /*source=*/a, - /*destination=*/e, /*path_count=*/2), - ElementsAre(FieldsAre(/*length=*/kInf, /*arc_path=*/IsEmpty(), - /*node_path=*/IsEmpty()))); - EXPECT_THAT(KShortestPathsOnDag(num_nodes, arcs_with_length, /*source=*/b, - /*destination=*/d, /*path_count=*/2), - ElementsAre(FieldsAre(/*length=*/kInf, /*arc_path=*/IsEmpty(), - /*node_path=*/IsEmpty()))); -} - -TEST(KShortestPathOnDagTest, SetsNotConnectedDueToInfiniteCost) { - const int source = 0; - const int destination = 1; - const int a = 2; - const int num_nodes = 3; - const std::vector arcs_with_length = {{a, destination, 1.0}, - {source, a, kInf}}; - - EXPECT_THAT(KShortestPathsOnDag(num_nodes, arcs_with_length, source, - destination, /*path_count=*/2), - ElementsAre(FieldsAre(/*length=*/kInf, /*arc_path=*/IsEmpty(), - /*node_path=*/IsEmpty()))); -} - -TEST(KShortestPathOnDagTest, AvoidInfiniteCost) { - const int source = 0; - const int destination = 1; - const int a = 2; - const int b = 3; - const int num_nodes = 4; - const std::vector arcs_with_length = {{a, destination, 1.0}, - {b, destination, 1.0}, - {source, a, kInf}, - {source, b, 3.0}}; - - EXPECT_THAT(KShortestPathsOnDag(num_nodes, arcs_with_length, source, - destination, /*path_count=*/2), - ElementsAre(FieldsAre( - /*length=*/4.0, /*arc_path=*/ElementsAre(3, 1), - /*node_path=*/ElementsAre(source, b, destination)))); -} - -TEST(KShortestPathOnDagTest, SourceNotFirst) { - const int destination = 0; - const int source = 1; - const int num_nodes = 2; - const std::vector arcs_with_length = { - {source, destination, 1.0}}; - - EXPECT_THAT(KShortestPathsOnDag(num_nodes, arcs_with_length, source, - destination, /*path_count=*/2), - ElementsAre(FieldsAre( - /*length=*/1.0, /*arc_path=*/ElementsAre(0), - /*node_path=*/ElementsAre(source, destination)))); -} - -TEST(KShortestPathOnDagTest, MultipleArcs) { - const int source = 0; - const int destination = 1; - const int num_nodes = 2; - const std::vector arcs_with_length = { - {source, destination, 4.0}, - {source, destination, 3.0}, - {source, destination, -2.0}, - {source, destination, 0.0}}; - - EXPECT_THAT(KShortestPathsOnDag(num_nodes, arcs_with_length, source, - destination, /*path_count=*/3), - ElementsAre(FieldsAre( - /*length=*/-2.0, /*arc_path=*/ElementsAre(2), - /*node_path=*/ElementsAre(source, destination)), - FieldsAre( - /*length=*/0.0, /*arc_path=*/ElementsAre(3), - /*node_path=*/ElementsAre(source, destination)), - FieldsAre( - /*length=*/3.0, /*arc_path=*/ElementsAre(1), - /*node_path=*/ElementsAre(source, destination)))); -} - -TEST(KShortestPathOnDagTest, UpdateCost) { - const int source = 0; - const int destination = 1; - const int a = 2; - const int b = 3; - const int num_nodes = 4; - std::vector arcs_with_length = {{source, a, 5.0}, - {source, b, 2.0}, - {a, destination, 3.0}, - {b, destination, 20.0}}; - - EXPECT_THAT( - KShortestPathsOnDag(num_nodes, arcs_with_length, source, destination, - /*path_count=*/2), - ElementsAre(FieldsAre( - /*length=*/8.0, /*arc_path=*/ElementsAre(0, 2), - /*node_path=*/ElementsAre(source, a, destination)), - FieldsAre( - /*length=*/22.0, /*arc_path=*/ElementsAre(1, 3), - /*node_path=*/ElementsAre(source, b, destination)))); - - // Update the length of arc b -> destination from 20.0 to -1.0. - arcs_with_length[3].length = -1.0; - - EXPECT_THAT( - KShortestPathsOnDag(num_nodes, arcs_with_length, source, destination, - /*path_count=*/2), - ElementsAre(FieldsAre( - /*length=*/1.0, /*arc_path=*/ElementsAre(1, 3), - /*node_path=*/ElementsAre(source, b, destination)), - FieldsAre( - /*length=*/8.0, /*arc_path=*/ElementsAre(0, 2), - /*node_path=*/ElementsAre(source, a, destination)))); -} - -TEST(KShortestPathsOnDagWrapperTest, MultipleSources) { - const int source_1 = 0; - const int source_2 = 1; - const int destination = 2; - const int num_nodes = 3; - util::ListGraph<> graph(num_nodes, /*arc_capacity=*/2); - std::vector arc_lengths; - graph.AddArc(source_1, destination); - arc_lengths.push_back(-6.0); - graph.AddArc(source_2, destination); - arc_lengths.push_back(3.0); - const std::vector topological_order = {source_2, source_1, destination}; - const int path_count = 2; - KShortestPathsOnDagWrapper> shortest_paths_on_dag( - &graph, &arc_lengths, topological_order, path_count); - shortest_paths_on_dag.RunKShortestPathOnDag({source_1, source_2}); - - EXPECT_TRUE(shortest_paths_on_dag.IsReachable(destination)); - EXPECT_THAT(shortest_paths_on_dag.LengthsTo(destination), - ElementsAre(-6.0, 3.0)); - EXPECT_THAT(shortest_paths_on_dag.ArcPathsTo(destination), - ElementsAre(ElementsAre(0), ElementsAre(1))); - EXPECT_THAT(shortest_paths_on_dag.NodePathsTo(destination), - ElementsAre(ElementsAre(source_1, destination), - ElementsAre(source_2, destination))); -} - -TEST(KShortestPathsOnDagWrapperTest, ShortestPathGoesThroughMultipleSources) { - const int source_1 = 0; - const int source_2 = 1; - const int destination = 2; - const int num_nodes = 3; - util::ListGraph<> graph(num_nodes, /*arc_capacity=*/3); - std::vector arc_lengths; - graph.AddArc(source_1, source_2); - arc_lengths.push_back(-7.0); - graph.AddArc(source_2, destination); - arc_lengths.push_back(3.0); - graph.AddArc(source_1, destination); - arc_lengths.push_back(5.0); - const std::vector topological_order = {source_1, source_2, destination}; - const int path_count = 2; - KShortestPathsOnDagWrapper> shortest_paths_on_dag( - &graph, &arc_lengths, topological_order, path_count); - shortest_paths_on_dag.RunKShortestPathOnDag({source_1, source_2}); - - EXPECT_TRUE(shortest_paths_on_dag.IsReachable(destination)); - EXPECT_THAT(shortest_paths_on_dag.LengthsTo(destination), - ElementsAre(-4.0, 3.0)); - EXPECT_THAT(shortest_paths_on_dag.ArcPathsTo(destination), - ElementsAre(ElementsAre(0, 1), ElementsAre(1))); - EXPECT_THAT(shortest_paths_on_dag.NodePathsTo(destination), - ElementsAre(ElementsAre(source_1, source_2, destination), - ElementsAre(source_2, destination))); -} - -TEST(KShortestPathsOnDagWrapperTest, MultipleDestinations) { - const int source = 0; - const int destination_1 = 1; - const int destination_2 = 2; - const int destination_3 = 3; - const int num_nodes = 4; - util::ListGraph<> graph(num_nodes, /*arc_capacity=*/3); - std::vector arc_lengths; - graph.AddArc(source, destination_1); - arc_lengths.push_back(3.0); - graph.AddArc(source, destination_2); - arc_lengths.push_back(1.0); - graph.AddArc(source, destination_3); - arc_lengths.push_back(2.0); - const std::vector topological_order = {source, destination_3, - destination_1, destination_2}; - const int path_count = 2; - KShortestPathsOnDagWrapper> shortest_paths_on_dag( - &graph, &arc_lengths, topological_order, path_count); - shortest_paths_on_dag.RunKShortestPathOnDag({source}); - - EXPECT_TRUE(shortest_paths_on_dag.IsReachable(destination_1)); - EXPECT_THAT(shortest_paths_on_dag.LengthsTo(destination_1)[0], 3.0); - EXPECT_THAT(shortest_paths_on_dag.ArcPathsTo(destination_1)[0], - ElementsAre(0)); - EXPECT_THAT(shortest_paths_on_dag.NodePathsTo(destination_1)[0], - ElementsAre(source, destination_1)); - - EXPECT_TRUE(shortest_paths_on_dag.IsReachable(destination_2)); - EXPECT_THAT(shortest_paths_on_dag.LengthsTo(destination_2)[0], 1.0); - EXPECT_THAT(shortest_paths_on_dag.ArcPathsTo(destination_2)[0], - ElementsAre(1)); - EXPECT_THAT(shortest_paths_on_dag.NodePathsTo(destination_2)[0], - ElementsAre(source, destination_2)); - - EXPECT_TRUE(shortest_paths_on_dag.IsReachable(destination_3)); - EXPECT_THAT(shortest_paths_on_dag.LengthsTo(destination_3)[0], 2.0); - EXPECT_THAT(shortest_paths_on_dag.ArcPathsTo(destination_3)[0], - ElementsAre(2)); - EXPECT_THAT(shortest_paths_on_dag.NodePathsTo(destination_3)[0], - ElementsAre(source, destination_3)); -} - -TEST(KShortestPathsOnDagWrapperTest, RandomizedStressTest) { - absl::BitGen bit_gen; - const int kNumTests = 10000; - for (int test = 0; test < kNumTests; ++test) { - const int num_nodes = absl::Uniform(bit_gen, 2, 12); - const int num_arcs = absl::Uniform( - bit_gen, 1, std::min(num_nodes * (num_nodes - 1) / 2, 15)); - // Generate a random DAG with random lengths. - const auto [graph, topological_order] = BuildRandomDag(num_nodes, num_arcs); - const std::vector arc_lengths = GenerateRandomLengths(graph); - - ShortestPathsOnDagWrapper> shortest_path_on_dag( - &graph, &arc_lengths, topological_order); - const int path_count = 5; - KShortestPathsOnDagWrapper> shortest_paths_on_dag( - &graph, &arc_lengths, topological_order, path_count); - for (int _ = 0; _ < 20; ++_) { - // Draw sources (*with* repetition) with initial distances. - const int num_sources = absl::Uniform(bit_gen, 1, 5); - std::vector sources(num_sources); - for (int& source : sources) { - source = absl::Uniform(bit_gen, 0, num_nodes); - } - // Compute the number of paths - std::vector all_paths_count(num_nodes); - for (const int source : sources) { - all_paths_count[source] = 1; - } - for (const int from : topological_order) { - for (const int arc : graph.OutgoingArcs(from)) { - const int to = graph.Head(arc); - all_paths_count[to] += all_paths_count[from]; - } - } - - shortest_path_on_dag.RunShortestPathOnDag(sources); - shortest_paths_on_dag.RunKShortestPathOnDag(sources); - for (const int node : shortest_path_on_dag.reached_nodes()) { - EXPECT_TRUE(shortest_paths_on_dag.IsReachable(node)); - EXPECT_EQ(shortest_paths_on_dag.LengthsTo(node)[0], - shortest_path_on_dag.LengthTo(node)) - << node; - EXPECT_EQ(shortest_paths_on_dag.LengthsTo(node).size(), - std::min(all_paths_count[node], path_count)) - << node; - } - ASSERT_FALSE(HasFailure()) - << DUMP_VARS(num_nodes, num_arcs, num_sources, sources, arc_lengths) - << "\n With graph:\n" - << util::GraphToString(graph, util::PRINT_GRAPH_ARCS); - } - } -} - -// Debug tests. -#ifndef NDEBUG -TEST(KShortestPathOnDagTest, MinusInfWeight) { - EXPECT_DEATH( - KShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, -kInf}}, - /*source=*/0, /*destination=*/1, /*path_count=*/2), - "-inf"); -} - -TEST(KShortestPathOnDagTest, NaNWeight) { - EXPECT_DEATH( - KShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/ - {{0, 1, std::numeric_limits::quiet_NaN()}}, - /*source=*/0, /*destination=*/1, /*path_count=*/2), - "NaN"); -} - -TEST(KShortestPathsOnDagWrapperTest, ValidateTopologicalOrder) { - const int source = 0; - const int destination = 1; - const int num_nodes = 2; - util::ListGraph<> graph(num_nodes, /*arc_capacity=*/1); - std::vector arc_lengths; - graph.AddArc(source, destination); - arc_lengths.push_back(1.0); - const std::vector topological_order = {source}; - const int path_count = 2; - - EXPECT_DEATH(KShortestPathsOnDagWrapper>( - &graph, &arc_lengths, topological_order, path_count), - "Invalid topological order"); -} -#endif // NDEBUG - -// ----------------------------------------------------------------------------- -// KShortestPathsOnDagWrapper benchmarks. -// ----------------------------------------------------------------------------- -void BM_RandomDag_K(benchmark::State& state) { - absl::BitGen bit_gen; - // Generate a fixed random DAG. - const int num_nodes = state.range(0); - const int num_arcs = num_nodes * state.range(1); - const int path_count = state.range(2); - const auto [graph, topological_order] = BuildRandomDag(num_nodes, num_arcs); - // Generate at most 20 scenarios of random arc lengths. - const int num_scenarios = std::min(20, (int)state.iterations()); - std::vector> arc_lengths_scenarios; - for (int _ = 0; _ < num_scenarios; ++_) { - arc_lengths_scenarios.push_back(GenerateRandomLengths(graph)); - } - std::vector arc_lengths = arc_lengths_scenarios.front(); - KShortestPathsOnDagWrapper> shortest_paths_on_dag( - &graph, &arc_lengths, topological_order, path_count); - for (auto _ : state) { - // Pick a arc lengths scenario at random. - arc_lengths = - arc_lengths_scenarios[absl::Uniform(bit_gen, 0, num_scenarios)]; - shortest_paths_on_dag.RunKShortestPathOnDag({0}); - CHECK(shortest_paths_on_dag.IsReachable(num_nodes - 1)); - const std::vector lengths = - shortest_paths_on_dag.LengthsTo(num_nodes - 1); - CHECK_GE(lengths[0], 0.0); - CHECK_LE(lengths[0], 10000.0); - } - state.SetItemsProcessed(state.iterations() * (num_nodes + num_arcs)); -} - -BENCHMARK(BM_RandomDag_K) - ->Args({1000, 10, 4}) - ->Args({1 << 16, 4, 4}) - ->Args({1 << 16, 4, 16}) - ->Args({1 << 16, 16, 4}) - ->Args({1 << 16, 16, 16}) - ->Args({1 << 22, 4, 4}) - ->Args({1 << 22, 4, 16}); - -} // namespace -} // namespace operations_research diff --git a/ortools/graph/graph.h b/ortools/graph/graph.h index 8def30b383..dc25dfc933 100644 --- a/ortools/graph/graph.h +++ b/ortools/graph/graph.h @@ -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 { BeginEndWrapper 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 OutgoingArcsStartingFrom( NodeIndexType node, ArcIndexType from) const; @@ -431,9 +435,16 @@ class StaticGraph : public BaseGraph { NodeIndexType Head(ArcIndexType arc) const; NodeIndexType Tail(ArcIndexType arc) const; ArcIndexType OutDegree(NodeIndexType node) const; // Work in O(1). - BeginEndWrapper OutgoingArcs(NodeIndexType node) const; - BeginEndWrapper OutgoingArcsStartingFrom( - NodeIndexType node, ArcIndexType from) const; + IntegerRange OutgoingArcs(NodeIndexType node) const { + return IntegerRange(start_[node], DirectArcLimit(node)); + } + IntegerRange OutgoingArcsStartingFrom(NodeIndexType node, + ArcIndexType from) const { + DCHECK_GE(from, start_[node]); + const ArcIndexType limit = DirectArcLimit(node); + return IntegerRange(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 OutgoingArcs(NodeIndexType node) const; + IntegerRange OutgoingArcs(NodeIndexType node) const { + return IntegerRange(start_[node], DirectArcLimit(node)); + } + IntegerRange OutgoingArcsStartingFrom(NodeIndexType node, + ArcIndexType from) const { + DCHECK_GE(from, start_[node]); + const ArcIndexType limit = DirectArcLimit(node); + return IntegerRange(from == Base::kNilArc ? limit : from, + limit); + } BeginEndWrapper IncomingArcs(NodeIndexType node) const; BeginEndWrapper OutgoingOrOppositeIncomingArcs(NodeIndexType node) const; BeginEndWrapper OppositeIncomingArcs( NodeIndexType node) const; - BeginEndWrapper OutgoingArcsStartingFrom( - NodeIndexType node, ArcIndexType from) const; BeginEndWrapper IncomingArcsStartingFrom( NodeIndexType node, ArcIndexType from) const; BeginEndWrapper @@ -1228,8 +1246,6 @@ StaticGraph::FromArcs(NodeIndexType num_nodes, return g; } -DEFINE_RANGE_BASED_ARC_ITERATION(StaticGraph, Outgoing); - template absl::Span StaticGraph::operator[](NodeIndexType node) const { @@ -1377,6 +1393,7 @@ void StaticGraph::Build( } } +// TODO(b/385094969): Remove this class. template class StaticGraph::OutgoingArcIterator { public: @@ -1398,15 +1415,6 @@ class StaticGraph::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::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::Build( } } +// TODO(b/385094969): Remove this class. template class ReverseArcStaticGraph::OutgoingArcIterator { public: @@ -1817,10 +1825,6 @@ class ReverseArcStaticGraph::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_; diff --git a/ortools/graph/iterators.h b/ortools/graph/iterators.h index ea99f9f5e0..90b549e9d4 100644 --- a/ortools/graph/iterators.h +++ b/ortools/graph/iterators.h @@ -227,28 +227,6 @@ class IntegerRange : public BeginEndWrapper> { } }; -// Allow iterating over a vector as a mutable vector. -template -struct MutableVectorIteration { - explicit MutableVectorIteration(std::vector* v) : v_(v) {} - struct Iterator { - explicit Iterator(typename std::vector::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::iterator it_; - }; - Iterator begin() { return Iterator(v_->begin()); } - Iterator end() { return Iterator(v_->end()); } - - private: - std::vector* const v_; -}; } // namespace util #endif // UTIL_GRAPH_ITERATORS_H_ diff --git a/ortools/graph/line_graph_test.cc b/ortools/graph/line_graph_test.cc index c86700b0b1..5ffeb913ef 100644 --- a/ortools/graph/line_graph_test.cc +++ b/ortools/graph/line_graph_test.cc @@ -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; diff --git a/ortools/graph/linear_assignment.h b/ortools/graph/linear_assignment.h index 014a3882c4..188a46ddab 100644 --- a/ortools/graph/linear_assignment.h +++ b/ortools/graph/linear_assignment.h @@ -1227,8 +1227,10 @@ LinearSumAssignment::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::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::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::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::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; } } diff --git a/ortools/graph/min_cost_flow.cc b/ortools/graph/min_cost_flow.cc index c956e0f287..2d9fc4088c 100644 --- a/ortools/graph/min_cost_flow.cc +++ b/ortools/graph/min_cost_flow.cc @@ -188,8 +188,7 @@ bool GenericMinCostFlowOutgoingArcs(node)) { residual_arc_capacity_[arc] = std::min(residual_arc_capacity_[arc], upper_bound); min_node_excess[node] = @@ -204,8 +203,7 @@ bool GenericMinCostFlowIncomingArcs(node)) { residual_arc_capacity_[arc] = std::min(residual_arc_capacity_[arc], upper_bound); max_node_excess[node] = diff --git a/ortools/graph/min_cost_flow.h b/ortools/graph/min_cost_flow.h index 2d13d1e6a4..e70c00e483 100644 --- a/ortools/graph/min_cost_flow.h +++ b/ortools/graph/min_cost_flow.h @@ -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 ArcIndexArray; diff --git a/ortools/graph/min_cost_flow_test.cc b/ortools/graph/min_cost_flow_test.cc index 98106f81a4..3440faabb8 100644 --- a/ortools/graph/min_cost_flow_test.cc +++ b/ortools/graph/min_cost_flow_test.cc @@ -646,8 +646,8 @@ bool CheckAssignmentFeasibility(const Graph& graph, absl::Span 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; diff --git a/ortools/graph/samples/BUILD.bazel b/ortools/graph/samples/BUILD.bazel index fcfde6be98..103dd7dd57 100644 --- a/ortools/graph/samples/BUILD.bazel +++ b/ortools/graph/samples/BUILD.bazel @@ -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") diff --git a/ortools/graph/samples/code_samples.bzl b/ortools/graph/samples/code_samples.bzl index b84d108e6c..1311db9c52 100644 --- a/ortools/graph/samples/code_samples.bzl +++ b/ortools/graph/samples/code_samples.bzl @@ -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", diff --git a/ortools/graph/samples/dag_constrained_shortest_path_sequential.cc b/ortools/graph/samples/dag_constrained_shortest_path_sequential.cc deleted file mode 100644 index 10e2a9d105..0000000000 --- a/ortools/graph/samples/dag_constrained_shortest_path_sequential.cc +++ /dev/null @@ -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 -#include -#include -#include -#include - -#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 weights(num_arcs); - // Resources are first indexed by resource, then by arc. - std::vector> resources(1, std::vector(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 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 topological_order = {source}; - for (int32_t i = 0; i < n; ++i) { - topological_order.push_back(i); - } - topological_order.push_back(dest); - - const std::vector sources = {source}; - const std::vector destinations = {dest}; - const std::vector max_resources = {1.0}; - - operations_research::ConstrainedShortestPathsOnDagWrapper> - 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> 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; -} diff --git a/ortools/graph/samples/dag_multiple_shortest_paths_one_to_all.cc b/ortools/graph/samples/dag_multiple_shortest_paths_one_to_all.cc deleted file mode 100644 index 7f51c3f1e4..0000000000 --- a/ortools/graph/samples/dag_multiple_shortest_paths_one_to_all.cc +++ /dev/null @@ -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 -#include -#include - -#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 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 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 topological_order, - util::graph::FastTopologicalSort(graph)); - - operations_research::KShortestPathsOnDagWrapper> - 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 lengths = shortest_paths_on_dag.LengthsTo(node); - const std::vector> 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; -} diff --git a/ortools/graph/samples/dag_multiple_shortest_paths_sequential.cc b/ortools/graph/samples/dag_multiple_shortest_paths_sequential.cc deleted file mode 100644 index 4538eae34f..0000000000 --- a/ortools/graph/samples/dag_multiple_shortest_paths_sequential.cc +++ /dev/null @@ -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 -#include -#include -#include -#include - -#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 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 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 topological_order = {source}; - for (int32_t i = 0; i < n; ++i) { - topological_order.push_back(i); - } - topological_order.push_back(dest); - - operations_research::KShortestPathsOnDagWrapper> - shortest_paths_on_dag(&graph, &weights, topological_order, - /*path_count=*/2); - shortest_paths_on_dag.RunKShortestPathOnDag({source}); - - const std::vector initial_lengths = - shortest_paths_on_dag.LengthsTo(dest); - const std::vector> 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> 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 lengths = shortest_paths_on_dag.LengthsTo(dest); - const std::vector> 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; -} diff --git a/ortools/graph/samples/dag_shortest_path_one_to_all.cc b/ortools/graph/samples/dag_shortest_path_one_to_all.cc deleted file mode 100644 index 118f31e221..0000000000 --- a/ortools/graph/samples/dag_shortest_path_one_to_all.cc +++ /dev/null @@ -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 -#include -#include - -#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 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 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 topological_order, - util::graph::FastTopologicalSort(graph)); - - operations_research::ShortestPathsOnDagWrapper> - 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; -} diff --git a/ortools/graph/samples/dag_shortest_path_sequential.cc b/ortools/graph/samples/dag_shortest_path_sequential.cc deleted file mode 100644 index 11b266d090..0000000000 --- a/ortools/graph/samples/dag_shortest_path_sequential.cc +++ /dev/null @@ -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 -#include -#include -#include -#include - -#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 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 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 topological_order = {source}; - for (int i = 0; i < n; ++i) { - topological_order.push_back(i); - } - topological_order.push_back(dest); - - operations_research::ShortestPathsOnDagWrapper> - 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> 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; -} diff --git a/ortools/graph/samples/dag_simple_constrained_shortest_path.cc b/ortools/graph/samples/dag_simple_constrained_shortest_path.cc deleted file mode 100644 index 1b4592c51e..0000000000 --- a/ortools/graph/samples/dag_simple_constrained_shortest_path.cc +++ /dev/null @@ -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 -#include - -#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 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 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; -} diff --git a/ortools/graph/samples/dag_simple_multiple_shortest_paths.cc b/ortools/graph/samples/dag_simple_multiple_shortest_paths.cc deleted file mode 100644 index 950f6cd3c9..0000000000 --- a/ortools/graph/samples/dag_simple_multiple_shortest_paths.cc +++ /dev/null @@ -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 -#include - -#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 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 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; -} diff --git a/ortools/graph/samples/dag_simple_shortest_path.cc b/ortools/graph/samples/dag_simple_shortest_path.cc deleted file mode 100644 index cb7fb5b119..0000000000 --- a/ortools/graph/samples/dag_simple_shortest_path.cc +++ /dev/null @@ -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 -#include - -#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 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; -} diff --git a/ortools/graph/shortest_paths_test.cc b/ortools/graph/shortest_paths_test.cc index 4490d9fd84..552f575292 100644 --- a/ortools/graph/shortest_paths_test.cc +++ b/ortools/graph/shortest_paths_test.cc @@ -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& 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); } diff --git a/tools/doc/gen_ref_doc.py b/tools/doc/gen_ref_doc.py index 7e82f8d0b0..a952b4060b 100755 --- a/tools/doc/gen_ref_doc.py +++ b/tools/doc/gen_ref_doc.py @@ -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 ' +