graph: remove all dag_*

This commit is contained in:
Corentin Le Molgat
2025-03-03 18:01:47 +01:00
parent d02e03bbc4
commit 40c2136bd4
27 changed files with 68 additions and 4740 deletions

View File

@@ -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",

View File

@@ -1,19 +1,15 @@
# Graph and Network Flows
This directory contains data structures and algorithms for graph and
network flow problems.
This directory contains data structures and algorithms for graph and network
flow problems.
It contains in particular:
* well-tuned algorithms (for example, shortest paths and
[Hamiltonian paths](https://en.wikipedia.org/wiki/Hamiltonian_path)).
* hard-to-find algorithms (Hamiltonian paths, push-relabel flow algorithms).
* other, more common algorithms, that are useful to use with `EbertGraph`.
Graph representations:
* [`ebert_graph.h`][ebert_graph_h]: entry point for a directed graph class.
Deprecated. Prefer using [`//ortools/graph/graph.h`][graph_h].
* other, more common algorithms, that are useful to use with graphs from
`util/graph`.
Generic algorithms for shortest paths:
@@ -32,16 +28,6 @@ Generic algorithms for shortest paths:
Specific algorithms for paths:
* [`dag_shortest_path.h`][dag_shortest_path_h]: shortest paths on directed
acyclic graphs. If you have such a graph, this implementation is likely to
be the fastest. Unlike most implementations, these algorithms have two
interfaces: a "simple" one (list of edges and weights) and a standard one
(taking as input a graph data structure from
[`//ortools/graph/graph.h`][graph_h]).
* [`dag_constrained_shortest_path.`][dag_constrained_shortest_path_h]:
shortest paths on directed acyclic graphs with resource constraints.
* [`hamiltonian_path.h`][hamiltonian_path_h]: entry point for computing
minimum [Hamiltonian paths](https://en.wikipedia.org/wiki/Hamiltonian_path)
and cycles on directed graphs with costs on arcs, using a
@@ -54,15 +40,15 @@ Specific algorithms for paths:
Graph decompositions:
* [`connected_components.h`][connected_components_h]: entry point for computing
connected components in an undirected graph. (It does not need `ebert_graph.h`
or `digraph.h`.)
connected components in an undirected graph. (It does not need an explicit
graph class.)
* [`strongly_connected_components.h`][strongly_connected_components_h]: entry
point for computing the strongly connected components of a directed graph.
* [`cliques.h`][cliques_h]: entry point for computing maximum cliques and
clique covers in a directed graph, based on the Bron-Kerbosch algorithm. (It
does not need `ebert_graph.h` or `digraph.h`.)
clique covers in a directed graph, based on the Bron-Kerbosch algorithm.(It
does not need an explicit graph class.)
Flow algorithms:
@@ -82,14 +68,14 @@ Flow algorithms:
## Wrappers
* [`python`](python): the SWIG code that makes the wrapper available in Python
and its unit tests.
* [`python`](python): the SWIG code that makes the wrapper available in Python
and its unit tests.
* [`java`](java): the SWIG code that makes the wrapper available in Java
and its unit tests.
* [`java`](java): the SWIG code that makes the wrapper available in Java and
its unit tests.
* [`csharp`](csharp): the SWIG code that makes the wrapper available in C#
and its unit tests.
* [`csharp`](csharp): the SWIG code that makes the wrapper available in C# and
its unit tests.
## Samples
@@ -97,7 +83,6 @@ You can find some canonical examples in [`samples`][samples].
<!-- Links used throughout the document. -->
[ebert_graph_h]: ../graph/ebert_graph.h
[graph_h]: ../graph/graph.h
[bounded_dijkstra_h]: ../graph/bounded_dijkstra.h
[bidirectional_dijkstra_h]: ../graph/bidirectional_dijkstra.h

View File

@@ -1,102 +0,0 @@
// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "ortools/graph/dag_constrained_shortest_path.h"
#include <vector>
#include "absl/log/check.h"
#include "absl/status/statusor.h"
#include "absl/types/span.h"
#include "ortools/graph/dag_shortest_path.h"
#include "ortools/graph/graph.h"
#include "ortools/graph/topologicalsorter.h"
namespace operations_research {
namespace {
void ApplyMapping(absl::Span<const int> mapping, std::vector<int>& values) {
if (!mapping.empty()) {
for (int i = 0; i < values.size(); ++i) {
values[i] = mapping[values[i]];
}
}
}
} // namespace
PathWithLength ConstrainedShortestPathsOnDag(
const int num_nodes,
absl::Span<const ArcWithLengthAndResources> arcs_with_length_and_resources,
int source, int destination, const std::vector<double>& max_resources) {
using GraphType = util::StaticGraph<>;
using NodeIndex = GraphType::NodeIndex;
using ArcIndex = GraphType::ArcIndex;
const int num_arcs = arcs_with_length_and_resources.size();
GraphType graph(num_nodes, num_arcs);
std::vector<double> arc_lengths;
arc_lengths.reserve(num_arcs);
std::vector<std::vector<double>> arc_resources(max_resources.size());
for (int i = 0; i < max_resources.size(); ++i) {
arc_resources[i].reserve(num_arcs);
}
for (const auto& arc : arcs_with_length_and_resources) {
graph.AddArc(arc.from, arc.to);
arc_lengths.push_back(arc.length);
for (int i = 0; i < arc.resources.size(); ++i) {
arc_resources[i].push_back(arc.resources[i]);
}
}
std::vector<ArcIndex> permutation;
graph.Build(&permutation);
util::Permute(permutation, &arc_lengths);
for (int i = 0; i < max_resources.size(); ++i) {
util::Permute(permutation, &arc_resources[i]);
}
std::vector<ArcIndex> inverse_permutation =
GetInversePermutation(permutation);
const absl::StatusOr<std::vector<NodeIndex>> topological_order =
util::graph::FastTopologicalSort(graph);
CHECK_OK(topological_order) << "arcs_with_length form a cycle.";
std::vector<NodeIndex> sources = {source};
std::vector<NodeIndex> destinations = {destination};
ConstrainedShortestPathsOnDagWrapper<util::StaticGraph<>>
constrained_shortest_path_on_dag(&graph, &arc_lengths, &arc_resources,
*topological_order, sources,
destinations, &max_resources);
PathWithLength path_with_length =
constrained_shortest_path_on_dag.RunConstrainedShortestPathOnDag();
ApplyMapping(inverse_permutation, path_with_length.arc_path);
return path_with_length;
}
std::vector<int> GetInversePermutation(absl::Span<const int> permutation) {
std::vector<int> inverse_permutation(permutation.size());
if (!permutation.empty()) {
for (int i = 0; i < permutation.size(); ++i) {
inverse_permutation[permutation[i]] = i;
}
}
return inverse_permutation;
}
} // namespace operations_research

View File

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

View File

@@ -1,821 +0,0 @@
// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "ortools/graph/dag_constrained_shortest_path.h"
#include <algorithm>
#include <cstdint>
#include <limits>
#include <utility>
#include <vector>
#include "absl/algorithm/container.h"
#include "absl/log/check.h"
#include "absl/random/random.h"
#include "absl/strings/str_cat.h"
#include "absl/types/span.h"
#include "benchmark/benchmark.h"
#include "gtest/gtest.h"
#include "ortools/base/dump_vars.h"
#include "ortools/base/gmock.h"
#include "ortools/graph/dag_shortest_path.h"
#include "ortools/graph/graph.h"
#include "ortools/graph/graph_io.h"
#include "ortools/math_opt/cpp/math_opt.h"
namespace operations_research {
namespace {
constexpr double kInf = std::numeric_limits<double>::infinity();
using ::testing::ElementsAre;
using ::testing::FieldsAre;
using ::testing::IsEmpty;
TEST(ConstrainedShortestPathOnDagTest, SimpleGraph) {
const int source = 0;
const int destination = 1;
const int a = 2;
const int b = 3;
const int num_nodes = 4;
const std::vector<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{source, a, 5.0, {6.0}},
{source, b, 2.0, {4.0}},
{a, destination, 3.0, {2.0}},
{b, destination, 20.0, {3.0}}};
EXPECT_THAT(ConstrainedShortestPathsOnDag(
num_nodes, arcs_with_length_and_resources, source,
destination, /*max_resources=*/{7.0}),
FieldsAre(/*length=*/22.0, /*arc_path=*/ElementsAre(1, 3),
/*node_path=*/ElementsAre(source, b, destination)));
}
TEST(ConstrainedShortestPathOnDagTest, SimpleGraphTwoPaths) {
const int source = 0;
const int destination = 1;
const int a = 2;
const int b = 3;
const int num_nodes = 4;
const std::vector<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{source, a, 5.0, {2.0}},
{source, b, 2.0, {1.0}},
{a, destination, 3.0, {1.0}},
{b, destination, 20.0, {1.0}}};
EXPECT_THAT(ConstrainedShortestPathsOnDag(
num_nodes, arcs_with_length_and_resources, source,
destination, /*max_resources=*/{6.0}),
FieldsAre(/*length=*/8.0, /*arc_path=*/ElementsAre(0, 2),
/*node_path=*/ElementsAre(source, a, destination)));
}
TEST(ConstrainedShortestPathOnDagTest, LargerGraphWithNegativeCost) {
const int source = 0;
const int a = 3;
const int b = 2;
const int c = 1;
const int destination = 4;
const int num_nodes = 5;
const std::vector<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{a, c, 5.0, {5.0}}, {source, b, 7.0, {4.0}},
{a, b, 1.0, {3.0}}, {source, a, 3.0, {4.0}},
{c, destination, 5.0, {1.0}}, {b, destination, -2.0, {1.0}}};
EXPECT_THAT(ConstrainedShortestPathsOnDag(
num_nodes, arcs_with_length_and_resources, source,
destination, /*max_resources=*/{6.0}),
FieldsAre(/*length=*/5.0, /*arc_path=*/ElementsAre(1, 5),
/*node_path=*/ElementsAre(source, b, destination)));
}
TEST(ConstrainedShortestPathOnDagTest, LargerGraphWithDominance) {
const int source = 0;
const int a = 3;
const int b = 2;
const int c = 1;
const int destination = 4;
const int num_nodes = 5;
const std::vector<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{a, c, 5.0, {1.0}}, {source, b, 1.0, {3.0}},
{a, b, 7.0, {4.0}}, {source, a, 3.0, {4.0}},
{c, destination, 5.0, {1.0}}, {b, destination, -2.0, {1.0}}};
EXPECT_THAT(ConstrainedShortestPathsOnDag(
num_nodes, arcs_with_length_and_resources, source,
destination, /*max_resources=*/{6.0}),
FieldsAre(/*length=*/-1.0, /*arc_path=*/ElementsAre(1, 5),
/*node_path=*/ElementsAre(source, b, destination)));
}
TEST(ConstrainedShortestPathOnDagTest, LargerGraphNoMaximumDuration) {
const int source = 0;
const int a = 3;
const int b = 2;
const int c = 1;
const int destination = 4;
const int num_nodes = 5;
const std::vector<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{a, c, 5.0, {1.0}}, {source, b, 7.0, {4.0}},
{a, b, 1.0, {3.0}}, {source, a, 3.0, {4.0}},
{c, destination, 5.0, {1.0}}, {b, destination, -2.0, {1.0}}};
EXPECT_THAT(
ConstrainedShortestPathsOnDag(
num_nodes, arcs_with_length_and_resources, source, destination,
/*max_resources=*/{std::numeric_limits<double>::max()}),
FieldsAre(/*length=*/2.0, /*arc_path=*/ElementsAre(3, 2, 5),
/*node_path=*/ElementsAre(source, a, b, destination)));
}
TEST(ConstrainedShortestPathOnDagTest, GraphWithInefficientEdge) {
const int source = 0;
const int a = 1;
const int destination = 2;
const int num_nodes = 3;
const std::vector<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{source, a, 3.0, {4.0}},
{source, destination, 9.0, {6.0}},
{a, destination, 5.0, {1.0}}};
EXPECT_THAT(
ConstrainedShortestPathsOnDag(num_nodes, arcs_with_length_and_resources,
source, destination,
/*max_resources=*/{6.0}),
FieldsAre(/*length=*/8.0, /*arc_path=*/ElementsAre(0, 2),
/*node_path=*/ElementsAre(source, a, destination)));
}
TEST(ConstrainedShortestPathOnDagTest, NoResources) {
const int source = 0;
const int destination = 1;
const int a = 2;
const int num_nodes = 3;
const std::vector<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{source, a, 5.0, {}}, {a, destination, 3.0, {}}};
EXPECT_DEATH(
ConstrainedShortestPathsOnDag(num_nodes, arcs_with_length_and_resources,
source, destination,
/*max_resources=*/{}),
"ortools/graph/dag_shortest_path.h");
}
TEST(ConstrainedShortestPathOnDagTest, Cycle) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
const std::vector<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{source, destination, 1.0, {0.0}}, {destination, source, 2.0, {1.0}}};
EXPECT_DEATH(
ConstrainedShortestPathsOnDag(num_nodes, arcs_with_length_and_resources,
source, destination,
/*max_resources=*/{0.0}),
"cycle");
}
TEST(ConstrainedShortestPathOnDagTest, SetsNotConnected) {
const int source = 0;
const int destination = 1;
const int a = 2;
const int num_nodes = 3;
const std::vector<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{source, a, 1.0, {0.0}}};
EXPECT_THAT(ConstrainedShortestPathsOnDag(
num_nodes, arcs_with_length_and_resources, source,
destination, /*max_resources=*/{0.0}),
FieldsAre(/*length=*/kInf,
/*arc_path=*/IsEmpty(),
/*node_path=*/IsEmpty()));
}
TEST(ConstrainedShortestPathOnDagTest, SetsNotConnectedDueToInfiniteCost) {
const int source = 2;
const int destination = 0;
const int a = 1;
const int num_nodes = 3;
const std::vector<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{a, destination, 1.0, {0.0}}, {source, a, kInf, {0.0}}};
EXPECT_THAT(ConstrainedShortestPathsOnDag(
num_nodes, arcs_with_length_and_resources, source,
destination, /*max_resources=*/{0.0}),
FieldsAre(/*length=*/kInf,
/*arc_path=*/IsEmpty(),
/*node_path=*/IsEmpty()));
}
TEST(ConstrainedShortestPathOnDagTest, SetsNotConnectedDueToLackOfResources) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
const std::vector<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{source, destination, 1.0, {1.0, 8.0}},
{source, destination, 2.0, {7.0, 2.0}},
{source, destination, 3.0, {6.0, 3.0}}};
EXPECT_THAT(ConstrainedShortestPathsOnDag(
num_nodes, arcs_with_length_and_resources, source,
destination, /*max_resources=*/{4.0, 4.0}),
FieldsAre(/*length=*/kInf,
/*arc_path=*/IsEmpty(),
/*node_path=*/IsEmpty()));
}
TEST(ConstrainedShortestPathOnDagTest, MultipleArcs) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
const std::vector<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{source, destination, 4.0, {2.0}}, {source, destination, 2.0, {4.0}}};
EXPECT_THAT(ConstrainedShortestPathsOnDag(
num_nodes, arcs_with_length_and_resources, source,
destination, /*max_resources=*/{3.0}),
FieldsAre(/*length=*/4.0, /*arc_path=*/ElementsAre(0),
/*node_path=*/ElementsAre(source, destination)));
}
TEST(ConstrainedShortestPathOnDagTest, UpdateArcsLengthAndResources) {
const int source = 0;
const int destination = 1;
const int a = 2;
const int b = 3;
const int num_nodes = 4;
std::vector<ArcWithLengthAndResources> arcs_with_length_and_resources = {
{source, a, 5.0, {1.0, 3.0}},
{source, b, 2.0, {4.0, 10.0}},
{a, destination, 3.0, {5.0, 9.0}},
{b, destination, 20.0, {2.0, 2.0}}};
const std::vector<double> max_resources = {6.0, 12.0};
EXPECT_THAT(
ConstrainedShortestPathsOnDag(num_nodes, arcs_with_length_and_resources,
source, destination, max_resources),
FieldsAre(/*length=*/8.0, /*arc_path=*/ElementsAre(0, 2),
/*node_path=*/ElementsAre(source, a, destination)));
// Update the length of arc b -> destination from 20.0 to -1.0.
arcs_with_length_and_resources[3].length = -1.0;
EXPECT_THAT(
ConstrainedShortestPathsOnDag(num_nodes, arcs_with_length_and_resources,
source, destination, max_resources),
FieldsAre(/*length=*/1.0, /*arc_path=*/ElementsAre(1, 3),
/*node_path=*/ElementsAre(source, b, destination)));
// Update the first resource of arc source -> b from 4.0 to 5.0 making
// the path source -> b -> destination infeasible.
arcs_with_length_and_resources[1].resources[0] = 5.0;
EXPECT_THAT(
ConstrainedShortestPathsOnDag(num_nodes, arcs_with_length_and_resources,
source, destination, max_resources),
FieldsAre(/*length=*/8.0, /*arc_path=*/ElementsAre(0, 2),
/*node_path=*/ElementsAre(source, a, destination)));
}
TEST(ConstrainedShortestPathsOnDagWrapperTest,
ShortestPathGoesThroughMultipleSources) {
const int source_1 = 0;
const int source_2 = 1;
const int destination = 2;
const int num_nodes = 3;
util::ListGraph<> graph(num_nodes, /*arc_capacity=*/2);
std::vector<double> arc_lengths;
std::vector<std::vector<double>> arc_resources(1);
graph.AddArc(source_1, source_2);
arc_lengths.push_back(-7.0);
arc_resources[0].push_back(2.0);
graph.AddArc(source_2, destination);
arc_lengths.push_back(3.0);
arc_resources[0].push_back(3.0);
const std::vector<int> topological_order = {source_1, source_2, destination};
const std::vector<int> sources = {source_1, source_2};
const std::vector<int> destinations = {destination};
const std::vector<double> max_resources = {6.0};
ConstrainedShortestPathsOnDagWrapper<util::ListGraph<>>
constrained_shortest_path_on_dag(&graph, &arc_lengths, &arc_resources,
topological_order, sources, destinations,
&max_resources);
EXPECT_THAT(
constrained_shortest_path_on_dag.RunConstrainedShortestPathOnDag(),
FieldsAre(/*length=*/-4.0, /*arc_path=*/ElementsAre(0, 1),
/*node_path=*/ElementsAre(source_1, source_2, destination)));
}
TEST(ConstrainedShortestPathsOnDagWrapperTest, MultipleDestinations) {
const int source = 0;
const int destination_1 = 1;
const int destination_2 = 2;
const int destination_3 = 3;
const int num_nodes = 4;
util::ListGraph<> graph(num_nodes, /*arc_capacity=*/3);
std::vector<double> arc_lengths;
std::vector<std::vector<double>> arc_resources(1);
graph.AddArc(source, destination_1);
arc_lengths.push_back(3.0);
arc_resources[0].push_back(5.0);
graph.AddArc(source, destination_2);
arc_lengths.push_back(1.0);
arc_resources[0].push_back(7.0);
graph.AddArc(source, destination_3);
arc_lengths.push_back(2.0);
arc_resources[0].push_back(6.0);
const std::vector<int> sources = {source};
const std::vector<int> destinations = {destination_1, destination_2,
destination_3};
const std::vector<int> topological_order = {source, destination_3,
destination_1, destination_2};
const std::vector<double> max_resources = {6.0};
ConstrainedShortestPathsOnDagWrapper<util::ListGraph<>>
constrained_shortest_path_on_dag(&graph, &arc_lengths, &arc_resources,
topological_order, sources, destinations,
&max_resources);
EXPECT_THAT(
constrained_shortest_path_on_dag.RunConstrainedShortestPathOnDag(),
FieldsAre(/*length=*/2.0, /*arc_path=*/ElementsAre(2),
/*node_path=*/ElementsAre(source, destination_3)));
}
TEST(ConstrainedShortestPathsOnDagWrapperTest, UpdateArcsLength) {
const int source = 0;
const int destination = 1;
const int a = 2;
const int b = 3;
const int num_nodes = 4;
util::ListGraph<> graph(num_nodes, /*arc_capacity=*/4);
std::vector<double> arc_lengths;
std::vector<std::vector<double>> arc_resources(2);
graph.AddArc(source, a);
arc_lengths.push_back(5.0);
arc_resources[0].push_back(1.0);
arc_resources[1].push_back(3.0);
graph.AddArc(source, b);
arc_lengths.push_back(2.0);
arc_resources[0].push_back(4.0);
arc_resources[1].push_back(10.0);
graph.AddArc(a, destination);
arc_lengths.push_back(3.0);
arc_resources[0].push_back(5.0);
arc_resources[1].push_back(9.0);
graph.AddArc(b, destination);
arc_lengths.push_back(20.0);
arc_resources[0].push_back(2.0);
arc_resources[1].push_back(2.0);
const std::vector<int> topological_order = {source, a, b, destination};
const std::vector<int> sources = {source};
const std::vector<int> destinations = {destination};
const std::vector<double> max_resources = {6.0, 12.0};
ConstrainedShortestPathsOnDagWrapper<util::ListGraph<>>
constrained_shortest_path_on_dag(&graph, &arc_lengths, &arc_resources,
topological_order, sources, destinations,
&max_resources);
EXPECT_THAT(
constrained_shortest_path_on_dag.RunConstrainedShortestPathOnDag(),
FieldsAre(/*length=*/8.0, /*arc_path=*/ElementsAre(0, 2),
/*node_path=*/ElementsAre(source, a, destination)));
// Update the length of arc b -> destination from 20.0 to -1.0.
arc_lengths[3] = -1.0;
EXPECT_THAT(
constrained_shortest_path_on_dag.RunConstrainedShortestPathOnDag(),
FieldsAre(/*length=*/1.0, /*arc_path=*/ElementsAre(1, 3),
/*node_path=*/ElementsAre(source, b, destination)));
}
TEST(ConstrainedShortestPathsOnDagWrapperTest, LimitMaximumNumberOfLabels) {
const int source = 0;
const int destination = 1;
const int a = 2;
const int num_nodes = 3;
util::ListGraph<> graph(num_nodes, /*arc_capacity=*/2);
std::vector<double> arc_lengths;
std::vector<std::vector<double>> arc_resources(1);
graph.AddArc(source, a);
arc_lengths.push_back(5.0);
arc_resources[0].push_back(2.0);
graph.AddArc(a, destination);
arc_lengths.push_back(-2.0);
arc_resources[0].push_back(1.0);
const std::vector<int> topological_order = {source, a, destination};
const std::vector<int> sources = {source};
const std::vector<int> destinations = {destination};
const std::vector<double> max_resources = {6.0};
ConstrainedShortestPathsOnDagWrapper<util::ListGraph<>>
constrained_shortest_path_on_dag_with_one_label(
&graph, &arc_lengths, &arc_resources, topological_order, sources,
destinations, &max_resources, /*max_num_created_labels=*/1);
ConstrainedShortestPathsOnDagWrapper<util::ListGraph<>>
constrained_shortest_path_on_dag_with_four_labels(
&graph, &arc_lengths, &arc_resources, topological_order, sources,
destinations, &max_resources, /*max_num_created_labels=*/4);
EXPECT_THAT(constrained_shortest_path_on_dag_with_one_label
.RunConstrainedShortestPathOnDag(),
FieldsAre(/*length=*/kInf,
/*arc_path=*/IsEmpty(),
/*node_path=*/IsEmpty()));
EXPECT_THAT(constrained_shortest_path_on_dag_with_four_labels
.RunConstrainedShortestPathOnDag(),
FieldsAre(/*length=*/3.0, /*arc_path=*/ElementsAre(0, 1),
/*node_path=*/ElementsAre(source, a, destination)));
}
// Builds a random DAG with a given number of nodes and arcs where 0 is always
// the first and num_nodes-1 the last element in the topological order. Note
// that the graph always include at least one arc from 0 to num_nodes-1.
std::pair<util::StaticGraph<>, std::vector<int>> BuildRandomDag(
const int64_t num_nodes, const int64_t num_arcs) {
absl::BitGen bit_gen;
CHECK_GE(num_nodes, 2);
CHECK_GE(num_arcs, 1);
CHECK_LE(num_arcs, (num_nodes * (num_nodes - 1)) / 2);
std::vector<int> topological_order(num_nodes);
topological_order.back() = num_nodes - 1;
absl::Span<int> non_start_end =
absl::MakeSpan(topological_order).subspan(1, num_nodes - 2);
absl::c_iota(non_start_end, 1);
absl::c_shuffle(non_start_end, bit_gen);
int edges_added = 0;
util::StaticGraph<> graph(num_nodes, num_arcs);
graph.AddArc(0, num_nodes - 1);
while (edges_added < num_arcs - 1) {
int start_index = absl::Uniform(bit_gen, 0, num_nodes - 1);
int end_index = absl::Uniform(bit_gen, start_index + 1, num_nodes);
graph.AddArc(topological_order[start_index], topological_order[end_index]);
edges_added++;
}
graph.Build();
return {graph, topological_order};
}
// The length of each arc is drawn uniformly at random within a given interval
// except if the first arc from 0 to num_nodes-1 where it is set to
// `start_to_end_value`.
std::vector<double> GenerateRandomIntegerValues(
const util::StaticGraph<>& graph, const double min_value = 0.0,
const double max_value = 10.0, const double start_to_end_value = 10000.0) {
absl::BitGen bit_gen;
std::vector<double> arc_values;
arc_values.reserve(graph.num_arcs());
bool start_to_end_value_set = false;
for (util::StaticGraph<>::ArcIndex arc = 0; arc < graph.num_arcs(); ++arc) {
if (!start_to_end_value_set && graph.Tail(arc) == 0 &&
graph.Head(arc) == graph.num_nodes() - 1) {
arc_values.push_back(start_to_end_value);
start_to_end_value_set = true;
continue;
}
arc_values.push_back(
static_cast<double>(absl::Uniform<int>(bit_gen, min_value, max_value)));
}
return arc_values;
}
double SolveConstrainedShortestPathUsingIntegerProgramming(
const util::StaticGraph<>& graph, absl::Span<const double> arc_lengths,
absl::Span<const std::vector<double>> arc_resources,
absl::Span<const double> max_resources,
absl::Span<const util::StaticGraph<>::NodeIndex> sources,
absl::Span<const util::StaticGraph<>::NodeIndex> destinations) {
using NodeIndex = util::StaticGraph<>::NodeIndex;
using ArcIndex = util::StaticGraph<>::ArcIndex;
math_opt::Model model;
std::vector<math_opt::Variable> arc_variables;
std::vector<math_opt::LinearExpression> flow_conservation(graph.num_nodes(),
0.0);
for (ArcIndex arc_index = 0; arc_index < graph.num_arcs(); ++arc_index) {
arc_variables.push_back(model.AddBinaryVariable(absl::StrCat(
arc_index, "_", graph.Tail(arc_index), "->", graph.Head(arc_index))));
model.set_objective_coefficient(arc_variables[arc_index],
arc_lengths[arc_index]);
flow_conservation[graph.Head(arc_index)] -= arc_variables[arc_index];
flow_conservation[graph.Tail(arc_index)] += arc_variables[arc_index];
}
math_opt::LinearExpression all_sources;
math_opt::LinearExpression all_destinations;
for (NodeIndex node_index = 0; node_index < graph.num_nodes(); ++node_index) {
math_opt::LinearExpression net_flow = 0;
if (absl::c_linear_search(sources, node_index)) {
const math_opt::Variable s = model.AddBinaryVariable();
all_sources += s;
net_flow += s;
}
if (absl::c_linear_search(destinations, node_index)) {
const math_opt::Variable t = model.AddBinaryVariable();
all_destinations += t;
net_flow -= t;
}
model.AddLinearConstraint(flow_conservation[node_index] == net_flow);
}
model.AddLinearConstraint(all_sources == 1);
model.AddLinearConstraint(all_destinations == 1);
for (int r = 0; r < max_resources.size(); ++r) {
math_opt::LinearExpression variable_resources;
for (ArcIndex arc_index = 0; arc_index < graph.num_arcs(); ++arc_index) {
variable_resources +=
arc_resources[r][arc_index] * arc_variables[arc_index];
}
model.AddLinearConstraint(variable_resources <= max_resources[r]);
}
const absl::StatusOr<math_opt::SolveResult> result =
math_opt::Solve(model, math_opt::SolverType::kCpSat, {});
CHECK_OK(result.status())
<< util::GraphToString(graph, util::PRINT_GRAPH_ARCS);
CHECK_OK(result->termination.EnsureIsOptimal())
<< util::GraphToString(graph, util::PRINT_GRAPH_ARCS);
return result->objective_value();
}
TEST(ConstrainedShortestPathsOnDagWrapperTest,
RandomizedStressTestSingleResource) {
absl::BitGen bit_gen;
const int kNumTests = 50;
for (int test = 0; test < kNumTests; ++test) {
const int num_nodes = absl::Uniform(bit_gen, 2, 12);
const int num_arcs = absl::Uniform(
bit_gen, 1, std::min(num_nodes * (num_nodes - 1) / 2, 15));
// Generate a random DAG with random resources
const auto [graph, topological_order] = BuildRandomDag(num_nodes, num_arcs);
std::vector<double> arc_lengths(num_arcs);
std::vector<std::vector<double>> arc_resources(1);
arc_resources[0] = GenerateRandomIntegerValues(graph, /*min_value=*/1.0,
/*max_value=*/10.0,
/*start_to_end_value=*/1.0);
const std::vector<int> sources = {0};
const std::vector<int> destinations = {num_nodes - 1};
const std::vector<double> max_resources = {15.0};
ConstrainedShortestPathsOnDagWrapper<util::StaticGraph<>>
constrained_shortest_path_on_dag(&graph, &arc_lengths, &arc_resources,
topological_order, sources,
destinations, &max_resources);
const int kNumSamples = 5;
for (int _ = 0; _ < kNumSamples; ++_) {
arc_lengths = GenerateRandomIntegerValues(graph);
const PathWithLength path_with_length =
constrained_shortest_path_on_dag.RunConstrainedShortestPathOnDag();
EXPECT_NEAR(path_with_length.length,
SolveConstrainedShortestPathUsingIntegerProgramming(
graph, arc_lengths, arc_resources, max_resources, sources,
destinations),
1e-5);
ASSERT_FALSE(HasFailure())
<< DUMP_VARS(num_nodes, num_arcs, arc_lengths) << "\n With graph :\n "
<< util::GraphToString(graph, util::PRINT_GRAPH_ARCS);
}
}
}
// -----------------------------------------------------------------------------
// Benchmark.
// -----------------------------------------------------------------------------
void BM_RandomDag(benchmark::State& state) {
absl::BitGen bit_gen;
// Generate a fixed random DAG.
const int num_nodes = state.range(0);
const int num_arcs = num_nodes * state.range(1);
const auto [graph, topological_order] = BuildRandomDag(num_nodes, num_arcs);
// Generate 20 scenarios of random arc lengths.
const int num_scenarios = 20;
std::vector<std::vector<double>> arc_lengths_scenarios;
for (int _ = 0; _ < num_scenarios; ++_) {
arc_lengths_scenarios.push_back(GenerateRandomIntegerValues(graph));
}
std::vector<double> arc_lengths(num_arcs);
std::vector<std::vector<double>> arc_resources(1);
arc_resources[0] =
GenerateRandomIntegerValues(graph, /*min_value=*/1.0,
/*max_value=*/10.0,
/*start_to_end_value=*/num_nodes * 0.2);
const std::vector<int> sources = {0};
const std::vector<int> destinations = {num_nodes - 1};
const std::vector<double> max_resources = {num_nodes * 0.2};
ConstrainedShortestPathsOnDagWrapper<util::StaticGraph<>>
constrained_shortest_path_on_dag(&graph, &arc_lengths, &arc_resources,
topological_order, sources, destinations,
&max_resources);
int total_label_count = 0;
for (auto _ : state) {
// Pick a arc lengths scenario at random.
arc_lengths =
arc_lengths_scenarios[absl::Uniform(bit_gen, 0, num_scenarios)];
const PathWithLength path_with_length =
constrained_shortest_path_on_dag.RunConstrainedShortestPathOnDag();
total_label_count += constrained_shortest_path_on_dag.label_count();
CHECK_GE(path_with_length.length, 0.0);
CHECK_LE(path_with_length.length, 10000.0);
}
state.SetItemsProcessed(std::max(1, total_label_count));
}
BENCHMARK(BM_RandomDag)
->ArgPair(1 << 10, 16)
->ArgPair(1 << 16, 4)
->ArgPair(1 << 16, 16)
->ArgPair(1 << 19, 4)
->ArgPair(1 << 19, 16)
->ArgPair(1 << 22, 4);
// Generate a 2-dimensional grid DAG.
// Eg. for width=3, height=2, it generates this:
// 0 ----> 1 ----> 2
// | | |
// | | |
// v v v
// 3 ----> 4 ----> 5
void BM_GridDAG(benchmark::State& state) {
const int64_t width = state.range(0);
const int64_t height = state.range(1);
const int num_resources = state.range(2);
const int num_nodes = width * height;
const int num_arcs = 2 * num_nodes - width - height;
util::StaticGraph<> graph(num_nodes, num_arcs);
// Add horizontal edges.
for (int i = 0; i < height; ++i) {
for (int j = 1; j < width; ++j) {
const int left = i * width + (j - 1);
const int right = i * width + j;
graph.AddArc(left, right);
}
}
// Add vertical edges.
for (int i = 1; i < height; ++i) {
for (int j = 0; j < width; ++j) {
const int up = (i - 1) * width + j;
const int down = i * width + j;
graph.AddArc(up, down);
}
}
graph.Build();
std::vector<int> topological_order(num_nodes);
absl::c_iota(topological_order, 0);
// Generate 20 scenarios of random arc lengths.
absl::BitGen bit_gen;
const int kNumScenarios = 20;
std::vector<std::vector<double>> arc_lengths_scenarios;
for (int unused = 0; unused < kNumScenarios; ++unused) {
std::vector<double> arc_lengths(graph.num_arcs());
for (int i = 0; i < graph.num_arcs(); ++i) {
arc_lengths[i] = absl::Uniform<double>(bit_gen, 0, 1);
}
arc_lengths_scenarios.push_back(arc_lengths);
}
std::vector<std::vector<double>> arc_resources(num_resources);
for (int r = 0; r < num_resources; ++r) {
arc_resources[r].resize(graph.num_arcs());
for (int i = 0; i < graph.num_arcs(); ++i) {
arc_resources[r][i] = absl::Uniform<double>(bit_gen, 0, 1);
}
}
std::vector<double> arc_lengths(num_arcs);
const std::vector<int> sources = {0};
const std::vector<int> destinations = {num_nodes - 1};
std::vector<double> max_resources(num_resources);
// Each path from source to destination has `(width + height - 2)` arcs. Each
// arc has mean resource(s) 0.5. We want to consider paths with half (0.5) the
// mean resource(s).
const double max_resource = (width + height - 2) * 0.5 * 0.5;
for (int r = 0; r < num_resources; ++r) {
max_resources[r] = max_resource;
}
ConstrainedShortestPathsOnDagWrapper<util::StaticGraph<>>
constrained_shortest_path_on_dag(&graph, &arc_lengths, &arc_resources,
topological_order, sources, destinations,
&max_resources);
int total_label_count = 0;
for (auto _ : state) {
// Pick a arc lengths scenario at random.
arc_lengths =
arc_lengths_scenarios[absl::Uniform<int>(bit_gen, 0, kNumScenarios)];
const PathWithLength path_with_length =
constrained_shortest_path_on_dag.RunConstrainedShortestPathOnDag();
total_label_count += constrained_shortest_path_on_dag.label_count();
CHECK_GE(path_with_length.length, 0.0);
}
state.SetItemsProcessed(std::max(1, total_label_count));
}
BENCHMARK(BM_GridDAG)
->Args({100, 100, 1})
->Args({100, 100, 2})
->Args({1000, 100, 1})
->Args({1000, 100, 2});
// -----------------------------------------------------------------------------
// Debug tests.
// -----------------------------------------------------------------------------
#ifndef NDEBUG
TEST(ConstrainedShortestPathOnDagTest, MinusInfWeight) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
const std::vector<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{source, destination, -kInf, {0.0}}};
EXPECT_DEATH(ConstrainedShortestPathsOnDag(
num_nodes, arcs_with_length_and_resources, source,
destination, /*max_resources=*/{0.0}),
"-inf");
}
TEST(ConstrainedShortestPathOnDagTest, NaNWeight) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
const std::vector<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{source, destination, std::numeric_limits<double>::quiet_NaN(), {0.0}}};
EXPECT_DEATH(ConstrainedShortestPathsOnDag(
num_nodes, arcs_with_length_and_resources, source,
destination, /*max_resources=*/{0.0}),
"NaN");
}
TEST(ConstrainedShortestPathOnDagTest, InfResource) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
const std::vector<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{source, destination, 0.0, {kInf}}};
EXPECT_DEATH(ConstrainedShortestPathsOnDag(
num_nodes, arcs_with_length_and_resources, source,
destination, /*max_resources=*/{0.0}),
"inf");
}
TEST(ConstrainedShortestPathOnDagTest, NegativeMaxResource) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
const std::vector<ArcWithLengthAndResources> arcs_with_length_and_resources =
{{source, destination, 0.0, {0.0}}};
EXPECT_DEATH(ConstrainedShortestPathsOnDag(
num_nodes, arcs_with_length_and_resources, source,
destination, /*max_resources=*/{-1.0}),
"negative");
}
TEST(ConstrainedShortestPathOnDagTest, SourceIsDestination) {
const int source = 0;
const int num_nodes = 1;
EXPECT_DEATH(
ConstrainedShortestPathsOnDag(
num_nodes, /*arcs_with_length_and_resources=*/{}, source, source,
/*max_resources=*/{0.0}),
"source and destination");
}
TEST(ConstrainedShortestPathsOnDagWrapperTest, ValidateTopologicalOrder) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
util::ListGraph<> graph(num_nodes, /*arc_capacity=*/1);
std::vector<double> arc_lengths;
std::vector<std::vector<double>> arc_resources(1);
graph.AddArc(source, destination);
arc_lengths.push_back(1.0);
arc_resources[0].push_back({1.0});
const std::vector<int> topological_order = {source};
const std::vector<int> sources = {source};
const std::vector<int> destinations = {destination};
const std::vector<double> max_resources = {0.0};
EXPECT_DEATH(ConstrainedShortestPathsOnDagWrapper<util::ListGraph<>>(
&graph, &arc_lengths, &arc_resources, topological_order,
sources, destinations, &max_resources),
"Invalid topological order");
}
#endif // NDEBUG
} // namespace
} // namespace operations_research

View File

@@ -1,136 +0,0 @@
// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "ortools/graph/dag_shortest_path.h"
#include <limits>
#include <utility>
#include <vector>
#include "absl/log/check.h"
#include "absl/types/span.h"
#include "ortools/graph/graph.h"
#include "ortools/graph/topologicalsorter.h"
namespace operations_research {
namespace {
using GraphType = util::StaticGraph<>;
using NodeIndex = GraphType::NodeIndex;
using ArcIndex = GraphType::ArcIndex;
struct ShortestPathOnDagProblem {
GraphType graph;
std::vector<double> arc_lengths;
std::vector<ArcIndex> original_arc_indices;
std::vector<NodeIndex> topological_order;
};
ShortestPathOnDagProblem ReadProblem(
const int num_nodes, absl::Span<const ArcWithLength> arcs_with_length) {
GraphType graph(num_nodes, arcs_with_length.size());
std::vector<double> arc_lengths;
arc_lengths.reserve(arcs_with_length.size());
for (const auto& arc : arcs_with_length) {
graph.AddArc(arc.from, arc.to);
arc_lengths.push_back(arc.length);
}
std::vector<ArcIndex> permutation;
graph.Build(&permutation);
util::Permute(permutation, &arc_lengths);
std::vector<ArcIndex> original_arc_indices(permutation.size());
if (!permutation.empty()) {
for (ArcIndex i = 0; i < permutation.size(); ++i) {
original_arc_indices[permutation[i]] = i;
}
}
absl::StatusOr<std::vector<NodeIndex>> topological_order =
util::graph::FastTopologicalSort(graph);
CHECK_OK(topological_order) << "arcs_with_length form a cycle.";
return ShortestPathOnDagProblem{
.graph = std::move(graph),
.arc_lengths = std::move(arc_lengths),
.original_arc_indices = std::move(original_arc_indices),
.topological_order = std::move(topological_order).value()};
}
void GetOriginalArcPath(absl::Span<const ArcIndex> original_arc_indices,
std::vector<ArcIndex>& arc_path) {
if (original_arc_indices.empty()) {
return;
}
for (int i = 0; i < arc_path.size(); ++i) {
arc_path[i] = original_arc_indices[arc_path[i]];
}
}
} // namespace
PathWithLength ShortestPathsOnDag(
const int num_nodes, absl::Span<const ArcWithLength> arcs_with_length,
const int source, const int destination) {
const ShortestPathOnDagProblem problem =
ReadProblem(num_nodes, arcs_with_length);
ShortestPathsOnDagWrapper<util::StaticGraph<>> shortest_path_on_dag(
&problem.graph, &problem.arc_lengths, problem.topological_order);
shortest_path_on_dag.RunShortestPathOnDag({source});
if (!shortest_path_on_dag.IsReachable(destination)) {
return PathWithLength{.length = std::numeric_limits<double>::infinity()};
}
std::vector<int> arc_path = shortest_path_on_dag.ArcPathTo(destination);
GetOriginalArcPath(problem.original_arc_indices, arc_path);
return PathWithLength{
.length = shortest_path_on_dag.LengthTo(destination),
.arc_path = std::move(arc_path),
.node_path = shortest_path_on_dag.NodePathTo(destination)};
}
std::vector<PathWithLength> KShortestPathsOnDag(
const int num_nodes, absl::Span<const ArcWithLength> arcs_with_length,
const int source, const int destination, const int path_count) {
const ShortestPathOnDagProblem problem =
ReadProblem(num_nodes, arcs_with_length);
KShortestPathsOnDagWrapper<GraphType> shortest_paths_on_dag(
&problem.graph, &problem.arc_lengths, problem.topological_order,
path_count);
shortest_paths_on_dag.RunKShortestPathOnDag({source});
if (!shortest_paths_on_dag.IsReachable(destination)) {
return {PathWithLength{.length = std::numeric_limits<double>::infinity()}};
}
std::vector<double> lengths = shortest_paths_on_dag.LengthsTo(destination);
std::vector<std::vector<GraphType::ArcIndex>> arc_paths =
shortest_paths_on_dag.ArcPathsTo(destination);
std::vector<std::vector<GraphType::NodeIndex>> node_paths =
shortest_paths_on_dag.NodePathsTo(destination);
std::vector<PathWithLength> paths;
paths.reserve(lengths.size());
for (int k = 0; k < lengths.size(); ++k) {
GetOriginalArcPath(problem.original_arc_indices, arc_paths[k]);
paths.push_back(PathWithLength{.length = lengths[k],
.arc_path = std::move(arc_paths[k]),
.node_path = std::move(node_paths[k])});
}
return paths;
}
} // namespace operations_research

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -18,6 +18,9 @@
// time. Its design is based on the experience acquired by the Operations
// Research team in their various graph algorithm implementations.
//
// Also see README.md#basegraph for a more graphical documentation of the
// concepts presented here.
//
// The main ideas are:
// - Graph nodes and arcs are represented by integers.
// - Node or arc annotations (weight, cost, ...) are not part of the graph
@@ -363,7 +366,8 @@ class ListGraph : public BaseGraph<NodeIndexType, ArcIndexType, false> {
BeginEndWrapper<OutgoingArcIterator> OutgoingArcs(NodeIndexType node) const;
// Advanced usage. Same as OutgoingArcs(), but allows to restart the iteration
// from an already known outgoing arc of the given node.
// from an already known outgoing arc of the given node. If `from` is
// `kNilArc`, an empty range is returned.
BeginEndWrapper<OutgoingArcIterator> OutgoingArcsStartingFrom(
NodeIndexType node, ArcIndexType from) const;
@@ -431,9 +435,16 @@ class StaticGraph : public BaseGraph<NodeIndexType, ArcIndexType, false> {
NodeIndexType Head(ArcIndexType arc) const;
NodeIndexType Tail(ArcIndexType arc) const;
ArcIndexType OutDegree(NodeIndexType node) const; // Work in O(1).
BeginEndWrapper<OutgoingArcIterator> OutgoingArcs(NodeIndexType node) const;
BeginEndWrapper<OutgoingArcIterator> OutgoingArcsStartingFrom(
NodeIndexType node, ArcIndexType from) const;
IntegerRange<ArcIndexType> OutgoingArcs(NodeIndexType node) const {
return IntegerRange<ArcIndexType>(start_[node], DirectArcLimit(node));
}
IntegerRange<ArcIndexType> OutgoingArcsStartingFrom(NodeIndexType node,
ArcIndexType from) const {
DCHECK_GE(from, start_[node]);
const ArcIndexType limit = DirectArcLimit(node);
return IntegerRange<ArcIndexType>(from == Base::kNilArc ? limit : from,
limit);
}
// This loops over the heads of the OutgoingArcs(node). It is just a more
// convenient way to achieve this. Moreover this interface is used by some
@@ -598,14 +609,21 @@ class ReverseArcStaticGraph
ArcIndexType OutDegree(NodeIndexType node) const;
ArcIndexType InDegree(NodeIndexType node) const;
BeginEndWrapper<OutgoingArcIterator> OutgoingArcs(NodeIndexType node) const;
IntegerRange<ArcIndexType> OutgoingArcs(NodeIndexType node) const {
return IntegerRange<ArcIndexType>(start_[node], DirectArcLimit(node));
}
IntegerRange<ArcIndexType> OutgoingArcsStartingFrom(NodeIndexType node,
ArcIndexType from) const {
DCHECK_GE(from, start_[node]);
const ArcIndexType limit = DirectArcLimit(node);
return IntegerRange<ArcIndexType>(from == Base::kNilArc ? limit : from,
limit);
}
BeginEndWrapper<IncomingArcIterator> IncomingArcs(NodeIndexType node) const;
BeginEndWrapper<OutgoingOrOppositeIncomingArcIterator>
OutgoingOrOppositeIncomingArcs(NodeIndexType node) const;
BeginEndWrapper<OppositeIncomingArcIterator> OppositeIncomingArcs(
NodeIndexType node) const;
BeginEndWrapper<OutgoingArcIterator> OutgoingArcsStartingFrom(
NodeIndexType node, ArcIndexType from) const;
BeginEndWrapper<IncomingArcIterator> IncomingArcsStartingFrom(
NodeIndexType node, ArcIndexType from) const;
BeginEndWrapper<OutgoingOrOppositeIncomingArcIterator>
@@ -1228,8 +1246,6 @@ StaticGraph<NodeIndexType, ArcIndexType>::FromArcs(NodeIndexType num_nodes,
return g;
}
DEFINE_RANGE_BASED_ARC_ITERATION(StaticGraph, Outgoing);
template <typename NodeIndexType, typename ArcIndexType>
absl::Span<const NodeIndexType>
StaticGraph<NodeIndexType, ArcIndexType>::operator[](NodeIndexType node) const {
@@ -1377,6 +1393,7 @@ void StaticGraph<NodeIndexType, ArcIndexType>::Build(
}
}
// TODO(b/385094969): Remove this class.
template <typename NodeIndexType, typename ArcIndexType>
class StaticGraph<NodeIndexType, ArcIndexType>::OutgoingArcIterator {
public:
@@ -1398,15 +1415,6 @@ class StaticGraph<NodeIndexType, ArcIndexType>::OutgoingArcIterator {
index_++;
}
// Note(user): we lose a bit by returning a BeginEndWrapper<> on top of
// this iterator rather than a simple IntegerRange<> on the arc indices.
// On my computer: around 420M arcs/sec instead of 440M arcs/sec.
//
// However, it is slightly more consistent to do it this way, and we don't
// have two different codes depending on the way a client iterates on the
// arcs.
DEFINE_STL_ITERATOR_FUNCTIONS(OutgoingArcIterator);
private:
ArcIndexType index_;
ArcIndexType limit_;
@@ -1669,7 +1677,6 @@ class ReverseArcListGraph<NodeIndexType, ArcIndexType>::OutgoingHeadIterator {
// ReverseArcStaticGraph implementation ----------------------------------------
DEFINE_RANGE_BASED_ARC_ITERATION(ReverseArcStaticGraph, Outgoing);
DEFINE_RANGE_BASED_ARC_ITERATION(ReverseArcStaticGraph, Incoming);
DEFINE_RANGE_BASED_ARC_ITERATION(ReverseArcStaticGraph,
OutgoingOrOppositeIncoming);
@@ -1798,6 +1805,7 @@ void ReverseArcStaticGraph<NodeIndexType, ArcIndexType>::Build(
}
}
// TODO(b/385094969): Remove this class.
template <typename NodeIndexType, typename ArcIndexType>
class ReverseArcStaticGraph<NodeIndexType, ArcIndexType>::OutgoingArcIterator {
public:
@@ -1817,10 +1825,6 @@ class ReverseArcStaticGraph<NodeIndexType, ArcIndexType>::OutgoingArcIterator {
index_++;
}
// TODO(user): we lose a bit by returning a BeginEndWrapper<> on top of this
// iterator rather than a simple IntegerRange on the arc indices.
DEFINE_STL_ITERATOR_FUNCTIONS(OutgoingArcIterator);
private:
ArcIndexType index_;
const ArcIndexType limit_;

View File

@@ -227,28 +227,6 @@ class IntegerRange : public BeginEndWrapper<IntegerRangeIterator<IntegerType>> {
}
};
// Allow iterating over a vector<T> as a mutable vector<T*>.
template <class T>
struct MutableVectorIteration {
explicit MutableVectorIteration(std::vector<T>* v) : v_(v) {}
struct Iterator {
explicit Iterator(typename std::vector<T>::iterator it) : it_(it) {}
T* operator*() { return &*it_; }
Iterator& operator++() {
it_++;
return *this;
}
bool operator!=(const Iterator& other) const { return other.it_ != it_; }
private:
typename std::vector<T>::iterator it_;
};
Iterator begin() { return Iterator(v_->begin()); }
Iterator end() { return Iterator(v_->end()); }
private:
std::vector<T>* const v_;
};
} // namespace util
#endif // UTIL_GRAPH_ITERATORS_H_

View File

@@ -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;

View File

@@ -1227,8 +1227,10 @@ LinearSumAssignment<GraphType, CostValue>::BestArcAndGap(
DCHECK(IsActive(left_node))
<< "Node " << left_node << " must be active (unmatched)!";
DCHECK_GT(epsilon_, 0);
typename GraphType::OutgoingArcIterator arc_it(*graph_, left_node);
ArcIndex best_arc = arc_it.Index();
const auto arcs = graph_->OutgoingArcs(left_node);
auto arc_it = arcs.begin();
DCHECK(!arcs.empty());
ArcIndex best_arc = *arc_it;
CostValue min_partial_reduced_cost = PartialReducedCost(best_arc);
// We choose second_min_partial_reduced_cost so that in the case of
// the largest possible gap (which results from a left-side node
@@ -1238,8 +1240,8 @@ LinearSumAssignment<GraphType, CostValue>::BestArcAndGap(
const CostValue max_gap = slack_relabeling_price_ - epsilon_;
CostValue second_min_partial_reduced_cost =
min_partial_reduced_cost + max_gap;
for (arc_it.Next(); arc_it.Ok(); arc_it.Next()) {
const ArcIndex arc = arc_it.Index();
for (++arc_it; arc_it != arcs.end(); ++arc_it) {
const ArcIndex arc = *arc_it;
const CostValue partial_reduced_cost = PartialReducedCost(arc);
if (partial_reduced_cost < second_min_partial_reduced_cost) {
if (partial_reduced_cost < min_partial_reduced_cost) {
@@ -1266,26 +1268,27 @@ inline CostValue LinearSumAssignment<GraphType, CostValue>::ImplicitPrice(
NodeIndex left_node) const {
DCHECK_GT(num_left_nodes_, left_node);
DCHECK_GT(epsilon_, 0);
typename GraphType::OutgoingArcIterator arc_it(*graph_, left_node);
const auto arcs = graph_->OutgoingArcs(left_node);
// We must not execute this method if left_node has no incident arc.
DCHECK(arc_it.Ok());
ArcIndex best_arc = arc_it.Index();
DCHECK(!arcs.empty());
auto arc_it = arcs.begin();
ArcIndex best_arc = *arc_it;
if (best_arc == matched_arc_[left_node]) {
arc_it.Next();
if (arc_it.Ok()) {
best_arc = arc_it.Index();
++arc_it;
if (arc_it != arcs.end()) {
best_arc = *arc_it;
}
}
CostValue min_partial_reduced_cost = PartialReducedCost(best_arc);
if (!arc_it.Ok()) {
if (arc_it == arcs.end()) {
// Only one arc is incident to left_node, and the node is
// currently matched along that arc, which must be the case in any
// feasible solution. Therefore we implicitly price this node so
// low that we will never consider unmatching it.
return -(min_partial_reduced_cost + slack_relabeling_price_);
}
for (arc_it.Next(); arc_it.Ok(); arc_it.Next()) {
const ArcIndex arc = arc_it.Index();
for (++arc_it; arc_it != arcs.end(); ++arc_it) {
const ArcIndex arc = *arc_it;
if (arc != matched_arc_[left_node]) {
const CostValue partial_reduced_cost = PartialReducedCost(arc);
if (partial_reduced_cost < min_partial_reduced_cost) {
@@ -1314,9 +1317,7 @@ bool LinearSumAssignment<GraphType, CostValue>::EpsilonOptimal() const {
// Get the implicit price of left_node and make sure the reduced
// costs of left_node's incident arcs are in bounds.
CostValue left_node_price = ImplicitPrice(left_node);
for (typename GraphType::OutgoingArcIterator arc_it(*graph_, left_node);
arc_it.Ok(); arc_it.Next()) {
const ArcIndex arc = arc_it.Index();
for (const ArcIndex arc : graph_->OutgoingArcs(left_node)) {
const CostValue reduced_cost = left_node_price + PartialReducedCost(arc);
// Note the asymmetric definition of epsilon-optimality that we
// use because it means we can saturate all admissible arcs in
@@ -1354,8 +1355,7 @@ bool LinearSumAssignment<GraphType, CostValue>::FinalizeSetup() {
// precondition.
for (NodeIndex node = 0; node < num_left_nodes_; ++node) {
matched_arc_[node] = GraphType::kNilArc;
typename GraphType::OutgoingArcIterator arc_it(*graph_, node);
if (!arc_it.Ok()) {
if (graph_->OutgoingArcs(node).empty()) {
incidence_precondition_satisfied_ = false;
}
}

View File

@@ -188,8 +188,7 @@ bool GenericMinCostFlow<Graph, ArcFlowType,
// Adjust and recompute min_node_excess[node].
min_node_excess[node] = node_excess_[node];
for (OutgoingArcIterator it(*graph_, node); it.Ok(); it.Next()) {
const int arc = it.Index();
for (const ArcIndex arc : graph_->OutgoingArcs(node)) {
residual_arc_capacity_[arc] =
std::min(residual_arc_capacity_[arc], upper_bound);
min_node_excess[node] =
@@ -204,8 +203,7 @@ bool GenericMinCostFlow<Graph, ArcFlowType,
// Adjust and recompute max_node_excess[node].
max_node_excess[node] = node_excess_[node];
for (IncomingArcIterator it(*graph_, node); it.Ok(); it.Next()) {
const int arc = it.Index();
for (const ArcIndex arc : graph_->IncomingArcs(node)) {
residual_arc_capacity_[arc] =
std::min(residual_arc_capacity_[arc], upper_bound);
max_node_excess[node] =

View File

@@ -408,8 +408,6 @@ class GenericMinCostFlow : public MinCostFlowBase {
typedef typename Graph::ArcIndex ArcIndex;
typedef int64_t CostValue;
typedef int64_t FlowQuantity;
typedef typename Graph::IncomingArcIterator IncomingArcIterator;
typedef typename Graph::OutgoingArcIterator OutgoingArcIterator;
typedef typename Graph::OutgoingOrOppositeIncomingArcIterator
OutgoingOrOppositeIncomingArcIterator;
typedef ZVector<ArcIndex> ArcIndexArray;

View File

@@ -646,8 +646,8 @@ bool CheckAssignmentFeasibility(const Graph& graph,
absl::Span<const int64_t> supply) {
for (typename Graph::NodeIndex node = 0; node < graph.num_nodes(); ++node) {
if (supply[node] != 0) {
typename Graph::OutgoingOrOppositeIncomingArcIterator it(graph, node);
EXPECT_TRUE(it.Ok()) << node << " has no incident arc";
EXPECT_FALSE(graph.OutgoingOrOppositeIncomingArcs(node).empty())
<< node << " has no incident arc";
}
}
return true;

View File

@@ -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")

View File

@@ -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",

View File

@@ -1,138 +0,0 @@
// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// [START imports]
#include <cstdint>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/graph/dag_constrained_shortest_path.h"
#include "ortools/graph/dag_shortest_path.h"
#include "ortools/graph/graph.h"
// [END imports]
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
// [START graph]
// Create a graph with n + 2 nodes, indexed from 0:
// * Node n is `source`
// * Node n+1 is `dest`
// * Nodes M = [0, 1, ..., n-1] are in the middle.
//
// There is a single resource constraints with limit 1.
//
// The graph has 3 * n - 1 arcs (with weights and both resources):
// * (source -> i) with weight 100 and no resource use for i in M
// * (i -> dest) with weight 100 and no resource use for i in M
// * (i -> (i+1)) with weight 1 and resource use of 1 for i = 0, ..., n-2
//
// Every path [source, i, dest] for i in M is a constrained shortest path from
// source to dest with weight 200.
const int n = 5;
const int source = n;
const int dest = n + 1;
const int num_arcs = 3 * n - 1;
util::StaticGraph<> graph;
// There are 3 types of arcs: (1) source to M, (2) M to dest, and (3) within
// M. This vector stores all of them, first of type (1), then type (2),
// then type (3). The arcs are ordered by i in M within each type.
std::vector<double> weights(num_arcs);
// Resources are first indexed by resource, then by arc.
std::vector<std::vector<double>> resources(1, std::vector<double>(num_arcs));
for (int i = 0; i < n; ++i) {
graph.AddArc(source, i);
weights[i] = 100.0;
resources[0][i] = 0.0;
}
for (int i = 0; i < n; ++i) {
graph.AddArc(i, dest);
weights[n + i] = 100.0;
resources[0][n + i] = 0.0;
}
for (int i = 0; i + 1 < n; ++i) {
graph.AddArc(i, i + 1);
weights[2 * n + i] = 1.0;
resources[0][2 * n + i] = 1.0;
}
// Static graph reorders the arcs at Build() time, use permutation to get from
// the old ordering to the new one.
std::vector<int32_t> permutation;
graph.Build(&permutation);
util::Permute(permutation, &weights);
util::Permute(permutation, &resources[0]);
// [END graph]
// [START first-path]
// A reusable shortest path calculator.
// We need a topological order. For this structured graph, we find it by hand
// instead of using util::graph::FastTopologicalSort().
std::vector<int32_t> topological_order = {source};
for (int32_t i = 0; i < n; ++i) {
topological_order.push_back(i);
}
topological_order.push_back(dest);
const std::vector<int> sources = {source};
const std::vector<int> destinations = {dest};
const std::vector<double> max_resources = {1.0};
operations_research::ConstrainedShortestPathsOnDagWrapper<util::StaticGraph<>>
constrained_shortest_path_on_dag(&graph, &weights, &resources,
topological_order, sources, destinations,
&max_resources);
operations_research::PathWithLength initial_constrained_shortest_path =
constrained_shortest_path_on_dag.RunConstrainedShortestPathOnDag();
std::cout << "Initial distance: " << initial_constrained_shortest_path.length
<< std::endl;
std::cout << "Initial path: "
<< absl::StrJoin(initial_constrained_shortest_path.node_path, ", ")
<< std::endl;
// [END first-path]
// [START more-paths]
// Now, we make a single arc from source to M free, and a single arc from M
// to dest free, and resolve. If the free edge from the source hits before
// the free edge to the dest in M, we use both, walking through M. Otherwise,
// we use only one free arc.
std::vector<std::pair<int, int>> fast_paths = {{2, 3}, {8, 1}, {3, 7}};
for (const auto [free_from_source, free_to_dest] : fast_paths) {
weights[permutation[free_from_source]] = 0;
weights[permutation[n + free_to_dest]] = 0;
operations_research::PathWithLength constrained_shortest_path =
constrained_shortest_path_on_dag.RunConstrainedShortestPathOnDag();
std::cout << "source -> " << free_from_source << " and " << free_to_dest
<< " -> dest are now free" << std::endl;
std::string label = absl::StrCat("_", free_from_source, "_", free_to_dest);
std::cout << "Distance" << label << ": " << constrained_shortest_path.length
<< std::endl;
std::cout << "Path" << label << ": "
<< absl::StrJoin(constrained_shortest_path.node_path, ", ")
<< std::endl;
// Restore the old weights
weights[permutation[free_from_source]] = 100;
weights[permutation[n + free_to_dest]] = 100;
}
// [END more-paths]
return 0;
}

View File

@@ -1,87 +0,0 @@
// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <cstdint>
#include <iostream>
#include <vector>
#include "absl/log/check.h"
#include "absl/status/status.h"
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/base/status_macros.h"
#include "ortools/graph/dag_shortest_path.h"
#include "ortools/graph/graph.h"
#include "ortools/graph/topologicalsorter.h"
namespace {
absl::Status Main() {
util::StaticGraph<> graph;
std::vector<double> weights;
graph.AddArc(0, 1);
weights.push_back(2.0);
graph.AddArc(0, 2);
weights.push_back(5.0);
graph.AddArc(1, 4);
weights.push_back(1.0);
graph.AddArc(2, 4);
weights.push_back(-3.0);
graph.AddArc(3, 4);
weights.push_back(0.0);
// Static graph reorders the arcs at Build() time, use permutation to get
// from the old ordering to the new one.
std::vector<int32_t> permutation;
graph.Build(&permutation);
util::Permute(permutation, &weights);
// We need a topological order. We can find it by hand on this small graph,
// e.g., {0, 1, 2, 3, 4}, but we demonstrate how to compute one instead.
ASSIGN_OR_RETURN(const std::vector<int32_t> topological_order,
util::graph::FastTopologicalSort(graph));
operations_research::KShortestPathsOnDagWrapper<util::StaticGraph<>>
shortest_paths_on_dag(&graph, &weights, topological_order,
/*path_count=*/2);
const int source = 0;
shortest_paths_on_dag.RunKShortestPathOnDag({source});
// For each node other than 0, print its distance and the shortest path.
for (int node = 1; node < 5; ++node) {
std::cout << "Node " << node << ":\n";
if (!shortest_paths_on_dag.IsReachable(node)) {
std::cout << "\tNo path to node " << node << std::endl;
continue;
}
const std::vector<double> lengths = shortest_paths_on_dag.LengthsTo(node);
const std::vector<std::vector<int32_t>> paths =
shortest_paths_on_dag.NodePathsTo(node);
for (int path_index = 0; path_index < lengths.size(); ++path_index) {
std::cout << "\t#" << (path_index + 1) << " shortest path to node "
<< node << " has length: " << lengths[path_index] << std::endl;
std::cout << "\t#" << (path_index + 1) << " shortest path to node "
<< node << " is: " << absl::StrJoin(paths[path_index], ", ")
<< std::endl;
}
}
return absl::OkStatus();
}
} // namespace
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
QCHECK_OK(Main());
return 0;
}

View File

@@ -1,135 +0,0 @@
// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// [START imports]
#include <cstdint>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/graph/dag_shortest_path.h"
#include "ortools/graph/graph.h"
// [END imports]
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
// [START graph]
// Create a graph with n + 2 nodes, indexed from 0:
// * Node n is `source`
// * Node n+1 is `dest`
// * Nodes M = [0, 1, ..., n-1] are in the middle.
//
// The graph has 3 * n - 1 arcs (with weights):
// * (source -> i) with weight 100 + i for i in M
// * (i -> dest) with weight 100 + i for i in M
// * (i -> (i+1)) with weight 10 for i = 0, ..., n-2
const int n = 10;
const int source = n;
const int dest = n + 1;
util::StaticGraph<> graph;
// There are 3 types of arcs: (1) source to M, (2) M to dest, and (3) within
// M. This vector stores all of them, first of type (1), then type (2),
// then type (3). The arcs are ordered by i in M within each type.
std::vector<double> weights(3 * n - 1);
for (int i = 0; i < n; ++i) {
graph.AddArc(source, i);
weights[i] = 100.0 + i;
}
for (int i = 0; i < n; ++i) {
graph.AddArc(i, dest);
weights[n + i] = 100.0 + i;
}
for (int i = 0; i + 1 < n; ++i) {
graph.AddArc(i, i + 1);
weights[2 * n + i] = 10.0;
}
// Static graph reorders the arcs at Build() time, use permutation to get from
// the old ordering to the new one.
std::vector<int32_t> permutation;
graph.Build(&permutation);
util::Permute(permutation, &weights);
// [END graph]
// [START first-path]
// A reusable shortest path calculator.
// We need a topological order. For this structured graph, we find it by hand
// instead of using util::graph::FastTopologicalSort().
std::vector<int32_t> topological_order = {source};
for (int32_t i = 0; i < n; ++i) {
topological_order.push_back(i);
}
topological_order.push_back(dest);
operations_research::KShortestPathsOnDagWrapper<util::StaticGraph<>>
shortest_paths_on_dag(&graph, &weights, topological_order,
/*path_count=*/2);
shortest_paths_on_dag.RunKShortestPathOnDag({source});
const std::vector<double> initial_lengths =
shortest_paths_on_dag.LengthsTo(dest);
const std::vector<std::vector<int32_t>> initial_paths =
shortest_paths_on_dag.NodePathsTo(dest);
std::cout << "No free arcs" << std::endl;
for (int path_index = 0; path_index < initial_lengths.size(); ++path_index) {
std::cout << "\t#" << (path_index + 1)
<< " shortest path has length: " << initial_lengths[path_index]
<< std::endl;
std::cout << "\t#" << (path_index + 1) << " shortest path is: "
<< absl::StrJoin(initial_paths[path_index], ", ") << std::endl;
}
// [END first-path]
// [START more-paths]
// Now, we make a single arc from source to M free, and a single arc from M
// to dest free, and resolve. If the free edge from the source hits before
// the free edge to the dest in M, we use both, walking through M. Otherwise,
// we use only one free arc.
std::vector<std::pair<int, int>> fast_paths = {
{2, 4}, {8, 1}, {3, 3}, {0, 0}};
for (const auto [free_from_source, free_to_dest] : fast_paths) {
weights[permutation[free_from_source]] = 0;
weights[permutation[n + free_to_dest]] = 0;
shortest_paths_on_dag.RunKShortestPathOnDag({source});
std::cout << "source -> " << free_from_source << " and " << free_to_dest
<< " -> dest are now free" << std::endl;
std::string label =
absl::StrCat(" (", free_from_source, ", ", free_to_dest, ")");
const std::vector<double> lengths = shortest_paths_on_dag.LengthsTo(dest);
const std::vector<std::vector<int32_t>> paths =
shortest_paths_on_dag.NodePathsTo(dest);
for (int path_index = 0; path_index < lengths.size(); ++path_index) {
std::cout << "\t#" << (path_index + 1) << " shortest path" << label
<< " has length: " << lengths[path_index] << std::endl;
std::cout << "\t#" << (path_index + 1) << " shortest path" << label
<< " is: " << absl::StrJoin(paths[path_index], ", ")
<< std::endl;
}
// Restore the old weights
weights[permutation[free_from_source]] = 100 + free_from_source;
weights[permutation[n + free_to_dest]] = 100 + free_to_dest;
}
// [END more-paths]
return 0;
}

View File

@@ -1,81 +0,0 @@
// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <cstdint>
#include <iostream>
#include <vector>
#include "absl/log/check.h"
#include "absl/status/status.h"
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/base/status_macros.h"
#include "ortools/graph/dag_shortest_path.h"
#include "ortools/graph/graph.h"
#include "ortools/graph/topologicalsorter.h"
namespace {
absl::Status Main() {
util::StaticGraph<> graph;
std::vector<double> weights;
graph.AddArc(0, 2);
weights.push_back(5.0);
graph.AddArc(0, 3);
weights.push_back(4.0);
graph.AddArc(1, 3);
weights.push_back(1.0);
graph.AddArc(2, 4);
weights.push_back(-3.0);
graph.AddArc(3, 4);
weights.push_back(0.0);
// Static graph reorders the arcs at Build() time, use permutation to get
// from the old ordering to the new one.
std::vector<int32_t> permutation;
graph.Build(&permutation);
util::Permute(permutation, &weights);
// We need a topological order. We can find it by hand on this small graph,
// e.g., {0, 1, 2, 3, 4}, but we demonstrate how to compute one instead.
ASSIGN_OR_RETURN(const std::vector<int32_t> topological_order,
util::graph::FastTopologicalSort(graph));
operations_research::ShortestPathsOnDagWrapper<util::StaticGraph<>>
shortest_path_on_dag(&graph, &weights, topological_order);
const int source = 0;
shortest_path_on_dag.RunShortestPathOnDag({source});
// For each node other than 0, print its distance and the shortest path.
for (int i = 1; i < 5; ++i) {
if (shortest_path_on_dag.IsReachable(i)) {
std::cout << "Length of shortest path to node " << i << ": "
<< shortest_path_on_dag.LengthTo(i) << std::endl;
std::cout << "Shortest path to node " << i << ": "
<< absl::StrJoin(shortest_path_on_dag.NodePathTo(i), ", ")
<< std::endl;
} else {
std::cout << "No path to node: " << i << std::endl;
}
}
return absl::OkStatus();
}
} // namespace
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
QCHECK_OK(Main());
return 0;
}

View File

@@ -1,120 +0,0 @@
// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// [START imports]
#include <cstdint>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/graph/dag_shortest_path.h"
#include "ortools/graph/graph.h"
// [END imports]
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
// [START graph]
// Create a graph with n + 2 nodes, indexed from 0:
// * Node n is `source`
// * Node n+1 is `dest`
// * Nodes M = [0, 1, ..., n-1] are in the middle.
//
// The graph has 3 * n - 1 arcs (with weights):
// * (source -> i) with weight 100 for i in M
// * (i -> dest) with weight 100 for i in M
// * (i -> (i+1)) with weight 1 for i = 0, ..., n-2
//
// Every path [source, i, dest] for i in M is a shortest path from source to
// dest with weight 200.
const int n = 10;
const int source = n;
const int dest = n + 1;
util::StaticGraph<> graph;
// There are 3 types of arcs: (1) source to M, (2) M to dest, and (3) within
// M. This vector stores all of them, first of type (1), then type (2),
// then type (3). The arcs are ordered by i in M within each type.
std::vector<double> weights(3 * n - 1);
for (int i = 0; i < n; ++i) {
graph.AddArc(source, i);
weights[i] = 100.0;
}
for (int i = 0; i < n; ++i) {
graph.AddArc(i, dest);
weights[n + i] = 100.0;
}
for (int i = 0; i + 1 < n; ++i) {
graph.AddArc(i, i + 1);
weights[2 * n + i] = 1.0;
}
// Static graph reorders the arcs at Build() time, use permutation to get from
// the old ordering to the new one.
std::vector<int32_t> permutation;
graph.Build(&permutation);
util::Permute(permutation, &weights);
// [END graph]
// [START first-path]
// A reusable shortest path calculator.
// We need a topological order. For this structured graph, we find it by hand
// instead of using util::graph::FastTopologicalSort().
std::vector<int32_t> topological_order = {source};
for (int i = 0; i < n; ++i) {
topological_order.push_back(i);
}
topological_order.push_back(dest);
operations_research::ShortestPathsOnDagWrapper<util::StaticGraph<>>
shortest_path_on_dag(&graph, &weights, topological_order);
shortest_path_on_dag.RunShortestPathOnDag({source});
std::cout << "Initial distance: " << shortest_path_on_dag.LengthTo(dest)
<< std::endl;
std::cout << "Initial path: "
<< absl::StrJoin(shortest_path_on_dag.NodePathTo(dest), ", ")
<< std::endl;
// [END first-path]
// [START more-paths]
// Now, we make a single arc from source to M free, and a single arc from M
// to dest free, and resolve. If the free edge from the source hits before
// the free edge to the dest in M, we use both, walking through M. Otherwise,
// we use only one free arc.
std::vector<std::pair<int, int>> fast_paths = {{2, 4}, {8, 1}, {3, 7}};
for (const auto [free_from_source, free_to_dest] : fast_paths) {
weights[permutation[free_from_source]] = 0;
weights[permutation[n + free_to_dest]] = 0;
shortest_path_on_dag.RunShortestPathOnDag({source});
std::cout << "source -> " << free_from_source << " and " << free_to_dest
<< " -> dest are now free" << std::endl;
std::string label = absl::StrCat("_", free_from_source, "_", free_to_dest);
std::cout << "Distance" << label << ": "
<< shortest_path_on_dag.LengthTo(dest) << std::endl;
std::cout << "Path" << label << ": "
<< absl::StrJoin(shortest_path_on_dag.NodePathTo(dest), ", ")
<< std::endl;
// Restore the old weights
weights[permutation[free_from_source]] = 100;
weights[permutation[n + free_to_dest]] = 100;
}
// [END more-paths]
return 0;
}

View File

@@ -1,47 +0,0 @@
// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <iostream>
#include <vector>
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/graph/dag_constrained_shortest_path.h"
#include "ortools/graph/dag_shortest_path.h"
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
// The input graph, encoded as a list of arcs with distances.
std::vector<operations_research::ArcWithLengthAndResources> arcs = {
{.from = 0, .to = 1, .length = 5, .resources = {1, 2}},
{.from = 0, .to = 2, .length = 4, .resources = {3, 2}},
{.from = 0, .to = 2, .length = 1, .resources = {2, 3}},
{.from = 1, .to = 3, .length = -3, .resources = {8, 0}},
{.from = 2, .to = 3, .length = 0, .resources = {3, 1}}};
const int num_nodes = 4;
const std::vector<double> max_resources = {6, 3};
const int source = 0;
const int destination = 3;
const operations_research::PathWithLength path_with_length =
operations_research::ConstrainedShortestPathsOnDag(
num_nodes, arcs, source, destination, max_resources);
// Print to length of the path and then the nodes in the path.
std::cout << "Constrained shortest path length: " << path_with_length.length
<< std::endl;
std::cout << "Constrained shortest path nodes: "
<< absl::StrJoin(path_with_length.node_path, ", ") << std::endl;
return 0;
}

View File

@@ -1,47 +0,0 @@
// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <iostream>
#include <vector>
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/graph/dag_shortest_path.h"
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
// The input graph, encoded as a list of arcs with distances.
std::vector<operations_research::ArcWithLength> arcs = {
{.from = 0, .to = 1, .length = 2}, {.from = 0, .to = 2, .length = 5},
{.from = 0, .to = 3, .length = 4}, {.from = 1, .to = 4, .length = 1},
{.from = 2, .to = 4, .length = -3}, {.from = 3, .to = 4, .length = 0}};
const int num_nodes = 5;
const int source = 0;
const int destination = 4;
const int path_count = 2;
const std::vector<operations_research::PathWithLength> paths_with_length =
operations_research::KShortestPathsOnDag(num_nodes, arcs, source,
destination, path_count);
for (int path_index = 0; path_index < paths_with_length.size();
++path_index) {
std::cout << "#" << (path_index + 1) << " shortest path has length: "
<< paths_with_length[path_index].length << std::endl;
std::cout << "#" << (path_index + 1) << " shortest path is: "
<< absl::StrJoin(paths_with_length[path_index].node_path, ", ")
<< std::endl;
}
return 0;
}

View File

@@ -1,44 +0,0 @@
// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <iostream>
#include <vector>
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/graph/dag_shortest_path.h"
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
// The input graph, encoded as a list of arcs with distances.
std::vector<operations_research::ArcWithLength> arcs = {
{.from = 0, .to = 2, .length = 5},
{.from = 0, .to = 3, .length = 4},
{.from = 1, .to = 3, .length = 1},
{.from = 2, .to = 4, .length = -3},
{.from = 3, .to = 4, .length = 0}};
const int num_nodes = 5;
const int source = 0;
const int destination = 4;
const operations_research::PathWithLength path_with_length =
operations_research::ShortestPathsOnDag(num_nodes, arcs, source,
destination);
// Print to length of the path and then the nodes in the path.
std::cout << "Shortest path length: " << path_with_length.length << std::endl;
std::cout << "Shortest path nodes: "
<< absl::StrJoin(path_with_length.node_path, ", ") << std::endl;
return 0;
}

View File

@@ -67,9 +67,7 @@ void CheckPathDataRow(const GraphType& graph,
const PathDistance expected_distances[],
typename GraphType::NodeIndex tail) {
int index = tail * graph.num_nodes();
for (typename GraphType::NodeIterator iterator(graph); iterator.Ok();
iterator.Next()) {
const typename GraphType::NodeIndex head(iterator.Index());
for (const typename GraphType::NodeIndex head : graph.AllNodes()) {
CheckPathDataPair(container, distance_container, expected_distances[index],
expected_paths[index], tail, head);
++index;
@@ -97,9 +95,7 @@ void CheckPathData(const GraphType& graph,
const GenericPathContainer<GraphType>& distance_container,
const typename GraphType::NodeIndex expected_paths[],
const PathDistance expected_distances[]) {
for (typename GraphType::NodeIterator iterator(graph); iterator.Ok();
iterator.Next()) {
const typename GraphType::NodeIndex tail(iterator.Index());
for (const typename GraphType::NodeIndex tail : graph.AllNodes()) {
CheckPathDataRow(graph, container, distance_container, expected_paths,
expected_distances, tail);
}

View File

@@ -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 ' +