graph: export from google3

This commit is contained in:
Corentin Le Molgat
2024-04-15 17:37:24 +02:00
parent 5746323699
commit 80e677c19b
5 changed files with 1266 additions and 232 deletions

View File

@@ -376,10 +376,10 @@ cc_library(
srcs = ["dag_shortest_path.cc"],
hdrs = ["dag_shortest_path.h"],
deps = [
":ebert_graph",
":graph",
":topologicalsorter",
"@com_google_absl//absl/algorithm:container",
"@com_google_absl//absl/base:log_severity",
"@com_google_absl//absl/log",
"@com_google_absl//absl/log:check",
"@com_google_absl//absl/status",

View File

@@ -17,7 +17,6 @@
#include <stdbool.h>
#include <cmath>
#include <concepts>
#include <limits>
#include <vector>
@@ -82,23 +81,6 @@ PathWithLength ConstrainedShortestPathsOnDag(
// -----------------------------------------------------------------------------
// Advanced API.
// -----------------------------------------------------------------------------
#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>;
};
#endif
// 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`.

View File

@@ -14,22 +14,32 @@
#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/ebert_graph.h"
#include "ortools/graph/graph.h"
#include "ortools/graph/topologicalsorter.h"
namespace operations_research {
PathWithLength ShortestPathsOnDag(
const int num_nodes, absl::Span<const ArcWithLength> arcs_with_length,
int source, int destination) {
using GraphType = util::StaticGraph<>;
using NodeIndex = GraphType::NodeIndex;
using ArcIndex = GraphType::ArcIndex;
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());
@@ -41,10 +51,10 @@ PathWithLength ShortestPathsOnDag(
graph.Build(&permutation);
util::Permute(permutation, &arc_lengths);
std::vector<ArcIndex> inverse_permutation(permutation.size());
std::vector<ArcIndex> original_arc_indices(permutation.size());
if (!permutation.empty()) {
for (ArcIndex i = 0; i < permutation.size(); ++i) {
inverse_permutation[permutation[i]] = i;
original_arc_indices[permutation[i]] = i;
}
}
@@ -52,27 +62,75 @@ PathWithLength ShortestPathsOnDag(
util::graph::FastTopologicalSort(graph);
CHECK_OK(topological_order) << "arcs_with_length form a cycle.";
ShortestPathsOnDagWrapper<util::StaticGraph<>> shortest_path_on_dag(
&graph, &arc_lengths, *topological_order);
return ShortestPathOnDagProblem{.graph = graph,
.arc_lengths = arc_lengths,
.original_arc_indices = original_arc_indices,
.topological_order = *topological_order};
}
void GetOriginalArcPath(const std::vector<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(),
.arc_path = std::vector<int>(),
.node_path = std::vector<int>()};
return PathWithLength{.length = std::numeric_limits<double>::infinity()};
}
std::vector<int> arc_path = shortest_path_on_dag.ArcPathTo(destination);
if (!inverse_permutation.empty()) {
for (int i = 0; i < arc_path.size(); ++i) {
arc_path[i] = inverse_permutation[arc_path[i]];
}
}
GetOriginalArcPath(problem.original_arc_indices, arc_path);
return PathWithLength{
.length = shortest_path_on_dag.LengthTo(destination),
.arc_path = 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<util::StaticGraph<>> 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<ArcIndex>> arc_paths =
shortest_paths_on_dag.ArcPathsTo(destination);
std::vector<std::vector<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

@@ -15,11 +15,12 @@
#define OR_TOOLS_GRAPH_DAG_SHORTEST_PATH_H_
#include <cmath>
#include <concepts>
#include <functional>
#include <limits>
#include <vector>
#include "absl/algorithm/container.h"
#include "absl/base/log_severity.h"
#include "absl/log/check.h"
#include "absl/status/status.h"
#include "absl/strings/str_format.h"
@@ -27,9 +28,8 @@
#include "ortools/base/logging.h"
namespace operations_research {
// TODO(user): extend to non-floating lengths.
// TODO(user): extend to allow for length functor.
// TODO(user): extend to k-shortest paths for k > 1.
// 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).
@@ -63,23 +63,52 @@ struct PathWithLength {
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.
// 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. `GraphType` *must* implement the following
// types and functions: `GraphType::NodeIndex`, `GraphType::ArcIndex`,
// `num_nodes()`, `num_arcs()`, `OutgoingArcs(tail)`, `Tail(arc)` and
// `Head(arc)`. You can use on of the interfaces defined in
// `util/graph/graph.h`.
// 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;
@@ -116,9 +145,7 @@ class ShortestPathsOnDagWrapper {
// 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 {
return length_from_sources_[node] < std::numeric_limits<double>::infinity();
}
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`.
@@ -138,6 +165,7 @@ class ShortestPathsOnDagWrapper {
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_;
@@ -148,122 +176,146 @@ class ShortestPathsOnDagWrapper {
std::vector<NodeIndex> reached_nodes_;
};
template <typename GraphType>
// 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);
// -----------------------------------------------------------------------------
// Implementation.
// Implementations.
// -----------------------------------------------------------------------------
template <class GraphType>
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);
if (DEBUG_MODE) {
CHECK_EQ(arc_lengths_->size(), graph_->num_arcs());
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";
}
// 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(),
std::numeric_limits<double>::infinity());
incoming_shortest_path_arc_.resize(graph_->num_nodes(), -1);
reached_nodes_.reserve(graph_->num_nodes());
}
template <class GraphType>
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_;
// Note that we do not re-assign `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] = std::numeric_limits<double>::infinity();
}
DCHECK(std::all_of(
length_from_sources.begin(), length_from_sources.end(),
[](double l) { return l == std::numeric_limits<double>::infinity(); }));
reached_nodes_.clear();
for (const NodeIndex source : sources) {
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 == std::numeric_limits<double>::infinity()) {
continue;
}
reached_nodes_.push_back(tail);
for (const ArcIndex arc : graph_->OutgoingArcs(tail)) {
const NodeIndex head = graph_->Head(arc);
DCHECK(arc_lengths[arc] != -std::numeric_limits<double>::infinity());
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;
}
}
}
}
// TODO(user): If `ArcPathTo` and/or `NodePathTo` functions become
// 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>
std::vector<typename GraphType::ArcIndex>
ShortestPathsOnDagWrapper<GraphType>::ArcPathTo(NodeIndex node) const {
CHECK(IsReachable(node));
std::vector<ArcIndex> arc_path;
ArcIndex current_arc = incoming_shortest_path_arc_[node];
while (current_arc != -1) {
arc_path.push_back(current_arc);
current_arc = incoming_shortest_path_arc_[graph_->Tail(current_arc)];
#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));
}
absl::c_reverse(arc_path);
return arc_path;
}
template <typename GraphType>
std::vector<typename GraphType::NodeIndex>
ShortestPathsOnDagWrapper<GraphType>::NodePathTo(NodeIndex node) const {
CHECK(IsReachable(node));
std::vector<NodeIndex> node_path;
NodeIndex current_node = node;
node_path.push_back(current_node);
ArcIndex current_arc = incoming_shortest_path_arc_[current_node];
while (current_arc != -1) {
current_node = graph_->Tail(current_arc);
node_path.push_back(current_node);
current_arc = incoming_shortest_path_arc_[current_node];
}
absl::c_reverse(node_path);
node_path.push_back(graph.Head(arc_path.back()));
return node_path;
}
template <typename GraphType>
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) {
@@ -296,5 +348,369 @@ absl::Status TopologicalOrderIsValid(
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>();
min_heap.reserve(path_count_ + 1);
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.size() > path_count_) {
min_heap.pop_back();
}
}
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_

View File

@@ -44,6 +44,82 @@ using ::testing::HasSubstr;
using ::testing::IsEmpty;
using ::testing::status::StatusIs;
TEST(TopologicalOrderIsValidTest, ValidateTopologicalOrder) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
util::ListGraph<> graph(num_nodes, /*arc_capacity=*/2);
graph.AddArc(source, destination);
EXPECT_OK(TopologicalOrderIsValid(graph, {source, destination}));
EXPECT_THAT(TopologicalOrderIsValid(graph, {source}),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("topological_order.size() = 1")));
EXPECT_THAT(TopologicalOrderIsValid(graph, {source, source}),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("0 appears twice")));
EXPECT_THAT(TopologicalOrderIsValid(graph, {destination, source}),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("arc (0, 1) is inconsistent")));
graph.AddArc(source, source);
EXPECT_THAT(TopologicalOrderIsValid(graph, {source, destination}),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("arc (0, 0) is inconsistent")));
}
// -----------------------------------------------------------------------------
// ShortestPathOnDagTest and ShortestPathsOnDagWrapperTest.
// -----------------------------------------------------------------------------
TEST(ShortestPathOnDagTest, EmptyGraph) {
EXPECT_DEATH(ShortestPathsOnDag(/*num_nodes=*/0, /*arcs_with_length=*/{},
/*source=*/0, /*destination=*/0),
"num_nodes\\(\\) > 0");
}
TEST(ShortestPathOnDagTest, NoArcGraph) {
EXPECT_DEATH(ShortestPathsOnDag(/*num_nodes=*/1, /*arcs_with_length=*/{},
/*source=*/0, /*destination=*/0),
"num_arcs\\(\\) > 0");
}
TEST(ShortestPathOnDagTest, NonExistingSourceBecauseNegative) {
EXPECT_DEATH(
ShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, 0.0}},
/*source=*/-1, /*destination=*/1),
"Node must be nonnegative");
}
TEST(ShortestPathOnDagTest, NonExistingSourceBecauseTooLarge) {
EXPECT_DEATH(
ShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, 0.0}},
/*source=*/3, /*destination=*/1),
"num_nodes\\(\\)");
}
TEST(ShortestPathOnDagTest, NonExistingDestinationBecauseNegative) {
EXPECT_DEATH(
ShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, 0.0}},
/*source=*/0, /*destination=*/-1),
"Node must be nonnegative");
}
TEST(ShortestPathOnDagTest, NonExistingDestinationBecauseTooLarge) {
EXPECT_DEATH(
ShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, 0.0}},
/*source=*/0, /*destination=*/3),
"num_nodes\\(\\)");
}
TEST(ShortestPathOnDagTest, Cycle) {
EXPECT_DEATH(
ShortestPathsOnDag(/*num_nodes=*/2,
/*arcs_with_length=*/{{0, 1, 0.0}, {1, 0, 0.0}},
/*source=*/0, /*destination=*/1),
"cycle");
}
TEST(ShortestPathOnDagTest, SimpleGraph) {
const int source = 0;
const int destination = 1;
@@ -61,6 +137,19 @@ TEST(ShortestPathOnDagTest, SimpleGraph) {
/*node_path=*/ElementsAre(source, a, destination)));
}
TEST(ShortestPathOnDagTest, SourceIsDestination) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
const std::vector<ArcWithLength> arcs_with_length = {
{source, destination, 1.0}};
EXPECT_THAT(ShortestPathsOnDag(num_nodes, arcs_with_length, source, source),
FieldsAre(
/*length=*/0.0, /*arc_path=*/IsEmpty(),
/*node_path=*/ElementsAre(source)));
}
TEST(ShortestPathOnDagTest, LargerGraphWithNegativeCost) {
const int source = 0;
const int a = 3;
@@ -78,42 +167,6 @@ TEST(ShortestPathOnDagTest, LargerGraphWithNegativeCost) {
/*node_path=*/ElementsAre(source, a, b, destination)));
}
TEST(ShortestPathOnDagTest, Cycle) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
const std::vector<ArcWithLength> arcs_with_length = {
{source, destination, 1.0}, {destination, source, 2.0}};
EXPECT_DEATH(
ShortestPathsOnDag(num_nodes, arcs_with_length, source, destination),
"cycle");
}
TEST(ShortestPathOnDagTest, NaNWeight) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
const std::vector<ArcWithLength> arcs_with_length = {
{source, destination, std::numeric_limits<double>::quiet_NaN()}};
EXPECT_DEBUG_DEATH(
ShortestPathsOnDag(num_nodes, arcs_with_length, source, destination),
"NaN");
}
TEST(ShortestPathOnDagTest, MinusInfWeight) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
const std::vector<ArcWithLength> arcs_with_length = {
{source, destination, -kInf}};
EXPECT_DEBUG_DEATH(
ShortestPathsOnDag(num_nodes, arcs_with_length, source, destination),
"-inf");
}
TEST(ShortestPathOnDagTest, SetsNotConnected) {
const int source = 0;
const int destination = 1;
@@ -127,6 +180,26 @@ TEST(ShortestPathOnDagTest, SetsNotConnected) {
/*node_path=*/IsEmpty()));
}
TEST(ShortestPathOnDagTest, TwoConnectedComponents) {
const int a = 0;
const int b = 1;
const int c = 2;
const int d = 3;
const int e = 4;
const int num_nodes = 5;
const std::vector<ArcWithLength> arcs_with_length = {
{a, b, 0.0}, {b, c, 0.0}, {d, e, 0.0}};
EXPECT_THAT(ShortestPathsOnDag(num_nodes, arcs_with_length, /*source=*/a,
/*destination=*/e),
FieldsAre(/*length=*/kInf, /*arc_path=*/IsEmpty(),
/*node_path=*/IsEmpty()));
EXPECT_THAT(ShortestPathsOnDag(num_nodes, arcs_with_length, /*source=*/b,
/*destination=*/d),
FieldsAre(/*length=*/kInf, /*arc_path=*/IsEmpty(),
/*node_path=*/IsEmpty()));
}
TEST(ShortestPathOnDagTest, SetsNotConnectedDueToInfiniteCost) {
const int source = 0;
const int destination = 1;
@@ -209,21 +282,6 @@ TEST(ShortestPathOnDagTest, UpdateCost) {
/*node_path=*/ElementsAre(source, b, destination)));
}
TEST(ShortestPathsOnDagWrapperTest, ValidateTopologicalOrder) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
util::ListGraph<> graph(num_nodes, /*arc_capacity=*/1);
std::vector<double> arc_lengths;
graph.AddArc(source, destination);
arc_lengths.push_back(1.0);
const std::vector<int> topological_order = {source};
EXPECT_DEBUG_DEATH(ShortestPathsOnDagWrapper<util::ListGraph<>>(
&graph, &arc_lengths, topological_order),
"Invalid topological order");
}
TEST(ShortestPathsOnDagWrapperTest, MultipleSources) {
const int source_1 = 0;
const int source_2 = 1;
@@ -247,7 +305,7 @@ TEST(ShortestPathsOnDagWrapperTest, MultipleSources) {
ElementsAre(source_1, destination));
}
TEST(ShortestPathOnDagTest, ShortestPathGoesThroughMultipleSources) {
TEST(ShortestPathsOnDagWrapperTest, ShortestPathGoesThroughMultipleSources) {
const int source_1 = 0;
const int source_2 = 1;
const int destination = 2;
@@ -270,7 +328,7 @@ TEST(ShortestPathOnDagTest, ShortestPathGoesThroughMultipleSources) {
ElementsAre(source_1, source_2, destination));
}
TEST(ShortestPathOnDagTest, MultipleDestinations) {
TEST(ShortestPathsOnDagWrapperTest, MultipleDestinations) {
const int source = 0;
const int destination_1 = 1;
const int destination_2 = 2;
@@ -347,31 +405,6 @@ TEST(ShortestPathsOnDagWrapperTest, UpdateCost) {
ElementsAre(source, b, destination));
}
TEST(TopologicalOrderIsValidTest, ValidateTopologicalOrder) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
util::ListGraph<> graph(num_nodes, /*arc_capacity=*/2);
graph.AddArc(source, destination);
EXPECT_OK(TopologicalOrderIsValid(graph, {source, destination}));
EXPECT_THAT(TopologicalOrderIsValid(graph, {source}),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("topological_order.size() = 1")));
EXPECT_THAT(TopologicalOrderIsValid(graph, {source, source}),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("0 appears twice")));
EXPECT_THAT(TopologicalOrderIsValid(graph, {destination, source}),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("arc (0, 1) is inconsistent")));
graph.AddArc(source, source);
EXPECT_THAT(TopologicalOrderIsValid(graph, {source, destination}),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("arc (0, 0) is inconsistent")));
}
// 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.
@@ -424,7 +457,7 @@ std::vector<double> GenerateRandomLengths(const util::StaticGraph<>& graph,
return arc_lengths;
}
TEST(ShortestPathOnDagTest, RandomizedStressTest) {
TEST(ShortestPathsOnDagWrapperTest, RandomizedStressTest) {
absl::BitGen bit_gen;
const int kNumTests = 10000;
for (int test = 0; test < kNumTests; ++test) {
@@ -492,8 +525,41 @@ TEST(ShortestPathOnDagTest, RandomizedStressTest) {
}
}
// Debug tests.
#ifndef NDEBUG
TEST(ShortestPathOnDagTest, MinusInfWeight) {
EXPECT_DEATH(
ShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, -kInf}},
/*source=*/0, /*destination=*/1),
"-inf");
}
TEST(ShortestPathOnDagTest, NaNWeight) {
EXPECT_DEATH(
ShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/
{{0, 1, std::numeric_limits<double>::quiet_NaN()}},
/*source=*/0, /*destination=*/1),
"NaN");
}
TEST(ShortestPathsOnDagWrapperTest, ValidateTopologicalOrder) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
util::ListGraph<> graph(num_nodes, /*arc_capacity=*/1);
std::vector<double> arc_lengths;
graph.AddArc(source, destination);
arc_lengths.push_back(1.0);
const std::vector<int> topological_order = {source};
EXPECT_DEATH(ShortestPathsOnDagWrapper<util::ListGraph<>>(
&graph, &arc_lengths, topological_order),
"Invalid topological order");
}
#endif // NDEBUG
// -----------------------------------------------------------------------------
// Benchmark.
// ShortestPathsOnDagWrapper benchmarks.
// -----------------------------------------------------------------------------
void BM_RandomDag(benchmark::State& state) {
absl::BitGen bit_gen;
@@ -556,5 +622,517 @@ void BM_LineDag(benchmark::State& state) {
BENCHMARK(BM_LineDag)->Arg(1 << 16)->Arg(1 << 22)->Arg(1 << 24);
// -----------------------------------------------------------------------------
// KShortestPathOnDagTest and KShortestPathsOnDagWrapperTest.
// -----------------------------------------------------------------------------
TEST(KShortestPathOnDagTest, EmptyGraph) {
EXPECT_DEATH(
KShortestPathsOnDag(/*num_nodes=*/0, /*arcs_with_length=*/{},
/*source=*/0, /*destination=*/0, /*path_count=*/2),
"num_nodes\\(\\) > 0");
}
TEST(KShortestPathOnDagTest, NoArcGraph) {
EXPECT_DEATH(
KShortestPathsOnDag(/*num_nodes=*/1, /*arcs_with_length=*/{},
/*source=*/0, /*destination=*/0, /*path_count=*/2),
"num_arcs\\(\\) > 0");
}
TEST(KShortestPathOnDagTest, NonExistingSourceBecauseNegative) {
EXPECT_DEATH(
KShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, 0.0}},
/*source=*/-1, /*destination=*/1, /*path_count=*/2),
"Node must be nonnegative");
}
TEST(KShortestPathOnDagTest, NonExistingSourceBecauseTooLarge) {
EXPECT_DEATH(
KShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, 0.0}},
/*source=*/3, /*destination=*/1, /*path_count=*/2),
"num_nodes\\(\\)");
}
TEST(KShortestPathOnDagTest, NonExistingDestinationBecauseNegative) {
EXPECT_DEATH(
KShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, 0.0}},
/*source=*/0, /*destination=*/-1, /*path_count=*/2),
"Node must be nonnegative");
}
TEST(KShortestPathOnDagTest, NonExistingDestinationBecauseTooLarge) {
EXPECT_DEATH(
KShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, 0.0}},
/*source=*/0, /*destination=*/3, /*path_count=*/2),
"num_nodes\\(\\)");
}
TEST(KShortestPathOnDagTest, KEqualsZero) {
EXPECT_DEATH(
KShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, 0.0}},
/*source=*/0, /*destination=*/1, /*path_count=*/0),
"path_count must be greater than 0");
}
TEST(KShortestPathOnDagTest, OnlyHasOnePath) {
const int source = 0;
const int destination = 1;
const int a = 2;
const int num_nodes = 3;
const std::vector<ArcWithLength> arcs_with_length = {{source, a, 1.0},
{a, destination, 1.0}};
EXPECT_THAT(KShortestPathsOnDag(num_nodes, arcs_with_length, source,
destination, /*path_count=*/2),
ElementsAre(FieldsAre(
/*length=*/2.0, /*arc_path=*/ElementsAre(0, 1),
/*node_path=*/ElementsAre(source, a, destination))));
}
TEST(KShortestPathOnDagTest, SourceIsDestination) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
const std::vector<ArcWithLength> arcs_with_length = {
{source, destination, 1.0}};
EXPECT_THAT(KShortestPathsOnDag(num_nodes, arcs_with_length, source, source,
/*path_count=*/2),
ElementsAre(FieldsAre(
/*length=*/0.0, /*arc_path=*/IsEmpty(),
/*node_path=*/ElementsAre(source))));
}
TEST(KShortestPathOnDagTest, HasTwoPaths) {
const int source = 0;
const int a = 1;
const int destination = 2;
const int num_nodes = 3;
const std::vector<ArcWithLength> arcs_with_length = {
{source, a, 1.0}, {source, destination, 30.0}, {a, destination, 1.0}};
EXPECT_THAT(
KShortestPathsOnDag(num_nodes, arcs_with_length, source, destination,
/*path_count=*/3),
ElementsAre(FieldsAre(
/*length=*/2.0, /*arc_path=*/ElementsAre(0, 2),
/*node_path=*/ElementsAre(source, a, destination)),
FieldsAre(
/*length=*/30.0, /*arc_path=*/ElementsAre(1),
/*node_path=*/ElementsAre(source, destination))));
}
TEST(KShortestPathOnDagTest, HasTwoPathsWithLongerPath) {
const int source = 0;
const int a = 1;
const int b = 2;
const int c = 3;
const int destination = 4;
const int num_nodes = 5;
const std::vector<ArcWithLength> arcs_with_length = {
{source, a, 1.0},
{source, destination, 30.0},
{a, b, 1.0},
{b, c, 1.0},
{c, destination, 1.0}};
EXPECT_THAT(
KShortestPathsOnDag(num_nodes, arcs_with_length, source, destination,
/*path_count=*/3),
ElementsAre(FieldsAre(
/*length=*/4.0, /*arc_path=*/ElementsAre(0, 2, 3, 4),
/*node_path=*/ElementsAre(source, a, b, c, destination)),
FieldsAre(
/*length=*/30.0, /*arc_path=*/ElementsAre(1),
/*node_path=*/ElementsAre(source, destination))));
}
TEST(KShortestPathOnDagTest, 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<ArcWithLength> arcs_with_length = {
{a, c, 5.0}, {source, b, 7.0}, {a, b, 1.0},
{source, a, 3.0}, {c, destination, 5.0}, {b, destination, -2.0}};
EXPECT_THAT(
KShortestPathsOnDag(num_nodes, arcs_with_length, source, destination,
/*path_count=*/2),
ElementsAre(
FieldsAre(/*length=*/2.0, /*arc_path=*/ElementsAre(3, 2, 5),
/*node_path=*/ElementsAre(source, a, b, destination)),
FieldsAre(/*length=*/5.0, /*arc_path=*/ElementsAre(1, 5),
/*node_path=*/ElementsAre(source, b, destination))));
}
TEST(KShortestPathOnDagTest, SetsNotConnected) {
const int source = 0;
const int destination = 1;
const int a = 2;
const int num_nodes = 3;
const std::vector<ArcWithLength> arcs_with_length = {{source, a, 1.0}};
EXPECT_THAT(KShortestPathsOnDag(num_nodes, arcs_with_length, source,
destination, /*path_count=*/2),
ElementsAre(FieldsAre(/*length=*/kInf, /*arc_path=*/IsEmpty(),
/*node_path=*/IsEmpty())));
}
TEST(KShortestPathOnDagTest, TwoConnectedComponents) {
const int a = 0;
const int b = 1;
const int c = 2;
const int d = 3;
const int e = 4;
const int num_nodes = 5;
const std::vector<ArcWithLength> arcs_with_length = {
{a, b, 0.0}, {b, c, 0.0}, {d, e, 0.0}};
EXPECT_THAT(KShortestPathsOnDag(num_nodes, arcs_with_length, /*source=*/a,
/*destination=*/e, /*path_count=*/2),
ElementsAre(FieldsAre(/*length=*/kInf, /*arc_path=*/IsEmpty(),
/*node_path=*/IsEmpty())));
EXPECT_THAT(KShortestPathsOnDag(num_nodes, arcs_with_length, /*source=*/b,
/*destination=*/d, /*path_count=*/2),
ElementsAre(FieldsAre(/*length=*/kInf, /*arc_path=*/IsEmpty(),
/*node_path=*/IsEmpty())));
}
TEST(KShortestPathOnDagTest, SetsNotConnectedDueToInfiniteCost) {
const int source = 0;
const int destination = 1;
const int a = 2;
const int num_nodes = 3;
const std::vector<ArcWithLength> arcs_with_length = {{a, destination, 1.0},
{source, a, kInf}};
EXPECT_THAT(KShortestPathsOnDag(num_nodes, arcs_with_length, source,
destination, /*path_count=*/2),
ElementsAre(FieldsAre(/*length=*/kInf, /*arc_path=*/IsEmpty(),
/*node_path=*/IsEmpty())));
}
TEST(KShortestPathOnDagTest, AvoidInfiniteCost) {
const int source = 0;
const int destination = 1;
const int a = 2;
const int b = 3;
const int num_nodes = 4;
const std::vector<ArcWithLength> arcs_with_length = {{a, destination, 1.0},
{b, destination, 1.0},
{source, a, kInf},
{source, b, 3.0}};
EXPECT_THAT(KShortestPathsOnDag(num_nodes, arcs_with_length, source,
destination, /*path_count=*/2),
ElementsAre(FieldsAre(
/*length=*/4.0, /*arc_path=*/ElementsAre(3, 1),
/*node_path=*/ElementsAre(source, b, destination))));
}
TEST(KShortestPathOnDagTest, SourceNotFirst) {
const int destination = 0;
const int source = 1;
const int num_nodes = 2;
const std::vector<ArcWithLength> arcs_with_length = {
{source, destination, 1.0}};
EXPECT_THAT(KShortestPathsOnDag(num_nodes, arcs_with_length, source,
destination, /*path_count=*/2),
ElementsAre(FieldsAre(
/*length=*/1.0, /*arc_path=*/ElementsAre(0),
/*node_path=*/ElementsAre(source, destination))));
}
TEST(KShortestPathOnDagTest, MultipleArcs) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
const std::vector<ArcWithLength> arcs_with_length = {
{source, destination, 4.0},
{source, destination, 3.0},
{source, destination, -2.0},
{source, destination, 0.0}};
EXPECT_THAT(KShortestPathsOnDag(num_nodes, arcs_with_length, source,
destination, /*path_count=*/3),
ElementsAre(FieldsAre(
/*length=*/-2.0, /*arc_path=*/ElementsAre(2),
/*node_path=*/ElementsAre(source, destination)),
FieldsAre(
/*length=*/0.0, /*arc_path=*/ElementsAre(3),
/*node_path=*/ElementsAre(source, destination)),
FieldsAre(
/*length=*/3.0, /*arc_path=*/ElementsAre(1),
/*node_path=*/ElementsAre(source, destination))));
}
TEST(KShortestPathOnDagTest, UpdateCost) {
const int source = 0;
const int destination = 1;
const int a = 2;
const int b = 3;
const int num_nodes = 4;
std::vector<ArcWithLength> arcs_with_length = {{source, a, 5.0},
{source, b, 2.0},
{a, destination, 3.0},
{b, destination, 20.0}};
EXPECT_THAT(
KShortestPathsOnDag(num_nodes, arcs_with_length, source, destination,
/*path_count=*/2),
ElementsAre(FieldsAre(
/*length=*/8.0, /*arc_path=*/ElementsAre(0, 2),
/*node_path=*/ElementsAre(source, a, destination)),
FieldsAre(
/*length=*/22.0, /*arc_path=*/ElementsAre(1, 3),
/*node_path=*/ElementsAre(source, b, destination))));
// Update the length of arc b -> destination from 20.0 to -1.0.
arcs_with_length[3].length = -1.0;
EXPECT_THAT(
KShortestPathsOnDag(num_nodes, arcs_with_length, source, destination,
/*path_count=*/2),
ElementsAre(FieldsAre(
/*length=*/1.0, /*arc_path=*/ElementsAre(1, 3),
/*node_path=*/ElementsAre(source, b, destination)),
FieldsAre(
/*length=*/8.0, /*arc_path=*/ElementsAre(0, 2),
/*node_path=*/ElementsAre(source, a, destination))));
}
TEST(KShortestPathsOnDagWrapperTest, MultipleSources) {
const int source_1 = 0;
const int source_2 = 1;
const int destination = 2;
const int num_nodes = 3;
util::ListGraph<> graph(num_nodes, /*arc_capacity=*/2);
std::vector<double> arc_lengths;
graph.AddArc(source_1, destination);
arc_lengths.push_back(-6.0);
graph.AddArc(source_2, destination);
arc_lengths.push_back(3.0);
const std::vector<int> topological_order = {source_2, source_1, destination};
const int path_count = 2;
KShortestPathsOnDagWrapper<util::ListGraph<>> shortest_paths_on_dag(
&graph, &arc_lengths, topological_order, path_count);
shortest_paths_on_dag.RunKShortestPathOnDag({source_1, source_2});
EXPECT_TRUE(shortest_paths_on_dag.IsReachable(destination));
EXPECT_THAT(shortest_paths_on_dag.LengthsTo(destination),
ElementsAre(-6.0, 3.0));
EXPECT_THAT(shortest_paths_on_dag.ArcPathsTo(destination),
ElementsAre(ElementsAre(0), ElementsAre(1)));
EXPECT_THAT(shortest_paths_on_dag.NodePathsTo(destination),
ElementsAre(ElementsAre(source_1, destination),
ElementsAre(source_2, destination)));
}
TEST(KShortestPathsOnDagWrapperTest, ShortestPathGoesThroughMultipleSources) {
const int source_1 = 0;
const int source_2 = 1;
const int destination = 2;
const int num_nodes = 3;
util::ListGraph<> graph(num_nodes, /*arc_capacity=*/3);
std::vector<double> arc_lengths;
graph.AddArc(source_1, source_2);
arc_lengths.push_back(-7.0);
graph.AddArc(source_2, destination);
arc_lengths.push_back(3.0);
graph.AddArc(source_1, destination);
arc_lengths.push_back(5.0);
const std::vector<int> topological_order = {source_1, source_2, destination};
const int path_count = 2;
KShortestPathsOnDagWrapper<util::ListGraph<>> shortest_paths_on_dag(
&graph, &arc_lengths, topological_order, path_count);
shortest_paths_on_dag.RunKShortestPathOnDag({source_1, source_2});
EXPECT_TRUE(shortest_paths_on_dag.IsReachable(destination));
EXPECT_THAT(shortest_paths_on_dag.LengthsTo(destination),
ElementsAre(-4.0, 3.0));
EXPECT_THAT(shortest_paths_on_dag.ArcPathsTo(destination),
ElementsAre(ElementsAre(0, 1), ElementsAre(1)));
EXPECT_THAT(shortest_paths_on_dag.NodePathsTo(destination),
ElementsAre(ElementsAre(source_1, source_2, destination),
ElementsAre(source_2, destination)));
}
TEST(KShortestPathsOnDagWrapperTest, MultipleDestinations) {
const int source = 0;
const int destination_1 = 1;
const int destination_2 = 2;
const int destination_3 = 3;
const int num_nodes = 4;
util::ListGraph<> graph(num_nodes, /*arc_capacity=*/3);
std::vector<double> arc_lengths;
graph.AddArc(source, destination_1);
arc_lengths.push_back(3.0);
graph.AddArc(source, destination_2);
arc_lengths.push_back(1.0);
graph.AddArc(source, destination_3);
arc_lengths.push_back(2.0);
const std::vector<int> topological_order = {source, destination_3,
destination_1, destination_2};
const int path_count = 2;
KShortestPathsOnDagWrapper<util::ListGraph<>> shortest_paths_on_dag(
&graph, &arc_lengths, topological_order, path_count);
shortest_paths_on_dag.RunKShortestPathOnDag({source});
EXPECT_TRUE(shortest_paths_on_dag.IsReachable(destination_1));
EXPECT_THAT(shortest_paths_on_dag.LengthsTo(destination_1)[0], 3.0);
EXPECT_THAT(shortest_paths_on_dag.ArcPathsTo(destination_1)[0],
ElementsAre(0));
EXPECT_THAT(shortest_paths_on_dag.NodePathsTo(destination_1)[0],
ElementsAre(source, destination_1));
EXPECT_TRUE(shortest_paths_on_dag.IsReachable(destination_2));
EXPECT_THAT(shortest_paths_on_dag.LengthsTo(destination_2)[0], 1.0);
EXPECT_THAT(shortest_paths_on_dag.ArcPathsTo(destination_2)[0],
ElementsAre(1));
EXPECT_THAT(shortest_paths_on_dag.NodePathsTo(destination_2)[0],
ElementsAre(source, destination_2));
EXPECT_TRUE(shortest_paths_on_dag.IsReachable(destination_3));
EXPECT_THAT(shortest_paths_on_dag.LengthsTo(destination_3)[0], 2.0);
EXPECT_THAT(shortest_paths_on_dag.ArcPathsTo(destination_3)[0],
ElementsAre(2));
EXPECT_THAT(shortest_paths_on_dag.NodePathsTo(destination_3)[0],
ElementsAre(source, destination_3));
}
TEST(KShortestPathsOnDagWrapperTest, RandomizedStressTest) {
absl::BitGen bit_gen;
const int kNumTests = 10000;
for (int test = 0; test < kNumTests; ++test) {
const int num_nodes = absl::Uniform(bit_gen, 2, 12);
const int num_arcs = absl::Uniform(
bit_gen, 1, std::min(num_nodes * (num_nodes - 1) / 2, 15));
// Generate a random DAG with random lengths.
const auto [graph, topological_order] = BuildRandomDag(num_nodes, num_arcs);
const std::vector<double> arc_lengths = GenerateRandomLengths(graph);
ShortestPathsOnDagWrapper<util::StaticGraph<>> shortest_path_on_dag(
&graph, &arc_lengths, topological_order);
const int path_count = 5;
KShortestPathsOnDagWrapper<util::StaticGraph<>> shortest_paths_on_dag(
&graph, &arc_lengths, topological_order, path_count);
for (int _ = 0; _ < 20; ++_) {
// Draw sources (*with* repetition) with initial distances.
const int num_sources = absl::Uniform(bit_gen, 1, 5);
std::vector<int> sources(num_sources);
for (int& source : sources) {
source = absl::Uniform(bit_gen, 0, num_nodes);
}
// Compute the number of paths
std::vector<int> all_paths_count(num_nodes);
for (const int source : sources) {
all_paths_count[source] = 1;
}
for (const int from : topological_order) {
for (const int arc : graph.OutgoingArcs(from)) {
const int to = graph.Head(arc);
all_paths_count[to] += all_paths_count[from];
}
}
shortest_path_on_dag.RunShortestPathOnDag(sources);
shortest_paths_on_dag.RunKShortestPathOnDag(sources);
for (const int node : shortest_path_on_dag.reached_nodes()) {
EXPECT_TRUE(shortest_paths_on_dag.IsReachable(node));
EXPECT_EQ(shortest_paths_on_dag.LengthsTo(node)[0],
shortest_path_on_dag.LengthTo(node))
<< node;
EXPECT_EQ(shortest_paths_on_dag.LengthsTo(node).size(),
std::min(all_paths_count[node], path_count))
<< node;
}
ASSERT_FALSE(HasFailure())
<< DUMP_VARS(num_nodes, num_arcs, num_sources, sources, arc_lengths)
<< "\n With graph:\n"
<< util::GraphToString(graph, util::PRINT_GRAPH_ARCS);
}
}
}
// Debug tests.
#ifndef NDEBUG
TEST(KShortestPathOnDagTest, MinusInfWeight) {
EXPECT_DEATH(
KShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, -kInf}},
/*source=*/0, /*destination=*/1, /*path_count=*/2),
"-inf");
}
TEST(KShortestPathOnDagTest, NaNWeight) {
EXPECT_DEATH(
KShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/
{{0, 1, std::numeric_limits<double>::quiet_NaN()}},
/*source=*/0, /*destination=*/1, /*path_count=*/2),
"NaN");
}
TEST(KShortestPathsOnDagWrapperTest, ValidateTopologicalOrder) {
const int source = 0;
const int destination = 1;
const int num_nodes = 2;
util::ListGraph<> graph(num_nodes, /*arc_capacity=*/1);
std::vector<double> arc_lengths;
graph.AddArc(source, destination);
arc_lengths.push_back(1.0);
const std::vector<int> topological_order = {source};
const int path_count = 2;
EXPECT_DEATH(KShortestPathsOnDagWrapper<util::ListGraph<>>(
&graph, &arc_lengths, topological_order, path_count),
"Invalid topological order");
}
#endif // NDEBUG
// -----------------------------------------------------------------------------
// KShortestPathsOnDagWrapper benchmarks.
// -----------------------------------------------------------------------------
void BM_RandomDag_K(benchmark::State& state) {
absl::BitGen bit_gen;
// Generate a fixed random DAG.
const int num_nodes = state.range(0);
const int num_arcs = num_nodes * state.range(1);
const int path_count = state.range(2);
const auto [graph, topological_order] = BuildRandomDag(num_nodes, num_arcs);
// Generate at most 20 scenarios of random arc lengths.
const int num_scenarios = std::min(20, (int)state.iterations());
std::vector<std::vector<double>> arc_lengths_scenarios;
for (int _ = 0; _ < num_scenarios; ++_) {
arc_lengths_scenarios.push_back(GenerateRandomLengths(graph));
}
std::vector<double> arc_lengths = arc_lengths_scenarios.front();
KShortestPathsOnDagWrapper<util::StaticGraph<>> shortest_paths_on_dag(
&graph, &arc_lengths, topological_order, path_count);
for (auto _ : state) {
// Pick a arc lengths scenario at random.
arc_lengths =
arc_lengths_scenarios[absl::Uniform(bit_gen, 0, num_scenarios)];
shortest_paths_on_dag.RunKShortestPathOnDag({0});
CHECK(shortest_paths_on_dag.IsReachable(num_nodes - 1));
const std::vector<double> lengths =
shortest_paths_on_dag.LengthsTo(num_nodes - 1);
CHECK_GE(lengths[0], 0.0);
CHECK_LE(lengths[0], 10000.0);
}
state.SetItemsProcessed(state.iterations() * (num_nodes + num_arcs));
}
BENCHMARK(BM_RandomDag_K)
->Args({1000, 10, 4})
->Args({1 << 16, 4, 4})
->Args({1 << 16, 4, 16})
->Args({1 << 16, 16, 4})
->Args({1 << 16, 16, 16})
->Args({1 << 22, 4, 4})
->Args({1 << 22, 4, 16});
} // namespace
} // namespace operations_research