graph: export from google3
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user