From 7096031050178bd6749718424d1c59db613c8579 Mon Sep 17 00:00:00 2001 From: Corentin Le Molgat Date: Mon, 16 Jun 2025 14:54:04 +0200 Subject: [PATCH] graph: export from google3 dump_vars: Add support for StrongInt and StrongVector --- ortools/base/BUILD.bazel | 4 + ortools/base/dump_vars.h | 11 + ortools/base/dump_vars_test.cc | 18 + ortools/graph/BUILD.bazel | 7 +- ortools/graph/bounded_dijkstra.h | 252 ++++---- ortools/graph/bounded_dijkstra_test.cc | 542 ++++++++++-------- ortools/graph/graph.h | 50 +- ortools/graph/graph_io.h | 4 +- ortools/graph/graph_test.cc | 240 +++++--- .../assignment_linear_sum_assignment.py | 1 + ortools/graph/samples/assignment_min_flow.py | 1 + ortools/graph/samples/balance_min_flow.py | 1 + ortools/graph/samples/dijkstra_directed.cc | 4 +- ortools/graph/samples/dijkstra_undirected.cc | 4 +- .../graph/samples/simple_max_flow_program.py | 1 + .../samples/simple_min_cost_flow_program.py | 1 + 16 files changed, 674 insertions(+), 467 deletions(-) diff --git a/ortools/base/BUILD.bazel b/ortools/base/BUILD.bazel index a6ac333dd8..c118a14e85 100644 --- a/ortools/base/BUILD.bazel +++ b/ortools/base/BUILD.bazel @@ -183,6 +183,8 @@ cc_library( "//conditions:default": [], }), deps = [ + ":strong_int", + ":strong_vector", "@abseil-cpp//absl/container:inlined_vector", ], ) @@ -199,6 +201,8 @@ cc_test( }), deps = [ ":dump_vars", + ":strong_int", + ":strong_vector", "@abseil-cpp//absl/strings", "@googletest//:gtest_main", ], diff --git a/ortools/base/dump_vars.h b/ortools/base/dump_vars.h index 8413948cd3..61e6073084 100644 --- a/ortools/base/dump_vars.h +++ b/ortools/base/dump_vars.h @@ -48,6 +48,8 @@ #include #include "absl/container/inlined_vector.h" +#include "ortools/base/strong_int.h" +#include "ortools/base/strong_vector.h" /* need extra level to force extra eval */ #define DUMP_FOR_EACH_N0(F) @@ -138,6 +140,15 @@ std::ostream& operator<<(std::ostream& os, const ::std::optional& opt) { return os; } +// needed by graph tests +template +std::ostream& operator<<(std::ostream& os, const ::util_intops::StrongVector& vec) { + for (U it : vec) { + os << ::std::to_string(it) << ','; + } + return os; +} + using DumpNames = ::std::vector<::std::string>; struct print_fields { diff --git a/ortools/base/dump_vars_test.cc b/ortools/base/dump_vars_test.cc index 1a295f386a..2dccc6381d 100644 --- a/ortools/base/dump_vars_test.cc +++ b/ortools/base/dump_vars_test.cc @@ -21,6 +21,12 @@ #include #include "gtest/gtest.h" +#include "ortools/base/strong_int.h" +#include "ortools/base/strong_vector.h" + +namespace util_intops { +DEFINE_STRONG_INT_TYPE(CustomStrongInt, uint32_t); +} // namespace util_intops namespace operations_research::base { namespace { @@ -124,6 +130,18 @@ TEST(DumpVars, Vector) { EXPECT_EQ("vec = 49.299999,3.140000,", DUMP_VARS(vec).str()); } +TEST(DumpVars, StrongInt) { + ::util_intops::CustomStrongInt val(42); + EXPECT_EQ(R"(val = 42)", ToString(DUMP_VARS(val))); + EXPECT_EQ(R"(val = 42)", DUMP_VARS(val).str()); +} + +TEST(DumpVars, StrongVector) { + ::util_intops::StrongVector<::util_intops::CustomStrongInt, float> vec = {49.3, 3.14}; + EXPECT_EQ(R"(vec = 49.299999,3.140000,)", ToString(DUMP_VARS(vec))); + EXPECT_EQ(R"(vec = 49.299999,3.140000,)", DUMP_VARS(vec).str()); +} + TEST(DumpVars, Optional) { std::optional of = {}; EXPECT_EQ("of = (none)", ToString(DUMP_VARS(of))); diff --git a/ortools/graph/BUILD.bazel b/ortools/graph/BUILD.bazel index d8d0a5c07d..fac523588a 100644 --- a/ortools/graph/BUILD.bazel +++ b/ortools/graph/BUILD.bazel @@ -52,6 +52,8 @@ cc_test( ":graph", "//ortools/base:gmock_main", "//ortools/base:intops", + "//ortools/base:strong_vector", + "@abseil-cpp//absl/algorithm:container", "@abseil-cpp//absl/log:check", "@abseil-cpp//absl/random", "@abseil-cpp//absl/strings", @@ -86,7 +88,9 @@ cc_library( hdrs = ["bounded_dijkstra.h"], deps = [ ":graph", + "//ortools/base:intops", "//ortools/base:iterator_adaptors", + "//ortools/base:strong_vector", "//ortools/base:threadpool", "//ortools/base:top_n", "@abseil-cpp//absl/algorithm:container", @@ -107,6 +111,7 @@ cc_test( ":test_util", "//ortools/base:dump_vars", "//ortools/base:gmock_main", + "//ortools/base:intops", "//ortools/util:flat_matrix", "@abseil-cpp//absl/log:check", "@abseil-cpp//absl/random", @@ -858,7 +863,7 @@ cc_test( deps = [ ":iterators", "//ortools/base:gmock_main", - "//ortools/base:strong_int", + "//ortools/base:intops", ], ) diff --git a/ortools/graph/bounded_dijkstra.h b/ortools/graph/bounded_dijkstra.h index e4522e5d90..98a7fc7e5f 100644 --- a/ortools/graph/bounded_dijkstra.h +++ b/ortools/graph/bounded_dijkstra.h @@ -15,8 +15,10 @@ #define OR_TOOLS_GRAPH_BOUNDED_DIJKSTRA_H_ #include +#include #include #include +#include #include #include @@ -25,6 +27,8 @@ #include "absl/log/check.h" #include "absl/types/span.h" #include "ortools/base/iterator_adaptors.h" +#include "ortools/base/strong_int.h" +#include "ortools/base/strong_vector.h" #include "ortools/base/top_n.h" #include "ortools/graph/graph.h" @@ -54,22 +58,40 @@ namespace operations_research { // is >= limit we will return {limit, {}}. As a consequence any arc length >= // limit is the same as no arc. The code is also overflow-safe and will behave // correctly if the limit is int64max or infinity. -template -std::pair> SimpleOneToOneShortestPath( - int source, int destination, absl::Span tails, - absl::Span heads, absl::Span lengths, +template +std::pair> SimpleOneToOneShortestPath( + NodeIndex source, NodeIndex destination, absl::Span tails, + absl::Span heads, absl::Span lengths, DistanceType limit = std::numeric_limits::max()); -template +namespace internal { + +// TODO(user): We should move `is_strong_int` to util/intops/strong_int.h. +template +struct is_strong_int : std::false_type {}; + +template +struct is_strong_int<::util_intops::StrongInt> + : std::true_type {}; + +template +using IndexedVector = + std::conditional_t::value, + ::util_intops::StrongVector, + std::vector>; + +template class ElementGetter { public: - explicit ElementGetter(const std::vector& c) : c_(c) {} - const T& operator()(int index) const { return c_[index]; } + explicit ElementGetter(const IndexedVector& c) : c_(c) {} + const T& operator()(ArcIndex index) const { return c_[index]; } private: - const std::vector& c_; + const IndexedVector& c_; }; +} // namespace internal + // A wrapper that holds the memory needed to run many bounded shortest path // computations on the given graph. The graph must implement the // interface described in graph.h (without the need for reverse arcs). @@ -92,12 +114,20 @@ class ElementGetter { // negative source_offset, arc with a length greater than the distance_limit can // still be considered! template > + class ArcLengthFunctor = internal::ElementGetter< + DistanceType, typename GraphType::ArcIndex>> class BoundedDijkstraWrapper { public: - typedef typename GraphType::NodeIndex node_type; + typedef typename GraphType::NodeIndex NodeIndex; + typedef typename GraphType::ArcIndex ArcIndex; typedef DistanceType distance_type; + // A vector of T, indexed by NodeIndex/ArcIndex. + template + using ByNode = internal::IndexedVector; + template + using ByArc = internal::IndexedVector; + // IMPORTANT: Both arguments must outlive the class. The arc lengths cannot be // negative and the vector must be of the correct size (both preconditions are // CHECKed). @@ -106,7 +136,7 @@ class BoundedDijkstraWrapper { // RunBoundedDijkstra(). That's fine. Doing so will obviously invalidate the // reader API of the last Dijkstra run, which could return junk, or crash. BoundedDijkstraWrapper(const GraphType* graph, - const std::vector* arc_lengths); + const ByArc* arc_lengths); // Variant that takes a custom arc length functor and copies it locally. BoundedDijkstraWrapper(const GraphType* graph, @@ -116,8 +146,8 @@ class BoundedDijkstraWrapper { // of the graph within the distance limit (exclusive). The first element of // the returned vector will always be the source_node with a distance of zero. // See RunBoundedDijkstraFromMultipleSources() for more information. - const std::vector& RunBoundedDijkstra(int source_node, - DistanceType distance_limit) { + const std::vector& RunBoundedDijkstra( + NodeIndex source_node, DistanceType distance_limit) { return RunBoundedDijkstraFromMultipleSources({{source_node, 0}}, distance_limit); } @@ -127,7 +157,8 @@ class BoundedDijkstraWrapper { // // If this returns true, you can get the path distance with distances()[to] // and the path with ArcPathTo(to) or NodePathTo(to). - bool OneToOneShortestPath(int from, int to, DistanceType distance_limit); + bool OneToOneShortestPath(NodeIndex from, NodeIndex to, + DistanceType distance_limit); // Returns the list of all the nodes which are under the given distance limit // (exclusive) from at least one of the given source nodes (which also have @@ -136,8 +167,8 @@ class BoundedDijkstraWrapper { // By "distance", we mean the length of the shortest path from any source // plus the source's distance offset, where the length of a path is the // sum of the length of its arcs - const std::vector& RunBoundedDijkstraFromMultipleSources( - const std::vector>& + const std::vector& RunBoundedDijkstraFromMultipleSources( + const std::vector>& sources_with_distance_offsets, DistanceType distance_limit); @@ -162,10 +193,11 @@ class BoundedDijkstraWrapper { // // Note that the distances() will take the source offsets into account, // but not the destination offsets. - std::vector RunBoundedDijkstraFromMultipleSourcesToMultipleDestinations( - const std::vector>& + std::vector + RunBoundedDijkstraFromMultipleSourcesToMultipleDestinations( + const std::vector>& sources_with_distance_offsets, - const std::vector>& + const std::vector>& destinations_with_distance_offsets, int num_destinations_to_reach, DistanceType distance_limit); @@ -174,19 +206,19 @@ class BoundedDijkstraWrapper { // happens at most once per node, when popping it from the Dijkstra queue, // meaning that the node has been fully 'processed'). This callback may modify // the distance limit dynamically, thus affecting the stopping criterion. - const std::vector& RunBoundedDijkstraWithSettledNodeCallback( - const std::vector>& + const std::vector& RunBoundedDijkstraWithSettledNodeCallback( + const std::vector>& sources_with_distance_offsets, - std::function settled_node_callback, DistanceType distance_limit); // Returns true if `node` was reached by the last Run*() call. - bool IsReachable(int node) const { return is_reached_[node]; } + bool IsReachable(NodeIndex node) const { return is_reached_[node]; } // Returns all the reached nodes form the previous Run*() call. - const std::vector& reached_nodes() const { return reached_nodes_; } + const ByNode& reached_nodes() const { return reached_nodes_; } // The following vectors are all indexed by graph node indices. // @@ -194,7 +226,7 @@ class BoundedDijkstraWrapper { // reached nodes are updated, the others will contain junk. // The distance of the nodes from their source. - const std::vector& distances() const { return distances_; } + const ByNode& distances() const { return distances_; } // The parent of the nodes in the shortest path from their source. // When a node doesn't have any parent (it has to be a source), its parent @@ -203,27 +235,29 @@ class BoundedDijkstraWrapper { // arcs have a length of zero. // Note also that some sources may have parents, because of the initial // distances. - const std::vector& parents() const { return parents_; } + const ByNode& parents() const { return parents_; } // The arc reaching a given node in the path from their source. // arc_from_source()[x] is undefined (i.e. junk) when parents()[x] == x. - const std::vector& arc_from_source() const { return arc_from_source_; } + const ByNode& arc_from_source() const { return arc_from_source_; } // Returns the list of all the arcs in the shortest path from the node's // source to the node. - std::vector ArcPathTo(int node) const; + std::vector ArcPathTo(NodeIndex node) const; ABSL_DEPRECATED("Use ArcPathTo() instead.") - std::vector ArcPathToNode(int node) const { return ArcPathTo(node); } + std::vector ArcPathToNode(NodeIndex node) const { + return ArcPathTo(node); + } // Returns the list of all the nodes in the shortest path from the node's // source to the node. This always start by the node's source, and end by // the given node. In the case that source == node, returns {node}. - std::vector NodePathTo(int node) const; + std::vector NodePathTo(NodeIndex node) const; // Returns the node's source. This is especially useful when running // Dijkstras from multiple sources. - int SourceOfShortestPathToNode(int node) const; + NodeIndex SourceOfShortestPathToNode(NodeIndex node) const; // Original Source/Destination index extraction, after a call to the // multi-source and/or multi-destination variants: @@ -239,16 +273,16 @@ class BoundedDijkstraWrapper { // rely on the value. // // These methods are invalidated by the next RunBoundedDijkstra*() call. - int GetSourceIndex(int node) const; - int GetDestinationIndex(int node) const; + int GetSourceIndex(NodeIndex node) const; + int GetDestinationIndex(NodeIndex node) const; // Trivial accessors to the underlying graph and arc lengths. const GraphType& graph() const { return *graph_; } - const std::vector& arc_lengths() const { + const ByArc& arc_lengths() const { CHECK(arc_lengths_); return *arc_lengths_; } - DistanceType GetArcLength(int arc) const { + DistanceType GetArcLength(ArcIndex arc) const { const DistanceType length = arc_length_functor_(arc); DCHECK_GE(length, 0); return length; @@ -262,18 +296,18 @@ class BoundedDijkstraWrapper { // The Graph and length of each arc. const GraphType* const graph_; ArcLengthFunctor arc_length_functor_; - const std::vector* const arc_lengths_; + const ByArc* const arc_lengths_; // Data about the last Dijkstra run. - std::vector distances_; - std::vector parents_; - std::vector arc_from_source_; - std::vector is_reached_; - std::vector reached_nodes_; + ByNode distances_; + ByNode parents_; + ByNode arc_from_source_; + ByNode is_reached_; + std::vector reached_nodes_; // Priority queue of nodes, ordered by their distance to the source. struct NodeDistance { - node_type node; // The target node. + NodeIndex node; // The target node. DistanceType distance; // Its distance from the source. bool operator<(const NodeDistance& other) const { @@ -287,7 +321,7 @@ class BoundedDijkstraWrapper { // or ieee754 floating-point, when the machine is little endian, and // when the total size of NodeDistance equals 16 bytes). // And here are the speeds of the BM_GridGraph benchmark (in which - // DistanceType=int64_t and node_type=int32_t), done with benchy + // DistanceType=int64_t and NodeIndex=int32_t), done with benchy // --runs=20: 0) BM_GridGraph 9.22ms ± 5% BM_GridGraph 3.19ms // ± 6% 1) BM_GridGraph 8.89ms ± 4% BM_GridGraph 3.07ms ± // 3% 2) BM_GridGraph 8.61ms ± 3% BM_GridGraph 3.13ms ± 6% @@ -303,8 +337,8 @@ class BoundedDijkstraWrapper { // The vectors are only allocated after they are first used. // Between calls, is_destination_ is all false, and the rest is junk. std::vector is_destination_; - std::vector node_to_source_index_; - std::vector node_to_destination_index_; + ByNode node_to_source_index_; + ByNode node_to_destination_index_; }; // ----------------------------------------------------------------------------- @@ -314,12 +348,12 @@ class BoundedDijkstraWrapper { template BoundedDijkstraWrapper:: BoundedDijkstraWrapper(const GraphType* graph, - const std::vector* arc_lengths) + const ByArc* arc_lengths) : graph_(graph), arc_length_functor_(*arc_lengths), arc_lengths_(arc_lengths) { CHECK(arc_lengths_ != nullptr); - CHECK_EQ(arc_lengths_->size(), graph->num_arcs()); + CHECK_EQ(ArcIndex(arc_lengths_->size()), graph->num_arcs()); for (const DistanceType length : *arc_lengths) { CHECK_GE(length, 0); } @@ -341,10 +375,10 @@ BoundedDijkstraWrapper:: arc_lengths_(other.arc_lengths_) {} template -const std::vector& +const std::vector& BoundedDijkstraWrapper:: RunBoundedDijkstraFromMultipleSources( - const std::vector>& + const std::vector>& sources_with_distance_offsets, DistanceType distance_limit) { return RunBoundedDijkstraWithSettledNodeCallback( @@ -352,12 +386,12 @@ BoundedDijkstraWrapper:: } template -std::vector +std::vector BoundedDijkstraWrapper:: RunBoundedDijkstraFromMultipleSourcesToMultipleDestinations( - const std::vector>& + const std::vector>& sources_with_distance_offsets, - const std::vector>& + const std::vector>& destinations_with_distance_offsets, int num_destinations_to_reach, DistanceType distance_limit) { if (destinations_with_distance_offsets.empty()) return {}; @@ -368,22 +402,22 @@ BoundedDijkstraWrapper:: // to reduce the search space. DCHECK_GE(num_destinations_to_reach, 0); int num_destinations = 0; - is_destination_.resize(graph_->num_nodes(), false); + is_destination_.resize(static_cast(graph_->num_nodes()), false); node_to_destination_index_.resize(graph_->num_nodes(), -1); DistanceType min_destination_distance_offset = destinations_with_distance_offsets[0].second; for (int i = 0; i < destinations_with_distance_offsets.size(); ++i) { - const int node = destinations_with_distance_offsets[i].first; + const NodeIndex node = destinations_with_distance_offsets[i].first; const DistanceType distance = destinations_with_distance_offsets[i].second; - if (!is_destination_[node]) ++num_destinations; + if (!is_destination_[static_cast(node)]) ++num_destinations; // Skip useless repetitions. - if (is_destination_[node] && + if (is_destination_[static_cast(node)] && distance >= destinations_with_distance_offsets[node_to_destination_index_[node]] .second) { continue; } - is_destination_[node] = true; + is_destination_[static_cast(node)] = true; node_to_destination_index_[node] = i; min_destination_distance_offset = std::min(min_destination_distance_offset, distance); @@ -395,13 +429,13 @@ BoundedDijkstraWrapper:: gtl::TopN> closest_destinations( /*limit=*/num_destinations_to_reach); - std::function + std::function settled_node_callback = [this, num_destinations_to_reach, min_destination_distance_offset, &destinations_with_distance_offsets, &closest_destinations]( - node_type settled_node, DistanceType settled_distance, + NodeIndex settled_node, DistanceType settled_distance, DistanceType* distance_limit) { - if (!is_destination_[settled_node]) return; + if (!is_destination_[static_cast(settled_node)]) return; const DistanceType distance = settled_distance + destinations_with_distance_offsets @@ -423,12 +457,12 @@ BoundedDijkstraWrapper:: // Clean up, sparsely, for the next call. for (const auto& [node, _] : destinations_with_distance_offsets) { - is_destination_[node] = false; + is_destination_[static_cast(node)] = false; } // Return the closest "num_destinations_to_reach" reached destinations, // sorted by distance. - std::vector sorted_destinations; + std::vector sorted_destinations; sorted_destinations.reserve(closest_destinations.size()); for (const NodeDistance& d : closest_destinations.Take()) { sorted_destinations.push_back(d.node); @@ -438,10 +472,11 @@ BoundedDijkstraWrapper:: template bool BoundedDijkstraWrapper:: - OneToOneShortestPath(int from, int to, DistanceType distance_limit) { + OneToOneShortestPath(NodeIndex from, NodeIndex to, + DistanceType distance_limit) { bool reached = false; - std::function - settled_node_callback = [to, &reached](node_type node, + std::function + settled_node_callback = [to, &reached](NodeIndex node, DistanceType distance, DistanceType* distance_limit) { if (node != to) return; @@ -456,18 +491,18 @@ bool BoundedDijkstraWrapper:: } template -const std::vector& +const std::vector& BoundedDijkstraWrapper:: RunBoundedDijkstraWithSettledNodeCallback( - const std::vector>& + const std::vector>& sources_with_distance_offsets, - std::function settled_node_callback, DistanceType distance_limit) { // Sparse clear is_reached_ from the last call. - for (const int node : reached_nodes_) { + for (const NodeIndex node : reached_nodes_) { is_reached_[node] = false; } reached_nodes_.clear(); @@ -475,15 +510,15 @@ BoundedDijkstraWrapper:: is_reached_.resize(graph_->num_nodes(), false); distances_.resize(graph_->num_nodes(), distance_limit); - parents_.resize(graph_->num_nodes(), std::numeric_limits::min()); - arc_from_source_.resize(graph_->num_nodes(), -1); + parents_.resize(graph_->num_nodes(), std::numeric_limits::min()); + arc_from_source_.resize(graph_->num_nodes(), GraphType::kNilArc); // Initialize sources. CHECK(queue_.empty()); node_to_source_index_.resize(graph_->num_nodes(), -1); for (int i = 0; i < sources_with_distance_offsets.size(); ++i) { - const int node = sources_with_distance_offsets[i].first; - DCHECK_GE(node, 0); + const NodeIndex node = sources_with_distance_offsets[i].first; + DCHECK_GE(node, NodeIndex(0)); DCHECK_LT(node, graph_->num_nodes()); const DistanceType distance = sources_with_distance_offsets[i].second; // Sources with an initial distance ≥ limit are *not* reached. @@ -498,7 +533,7 @@ BoundedDijkstraWrapper:: node_to_source_index_[node] = i; distances_[node] = distance; } - for (const int source : reached_nodes_) { + for (const NodeIndex source : reached_nodes_) { queue_.push_back({source, distances_[source]}); } std::make_heap(queue_.begin(), queue_.end(), std::greater()); @@ -533,7 +568,8 @@ BoundedDijkstraWrapper:: // Visit the neighbors. const DistanceType limit = distance_limit - top.distance; - for (const int arc : graph_->OutgoingArcs(top.node)) { + for (const typename GraphType::ArcIndex arc : + graph_->OutgoingArcs(top.node)) { // Overflow-safe check of top.distance + arc_length >= distance_limit. // This works since we know top.distance < distance_limit, as long as we // don't have negative top.distance (which might happen with negative @@ -543,7 +579,7 @@ BoundedDijkstraWrapper:: if (arc_length >= limit) continue; const DistanceType candidate_distance = top.distance + arc_length; - const int head = graph_->Head(arc); + const NodeIndex head = graph_->Head(arc); if (is_reached_[head]) { if (candidate_distance >= distances_[head]) continue; } else { @@ -563,14 +599,14 @@ BoundedDijkstraWrapper:: } template -std::vector +std::vector BoundedDijkstraWrapper::ArcPathTo( - int node) const { - std::vector output; + NodeIndex node) const { + std::vector output; int loop_detector = 0; while (true) { - DCHECK_GE(node, 0); - DCHECK_LT(node, parents_.size()); + DCHECK_GE(node, NodeIndex(0)); + DCHECK_LT(node, NodeIndex(parents_.size())); CHECK_LT(loop_detector++, parents_.size()); if (parents_[node] == node) break; output.push_back(arc_from_source_[node]); @@ -581,14 +617,14 @@ BoundedDijkstraWrapper::ArcPathTo( } template -std::vector +std::vector BoundedDijkstraWrapper::NodePathTo( - int node) const { - std::vector output; + NodeIndex node) const { + std::vector output; int loop_detector = 0; while (true) { - DCHECK_GE(node, 0); - DCHECK_LT(node, parents_.size()); + DCHECK_GE(node, NodeIndex(0)); + DCHECK_LT(node, NodeIndex(parents_.size())); CHECK_LT(loop_detector++, parents_.size()); output.push_back(node); if (parents_[node] == node) break; @@ -599,27 +635,28 @@ BoundedDijkstraWrapper::NodePathTo( } template -int BoundedDijkstraWrapper:: - SourceOfShortestPathToNode(int node) const { - int parent = node; +typename GraphType::NodeIndex BoundedDijkstraWrapper< + GraphType, DistanceType, + ArcLengthFunctor>::SourceOfShortestPathToNode(NodeIndex node) const { + NodeIndex parent = node; while (parents_[parent] != parent) parent = parents_[parent]; return parent; } template int BoundedDijkstraWrapper::GetSourceIndex(int node) const { - DCHECK_GE(node, 0); - DCHECK_LT(node, node_to_source_index_.size()); + ArcLengthFunctor>::GetSourceIndex(NodeIndex node) + const { + DCHECK_GE(node, NodeIndex(0)); + DCHECK_LT(node, NodeIndex(node_to_source_index_.size())); return node_to_source_index_[node]; } template -int BoundedDijkstraWrapper::GetDestinationIndex(int node) - const { - DCHECK_GE(node, 0); - DCHECK_LT(node, node_to_destination_index_.size()); +int BoundedDijkstraWrapper:: + GetDestinationIndex(NodeIndex node) const { + DCHECK_GE(node, NodeIndex(0)); + DCHECK_LT(node, NodeIndex(node_to_destination_index_.size())); return node_to_destination_index_[node]; } @@ -627,37 +664,38 @@ int BoundedDijkstraWrapper -std::pair> SimpleOneToOneShortestPath( - int source, int destination, absl::Span tails, - absl::Span heads, absl::Span lengths, +template +std::pair> SimpleOneToOneShortestPath( + NodeIndex source, NodeIndex destination, absl::Span tails, + absl::Span heads, absl::Span lengths, DistanceType limit) { + using ArcIndex = NodeIndex; // Compute the number of nodes. // // This is not necessary, but is a good practice to allocate the graph size in // one go. We also do some basic validation. CHECK_GE(source, 0); CHECK_GE(destination, 0); - int num_nodes = std::max(source + 1, destination + 1); - for (const int tail : tails) { + NodeIndex num_nodes = std::max(source + 1, destination + 1); + for (const NodeIndex tail : tails) { CHECK_GE(tail, 0); num_nodes = std::max(tail + 1, num_nodes); } - for (const int head : heads) { + for (const NodeIndex head : heads) { CHECK_GE(head, 0); num_nodes = std::max(head + 1, num_nodes); } // The number of arcs. - const int num_arcs = tails.size(); + const ArcIndex num_arcs = tails.size(); CHECK_EQ(num_arcs, heads.size()); CHECK_EQ(num_arcs, lengths.size()); // Build the graph. Note that this permutes arc indices for speed, but we // don't care here since we will return a node path. - util::StaticGraph<> graph(num_nodes, num_arcs); + util::StaticGraph graph(num_nodes, num_arcs); std::vector arc_lengths(lengths.begin(), lengths.end()); - for (int a = 0; a < num_arcs; ++a) { + for (ArcIndex a = 0; a < num_arcs; ++a) { // Negative length can cause the algo to loop forever and/or use a lot of // memory. So it should be validated. CHECK_GE(lengths[a], 0); diff --git a/ortools/graph/bounded_dijkstra_test.cc b/ortools/graph/bounded_dijkstra_test.cc index 07a21f5a8d..a5f256cce1 100644 --- a/ortools/graph/bounded_dijkstra_test.cc +++ b/ortools/graph/bounded_dijkstra_test.cc @@ -30,6 +30,7 @@ #include "gtest/gtest.h" #include "ortools/base/dump_vars.h" #include "ortools/base/gmock.h" +#include "ortools/base/strong_int.h" #include "ortools/graph/graph.h" #include "ortools/graph/graph_io.h" #include "ortools/graph/test_util.h" @@ -45,122 +46,140 @@ using ::testing::Pair; using ::testing::UnorderedElementsAreArray; using ::util::ListGraph; +DEFINE_STRONG_INT_TYPE(NodeIndex, int32_t); +DEFINE_STRONG_INT_TYPE(ArcIndex, int64_t); + +using TestGraph = ListGraph; +template +using DijkstraWrapper = BoundedDijkstraWrapper; + TEST(BoundedDijkstraWrapperDeathTest, Accessors) { - ListGraph<> graph; - graph.AddArc(1, 3); - std::vector arc_lengths = {2.5}; - BoundedDijkstraWrapper, float> dijkstra(&graph, &arc_lengths); + TestGraph graph; + graph.AddArc(NodeIndex(1), NodeIndex(3)); + DijkstraWrapper::ByArc arc_lengths = {2.5}; + DijkstraWrapper dijkstra(&graph, &arc_lengths); const std::is_same same_type; ASSERT_TRUE(same_type.value); ASSERT_EQ(&dijkstra.graph(), &graph); - ASSERT_EQ(dijkstra.GetArcLength(0), 2.5); + ASSERT_EQ(dijkstra.GetArcLength(ArcIndex(0)), 2.5); } TEST(BoundedDijkstraWrapperDeathTest, WithArcLengthFunctor) { - ListGraph<> graph; - graph.AddArc(1, 3); - BoundedDijkstraWrapper, float, std::function> - dijkstra(&graph, [](int) { return 2.34; }); - ASSERT_FLOAT_EQ(dijkstra.GetArcLength(0), 2.34f); + TestGraph graph; + graph.AddArc(NodeIndex(1), NodeIndex(3)); + BoundedDijkstraWrapper> + dijkstra(&graph, [](ArcIndex) { return 2.34; }); + ASSERT_FLOAT_EQ(dijkstra.GetArcLength(ArcIndex(0)), 2.34f); } TEST(BoundedDijkstraWrapperDeathTest, ConstructorPreconditions) { - ListGraph<> graph; - for (int i = 0; i < 50; ++i) graph.AddArc(i, i + 1); + TestGraph graph; + for (int i = 0; i < 50; ++i) graph.AddArc(NodeIndex(i), NodeIndex(i + 1)); - std::vector arc_lengths(13, 0); - typedef BoundedDijkstraWrapper, int> TestedClass; + typedef DijkstraWrapper TestedClass; + TestedClass::ByArc arc_lengths(13, 0); EXPECT_DEATH(new TestedClass(&graph, &arc_lengths), "13"); arc_lengths.resize(50, 0); - arc_lengths[20] = -132; + arc_lengths[ArcIndex(20)] = -132; EXPECT_DEATH(new TestedClass(&graph, &arc_lengths), "-132"); } TEST(BoundedDijkstraWrapper, ArcPathToAndSourceOfShortestPathToNode) { - ListGraph<> graph; - std::vector arc_lengths = {1, 2, 3, 4, 6, 5}; - graph.AddArc(0, 1); - graph.AddArc(0, 1); - graph.AddArc(1, 2); - graph.AddArc(1, 2); - graph.AddArc(2, 3); - graph.AddArc(2, 3); + TestGraph graph; + DijkstraWrapper::ByArc arc_lengths = {1, 2, 3, 4, 6, 5}; + graph.AddArc(NodeIndex(0), NodeIndex(1)); + graph.AddArc(NodeIndex(0), NodeIndex(1)); + graph.AddArc(NodeIndex(1), NodeIndex(2)); + graph.AddArc(NodeIndex(1), NodeIndex(2)); + graph.AddArc(NodeIndex(2), NodeIndex(3)); + graph.AddArc(NodeIndex(2), NodeIndex(3)); - BoundedDijkstraWrapper, int> dijkstra(&graph, &arc_lengths); - const std::vector reached = dijkstra.RunBoundedDijkstra(0, 10); - EXPECT_THAT(reached, ElementsAre(0, 1, 2, 3)); - EXPECT_EQ(9, dijkstra.distances()[3]); - EXPECT_THAT(dijkstra.ArcPathTo(3), ElementsAre(0, 2, 5)); - EXPECT_THAT(dijkstra.NodePathTo(3), ElementsAre(0, 1, 2, 3)); - EXPECT_EQ(0, dijkstra.SourceOfShortestPathToNode(3)); + DijkstraWrapper dijkstra(&graph, &arc_lengths); + const auto reached = dijkstra.RunBoundedDijkstra(NodeIndex(0), 10); + EXPECT_THAT(reached, ElementsAre(NodeIndex(0), NodeIndex(1), NodeIndex(2), + NodeIndex(3))); + EXPECT_EQ(9, dijkstra.distances()[NodeIndex(3)]); + EXPECT_THAT(dijkstra.ArcPathTo(NodeIndex(3)), + ElementsAre(ArcIndex(0), ArcIndex(2), ArcIndex(5))); + EXPECT_THAT( + dijkstra.NodePathTo(NodeIndex(3)), + ElementsAre(NodeIndex(0), NodeIndex(1), NodeIndex(2), NodeIndex(3))); + EXPECT_EQ(NodeIndex(0), dijkstra.SourceOfShortestPathToNode(NodeIndex(3))); } TEST(BoundedDijkstraWrapper, EmptyPath) { - ListGraph<> graph; - std::vector arc_lengths = {1, 2}; - graph.AddArc(0, 1); - graph.AddArc(2, 3); + TestGraph graph; + DijkstraWrapper::ByArc arc_lengths = {1, 2}; + graph.AddArc(NodeIndex(0), NodeIndex(1)); + graph.AddArc(NodeIndex(2), NodeIndex(3)); - BoundedDijkstraWrapper, int> dijkstra(&graph, &arc_lengths); - const std::vector reached = dijkstra.RunBoundedDijkstra(0, 10); - EXPECT_THAT(reached, ElementsAre(0, 1)); + DijkstraWrapper dijkstra(&graph, &arc_lengths); + const auto reached = dijkstra.RunBoundedDijkstra(NodeIndex(0), 10); + EXPECT_THAT(reached, ElementsAre(NodeIndex(0), NodeIndex(1))); - EXPECT_EQ(0, dijkstra.distances()[0]); - EXPECT_THAT(dijkstra.ArcPathTo(0), ElementsAre()); - EXPECT_THAT(dijkstra.NodePathTo(0), ElementsAre(0)); - EXPECT_EQ(0, dijkstra.SourceOfShortestPathToNode(0)); + EXPECT_EQ(0, dijkstra.distances()[NodeIndex(0)]); + EXPECT_THAT(dijkstra.ArcPathTo(NodeIndex(0)), ElementsAre()); + EXPECT_THAT(dijkstra.NodePathTo(NodeIndex(0)), ElementsAre(NodeIndex(0))); + EXPECT_EQ(NodeIndex(0), dijkstra.SourceOfShortestPathToNode(NodeIndex(0))); } TEST(BoundedDijkstraWrapper, OverflowSafe) { - ListGraph<> graph; + TestGraph graph; const int64_t int_max = std::numeric_limits::max(); - std::vector arc_lengths = {int_max, int_max / 2, int_max / 2, 1}; - graph.AddArc(0, 1); - graph.AddArc(0, 1); - graph.AddArc(1, 2); - graph.AddArc(2, 3); + DijkstraWrapper::ByArc arc_lengths = {int_max, int_max / 2, + int_max / 2, 1}; + graph.AddArc(NodeIndex(0), NodeIndex(1)); + graph.AddArc(NodeIndex(0), NodeIndex(1)); + graph.AddArc(NodeIndex(1), NodeIndex(2)); + graph.AddArc(NodeIndex(2), NodeIndex(3)); - BoundedDijkstraWrapper, int64_t> dijkstra(&graph, &arc_lengths); - const std::vector reached = dijkstra.RunBoundedDijkstra(0, int_max); + BoundedDijkstraWrapper dijkstra(&graph, &arc_lengths); + const auto reached = dijkstra.RunBoundedDijkstra(NodeIndex(0), int_max); // This works because int_max is odd, i.e. 2 * (int_max / 2) = int_max - 1 - EXPECT_THAT(reached, ElementsAre(0, 1, 2)); - EXPECT_EQ(0, dijkstra.distances()[0]); - EXPECT_EQ(int_max / 2, dijkstra.distances()[1]); - EXPECT_EQ(int_max - 1, dijkstra.distances()[2]); + EXPECT_THAT(reached, ElementsAre(NodeIndex(0), NodeIndex(1), NodeIndex(2))); + EXPECT_EQ(0, dijkstra.distances()[NodeIndex(0)]); + EXPECT_EQ(int_max / 2, dijkstra.distances()[NodeIndex(1)]); + EXPECT_EQ(int_max - 1, dijkstra.distances()[NodeIndex(2)]); } TEST(BoundedDijkstraWrapper, ArcPathToAndSourceOfShortestPathToNode_WithArcLengthFunction) { - ListGraph<> graph; - std::vector arc_lengths = {1, 2, 3, 4, 6, 5}; - graph.AddArc(0, 1); - graph.AddArc(0, 1); - graph.AddArc(1, 2); - graph.AddArc(1, 2); - graph.AddArc(2, 3); - graph.AddArc(2, 3); + TestGraph graph; + DijkstraWrapper::ByArc arc_lengths = {1, 2, 3, 4, 6, 5}; + graph.AddArc(NodeIndex(0), NodeIndex(1)); + graph.AddArc(NodeIndex(0), NodeIndex(1)); + graph.AddArc(NodeIndex(1), NodeIndex(2)); + graph.AddArc(NodeIndex(1), NodeIndex(2)); + graph.AddArc(NodeIndex(2), NodeIndex(3)); + graph.AddArc(NodeIndex(2), NodeIndex(3)); class MyArcLengthFunctor { public: - explicit MyArcLengthFunctor(const std::vector& arc_lengths) + explicit MyArcLengthFunctor( + const DijkstraWrapper::ByArc& arc_lengths) : arc_lengths_(arc_lengths) {} - int operator()(int arc) const { - return arc % 2 == 1 ? arc_lengths_[arc] : 100; + + int operator()(ArcIndex arc) const { + return arc.value() % 2 == 1 ? arc_lengths_[arc] : 100; } private: - const std::vector& arc_lengths_; + const DijkstraWrapper::ByArc& arc_lengths_; }; - BoundedDijkstraWrapper, int, MyArcLengthFunctor> dijkstra( + BoundedDijkstraWrapper dijkstra( &graph, MyArcLengthFunctor(arc_lengths)); - const std::vector reached = dijkstra.RunBoundedDijkstra(0, 20); - EXPECT_THAT(reached, ElementsAre(0, 1, 2, 3)); - EXPECT_EQ(11, dijkstra.distances()[3]); - EXPECT_THAT(dijkstra.ArcPathTo(3), ElementsAre(1, 3, 5)); - EXPECT_THAT(dijkstra.NodePathTo(3), ElementsAre(0, 1, 2, 3)); - EXPECT_EQ(0, dijkstra.SourceOfShortestPathToNode(3)); + const auto reached = dijkstra.RunBoundedDijkstra(NodeIndex(0), 20); + EXPECT_THAT(reached, ElementsAre(NodeIndex(0), NodeIndex(1), NodeIndex(2), + NodeIndex(3))); + EXPECT_EQ(11, dijkstra.distances()[NodeIndex(3)]); + EXPECT_THAT(dijkstra.ArcPathTo(NodeIndex(3)), + ElementsAre(ArcIndex(1), ArcIndex(3), ArcIndex(5))); + EXPECT_THAT( + dijkstra.NodePathTo(NodeIndex(3)), + ElementsAre(NodeIndex(0), NodeIndex(1), NodeIndex(2), NodeIndex(3))); + EXPECT_EQ(NodeIndex(0), dijkstra.SourceOfShortestPathToNode(NodeIndex(3))); } TEST(BoundedDijkstraWrapperTest, RandomDenseGraph) { @@ -168,12 +187,12 @@ TEST(BoundedDijkstraWrapperTest, RandomDenseGraph) { const int num_nodes = 50; std::vector> lengths(num_nodes, std::vector(num_nodes)); - ListGraph<> graph; - std::vector arc_lengths; + TestGraph graph; + DijkstraWrapper::ByArc arc_lengths; for (int i = 0; i < num_nodes; ++i) { for (int j = 0; j < num_nodes; ++j) { lengths[i][j] = (i == j) ? 0 : absl::Uniform(random, 0, 1000); - graph.AddArc(i, j); + graph.AddArc(NodeIndex(i), NodeIndex(j)); arc_lengths.push_back(lengths[i][j]); } } @@ -191,15 +210,15 @@ TEST(BoundedDijkstraWrapperTest, RandomDenseGraph) { std::vector reached_sizes; for (int source = 0; source < num_nodes; ++source) { const int limit = 100; - BoundedDijkstraWrapper, int> dijkstra(&graph, &arc_lengths); - const std::vector reached = dijkstra.RunBoundedDijkstra(source, limit); - for (const int node : reached) { + DijkstraWrapper dijkstra(&graph, &arc_lengths); + const auto reached = dijkstra.RunBoundedDijkstra(NodeIndex(source), limit); + for (const NodeIndex node : reached) { EXPECT_LT(dijkstra.distances()[node], limit); - EXPECT_EQ(dijkstra.distances()[node], lengths[source][node]); + EXPECT_EQ(dijkstra.distances()[node], lengths[source][node.value()]); // Check that we never have the same node twice in the paths. - std::vector path = {node}; - int parent = node; + std::vector path = {node}; + NodeIndex parent = node; while (dijkstra.parents()[parent] != parent) { parent = dijkstra.parents()[parent]; path.push_back(parent); @@ -230,7 +249,7 @@ TEST(SimpleOneToOneShortestPathTest, PathTooLong) { { const auto [distance, path] = - SimpleOneToOneShortestPath(0, 3, tails, heads, lengths); + SimpleOneToOneShortestPath(0, 3, tails, heads, lengths); EXPECT_EQ(distance, std::numeric_limits::max()); EXPECT_TRUE(path.empty()); } @@ -238,7 +257,7 @@ TEST(SimpleOneToOneShortestPathTest, PathTooLong) { { // from 0 to 2 work because 2 * big_length < int_max. const auto [distance, path] = - SimpleOneToOneShortestPath(0, 2, tails, heads, lengths); + SimpleOneToOneShortestPath(0, 2, tails, heads, lengths); EXPECT_EQ(distance, std::numeric_limits::max() - 1); EXPECT_THAT(path, ElementsAre(0, 1, 2)); } @@ -256,7 +275,7 @@ TEST(SimpleOneToOneShortestPathTest, Random) { // This will be the "sparse" representation. std::vector tails; std::vector heads; - std::vector arc_lengths; + DijkstraWrapper::ByArc arc_lengths; // We permutes the arc order to properly test that it do not matter. std::vector nodes(num_nodes); @@ -292,8 +311,8 @@ TEST(SimpleOneToOneShortestPathTest, Random) { // No limit. There should always be a path with our generated data. { - const auto [distance, path] = - SimpleOneToOneShortestPath(from, to, tails, heads, arc_lengths); + const auto [distance, path] = SimpleOneToOneShortestPath( + from, to, tails, heads, arc_lengths); EXPECT_EQ(distance, shortest_distance[from][to]); EXPECT_FALSE(path.empty()); EXPECT_EQ(path.front(), from); @@ -302,7 +321,7 @@ TEST(SimpleOneToOneShortestPathTest, Random) { // A limit of shortest_distance[from][to] + 1 works too. { - const auto [distance, path] = SimpleOneToOneShortestPath( + const auto [distance, path] = SimpleOneToOneShortestPath( from, to, tails, heads, arc_lengths, shortest_distance[from][to] + 1); EXPECT_EQ(distance, shortest_distance[from][to]); EXPECT_FALSE(path.empty()); @@ -312,7 +331,7 @@ TEST(SimpleOneToOneShortestPathTest, Random) { // But a limit of shortest_distance[from][to] should fail. { - const auto [distance, path] = SimpleOneToOneShortestPath( + const auto [distance, path] = SimpleOneToOneShortestPath( from, to, tails, heads, arc_lengths, shortest_distance[from][to]); EXPECT_EQ(distance, shortest_distance[from][to]); EXPECT_TRUE(path.empty()); @@ -321,101 +340,116 @@ TEST(SimpleOneToOneShortestPathTest, Random) { } TEST(BoundedDijkstraWrapperTest, MultiRunsOverDynamicGraphAndLengths) { - ListGraph<> graph; - graph.AddArc(0, 1); - graph.AddArc(0, 1); - std::vector arc_lengths = {4, 3}; - BoundedDijkstraWrapper, int> dijkstra(&graph, &arc_lengths); + TestGraph graph; + graph.AddArc(NodeIndex(0), NodeIndex(1)); + graph.AddArc(NodeIndex(0), NodeIndex(1)); + DijkstraWrapper::ByArc arc_lengths = {4, 3}; + DijkstraWrapper dijkstra(&graph, &arc_lengths); - EXPECT_THAT(dijkstra.RunBoundedDijkstra(0, 5), ElementsAre(0, 1)); - EXPECT_EQ(0, dijkstra.SourceOfShortestPathToNode(1)); - EXPECT_THAT(dijkstra.ArcPathTo(1), ElementsAre(1)); + EXPECT_THAT(dijkstra.RunBoundedDijkstra(NodeIndex(0), 5), + ElementsAre(NodeIndex(0), NodeIndex(1))); + EXPECT_EQ(NodeIndex(0), dijkstra.SourceOfShortestPathToNode(NodeIndex(1))); + EXPECT_THAT(dijkstra.ArcPathTo(NodeIndex(1)), ElementsAre(ArcIndex(1))); - EXPECT_THAT(dijkstra.RunBoundedDijkstra(0, 2), ElementsAre(0)); - EXPECT_EQ(0, dijkstra.SourceOfShortestPathToNode(0)); - EXPECT_THAT(dijkstra.ArcPathTo(0), IsEmpty()); + EXPECT_THAT(dijkstra.RunBoundedDijkstra(NodeIndex(0), 2), + ElementsAre(NodeIndex(0))); + EXPECT_EQ(NodeIndex(0), dijkstra.SourceOfShortestPathToNode(NodeIndex(0))); + EXPECT_THAT(dijkstra.ArcPathTo(NodeIndex(0)), IsEmpty()); - EXPECT_THAT(dijkstra.RunBoundedDijkstra(1, 99), ElementsAre(1)); - EXPECT_EQ(1, dijkstra.SourceOfShortestPathToNode(1)); - EXPECT_THAT(dijkstra.ArcPathTo(1), IsEmpty()); + EXPECT_THAT(dijkstra.RunBoundedDijkstra(NodeIndex(1), 99), + ElementsAre(NodeIndex(1))); + EXPECT_EQ(NodeIndex(1), dijkstra.SourceOfShortestPathToNode(NodeIndex(1))); + EXPECT_THAT(dijkstra.ArcPathTo(NodeIndex(1)), IsEmpty()); // Add some arcs and nodes... - graph.AddArc(0, 2); + graph.AddArc(NodeIndex(0), NodeIndex(2)); arc_lengths.push_back(1); - graph.AddArc(1, 2); + graph.AddArc(NodeIndex(1), NodeIndex(2)); arc_lengths.push_back(0); - graph.AddArc(2, 1); + graph.AddArc(NodeIndex(2), NodeIndex(1)); arc_lengths.push_back(1); - graph.AddArc(1, 3); + graph.AddArc(NodeIndex(1), NodeIndex(3)); arc_lengths.push_back(5); - EXPECT_THAT(dijkstra.RunBoundedDijkstra(0, 10), ElementsAre(0, 2, 1, 3)); - EXPECT_EQ(0, dijkstra.SourceOfShortestPathToNode(3)); - EXPECT_THAT(dijkstra.ArcPathTo(3), ElementsAre(2, 4, 5)); + EXPECT_THAT( + dijkstra.RunBoundedDijkstra(NodeIndex(0), 10), + ElementsAre(NodeIndex(0), NodeIndex(2), NodeIndex(1), NodeIndex(3))); + EXPECT_EQ(NodeIndex(0), dijkstra.SourceOfShortestPathToNode(NodeIndex(3))); + EXPECT_THAT(dijkstra.ArcPathTo(NodeIndex(3)), + ElementsAre(ArcIndex(2), ArcIndex(4), ArcIndex(5))); - EXPECT_THAT(dijkstra.RunBoundedDijkstra(0, 6), ElementsAre(0, 2, 1)); - EXPECT_EQ(0, dijkstra.SourceOfShortestPathToNode(1)); - EXPECT_THAT(dijkstra.ArcPathTo(1), ElementsAre(2, 4)); + EXPECT_THAT(dijkstra.RunBoundedDijkstra(NodeIndex(0), 6), + ElementsAre(NodeIndex(0), NodeIndex(2), NodeIndex(1))); + EXPECT_EQ(NodeIndex(0), dijkstra.SourceOfShortestPathToNode(NodeIndex(1))); + EXPECT_THAT(dijkstra.ArcPathTo(NodeIndex(1)), + ElementsAre(ArcIndex(2), ArcIndex(4))); } TEST(BoundedDijkstraWrapperTest, MultipleSources) { // Use this graph. Source nodes have their initial distance in [ ]. // // N1[0] --(2)--> N0[4] --(1)--> N2 --(5)--> N3 <--(4)-- N4[3] --(5)--> N5 - ListGraph<> graph; - std::vector arc_lengths; - graph.AddArc(1, 0); + TestGraph graph; + DijkstraWrapper::ByArc arc_lengths; + graph.AddArc(NodeIndex(1), NodeIndex(0)); arc_lengths.push_back(2); - graph.AddArc(0, 2); + graph.AddArc(NodeIndex(0), NodeIndex(2)); arc_lengths.push_back(1); - graph.AddArc(2, 3); + graph.AddArc(NodeIndex(2), NodeIndex(3)); arc_lengths.push_back(5); - graph.AddArc(4, 3); + graph.AddArc(NodeIndex(4), NodeIndex(3)); arc_lengths.push_back(4); - graph.AddArc(4, 5); + graph.AddArc(NodeIndex(4), NodeIndex(5)); arc_lengths.push_back(5); - BoundedDijkstraWrapper, int> dijkstra(&graph, &arc_lengths); + DijkstraWrapper dijkstra(&graph, &arc_lengths); // The distance limit is exclusive, so we can't reach Node 5. ASSERT_THAT(dijkstra.RunBoundedDijkstraFromMultipleSources( - {{1, 0}, {0, 4}, {4, 3}}, 8), + {{NodeIndex(1), 0}, {NodeIndex(0), 4}, {NodeIndex(4), 3}}, 8), // The order is deterministic: node 4 comes before node 2, despite // having equal distance and higher index, because it's a source. - ElementsAre(1, 0, 4, 2, 3)); - EXPECT_EQ(2, dijkstra.distances()[0]); - EXPECT_EQ(1, dijkstra.SourceOfShortestPathToNode(0)); - EXPECT_THAT(dijkstra.ArcPathTo(0), ElementsAre(0)); - EXPECT_EQ(0, dijkstra.distances()[1]); - EXPECT_EQ(1, dijkstra.SourceOfShortestPathToNode(1)); - EXPECT_THAT(dijkstra.ArcPathTo(1), IsEmpty()); - EXPECT_EQ(3, dijkstra.distances()[2]); - EXPECT_EQ(1, dijkstra.SourceOfShortestPathToNode(2)); - EXPECT_THAT(dijkstra.ArcPathTo(2), ElementsAre(0, 1)); - EXPECT_EQ(7, dijkstra.distances()[3]); - EXPECT_EQ(4, dijkstra.SourceOfShortestPathToNode(3)); - EXPECT_THAT(dijkstra.ArcPathTo(3), ElementsAre(3)); - EXPECT_EQ(3, dijkstra.distances()[4]); - EXPECT_EQ(4, dijkstra.SourceOfShortestPathToNode(4)); - EXPECT_THAT(dijkstra.ArcPathTo(4), IsEmpty()); + ElementsAre(NodeIndex(1), NodeIndex(0), NodeIndex(4), + NodeIndex(2), NodeIndex(3))); + EXPECT_EQ(2, dijkstra.distances()[NodeIndex(0)]); + EXPECT_EQ(NodeIndex(1), dijkstra.SourceOfShortestPathToNode(NodeIndex(0))); + EXPECT_THAT(dijkstra.ArcPathTo(NodeIndex(0)), ElementsAre(ArcIndex(0))); + EXPECT_EQ(0, dijkstra.distances()[NodeIndex(1)]); + EXPECT_EQ(NodeIndex(1), dijkstra.SourceOfShortestPathToNode(NodeIndex(1))); + EXPECT_THAT(dijkstra.ArcPathTo(NodeIndex(1)), IsEmpty()); + EXPECT_EQ(3, dijkstra.distances()[NodeIndex(2)]); + EXPECT_EQ(NodeIndex(1), dijkstra.SourceOfShortestPathToNode(NodeIndex(2))); + EXPECT_THAT(dijkstra.ArcPathTo(NodeIndex(2)), + ElementsAre(ArcIndex(0), ArcIndex(1))); + EXPECT_EQ(7, dijkstra.distances()[NodeIndex(3)]); + EXPECT_EQ(NodeIndex(4), dijkstra.SourceOfShortestPathToNode(NodeIndex(3))); + EXPECT_THAT(dijkstra.ArcPathTo(NodeIndex(3)), ElementsAre(ArcIndex(3))); + EXPECT_EQ(3, dijkstra.distances()[NodeIndex(4)]); + EXPECT_EQ(NodeIndex(4), dijkstra.SourceOfShortestPathToNode(NodeIndex(4))); + EXPECT_THAT(dijkstra.ArcPathTo(NodeIndex(4)), IsEmpty()); } TEST(BoundedDijkstraWrapperTest, SourcesAtOrBeyondDistanceLimitAreNotReached) { - ListGraph<> graph(/*num_nodes=*/5, /*arc_capacity=*/0); - std::vector arc_lengths; // No arcs. - BoundedDijkstraWrapper, int> dijkstra(&graph, &arc_lengths); - EXPECT_THAT(dijkstra.RunBoundedDijkstraFromMultipleSources( - {{0, 10}, {1, 11}, {2, 12}, {3, 13}}, 12), - ElementsAre(0, 1)); + TestGraph graph(/*num_nodes=*/NodeIndex(5), /*arc_capacity=*/ArcIndex(0)); + DijkstraWrapper::ByArc arc_lengths; // No arcs. + DijkstraWrapper dijkstra(&graph, &arc_lengths); + EXPECT_THAT( + dijkstra.RunBoundedDijkstraFromMultipleSources({{NodeIndex(0), 10}, + {NodeIndex(1), 11}, + {NodeIndex(2), 12}, + {NodeIndex(3), 13}}, + 12), + ElementsAre(NodeIndex(0), NodeIndex(1))); } TEST(BoundedDijkstraWrapperTest, SourcesListedMultipleTimesKeepsMinDistance) { - ListGraph<> graph(/*num_nodes=*/5, /*arc_capacity=*/1); - graph.AddArc(1, 3); - std::vector arc_lengths = {20}; - BoundedDijkstraWrapper, int> dijkstra(&graph, &arc_lengths); - EXPECT_THAT(dijkstra.RunBoundedDijkstraFromMultipleSources( - {{1, 12}, {1, 10}, {1, 14}}, 31), - ElementsAre(1, 3)); - EXPECT_EQ(dijkstra.distances()[3], 30); + TestGraph graph(/*num_nodes=*/NodeIndex(5), /*arc_capacity=*/ArcIndex(1)); + graph.AddArc(NodeIndex(1), NodeIndex(3)); + DijkstraWrapper::ByArc arc_lengths = {20}; + DijkstraWrapper dijkstra(&graph, &arc_lengths); + EXPECT_THAT( + dijkstra.RunBoundedDijkstraFromMultipleSources( + {{NodeIndex(1), 12}, {NodeIndex(1), 10}, {NodeIndex(1), 14}}, 31), + ElementsAre(NodeIndex(1), NodeIndex(3))); + EXPECT_EQ(dijkstra.distances()[NodeIndex(3)], 30); } TEST(BoundedDijkstraWrapperTest, MultipleSourcesMultipleDestinations) { @@ -430,38 +464,45 @@ TEST(BoundedDijkstraWrapperTest, MultipleSourcesMultipleDestinations) { // `------(0)-----' // // The shortest path is S0->D1->N5->D4, of distance 2 + 3 + 1 + 1 + 1 = 8. - ListGraph<> graph; - std::vector arc_lengths; - graph.AddArc(0, 1); + TestGraph graph; + DijkstraWrapper::ByArc arc_lengths; + graph.AddArc(NodeIndex(0), NodeIndex(1)); arc_lengths.push_back(3); - graph.AddArc(2, 3); + graph.AddArc(NodeIndex(2), NodeIndex(3)); arc_lengths.push_back(3); - graph.AddArc(1, 5); + graph.AddArc(NodeIndex(1), NodeIndex(5)); arc_lengths.push_back(1); - graph.AddArc(3, 5); + graph.AddArc(NodeIndex(3), NodeIndex(5)); arc_lengths.push_back(0); - graph.AddArc(5, 3); + graph.AddArc(NodeIndex(5), NodeIndex(3)); arc_lengths.push_back(0); - graph.AddArc(5, 4); + graph.AddArc(NodeIndex(5), NodeIndex(4)); arc_lengths.push_back(1); - BoundedDijkstraWrapper, int> dijkstra(&graph, &arc_lengths); + DijkstraWrapper dijkstra(&graph, &arc_lengths); // Repeat the same source and destination multiple times, to verify that // it's supported. - std::vector> sources = {{0, 5}, {2, 4}, {0, 2}, {0, 9}}; - std::vector> destinations = { - {1, 7}, {4, 5}, {3, 3}, {4, 1}, {4, 3}}; + std::vector> sources = {{NodeIndex(0), 5}, + {NodeIndex(2), 4}, + {NodeIndex(0), 2}, + {NodeIndex(0), 9}}; + std::vector> destinations = {{NodeIndex(1), 7}, + {NodeIndex(4), 5}, + {NodeIndex(3), 3}, + {NodeIndex(4), 1}, + {NodeIndex(4), 3}}; EXPECT_THAT( dijkstra.RunBoundedDijkstraFromMultipleSourcesToMultipleDestinations( sources, destinations, /*num_destinations_to_reach=*/1, /*distance_limit=*/1000), - Contains(4)); - EXPECT_EQ(2 + 3 + 1 + 1, dijkstra.distances()[4]); - EXPECT_EQ(0, dijkstra.SourceOfShortestPathToNode(4)); - EXPECT_THAT(dijkstra.ArcPathTo(4), - ElementsAre(/*0->1*/ 0, /*1->5*/ 2, /*5->4*/ 5)); - EXPECT_EQ(2, dijkstra.GetSourceIndex(0)); - EXPECT_EQ(3, dijkstra.GetDestinationIndex(4)); + Contains(NodeIndex(4))); + EXPECT_EQ(2 + 3 + 1 + 1, dijkstra.distances()[NodeIndex(4)]); + EXPECT_EQ(NodeIndex(0), dijkstra.SourceOfShortestPathToNode(NodeIndex(4))); + EXPECT_THAT(dijkstra.ArcPathTo(NodeIndex(4)), + ElementsAre(/*0->1*/ ArcIndex(0), /*1->5*/ ArcIndex(2), + /*5->4*/ ArcIndex(5))); + EXPECT_EQ(2, dijkstra.GetSourceIndex(NodeIndex(0))); + EXPECT_EQ(3, dijkstra.GetDestinationIndex(NodeIndex(4))); // Run it with a limit too small: it'll fail to discover any destination. EXPECT_THAT( @@ -475,18 +516,20 @@ TEST(BoundedDijkstraWrapperTest, MultipleSourcesMultipleDestinations) { dijkstra.RunBoundedDijkstraFromMultipleSourcesToMultipleDestinations( sources, destinations, /*num_destinations_to_reach=*/2, /*distance_limit=*/9), // Limit is exclusive. - ElementsAre(4)); + ElementsAre(NodeIndex(4))); // Slightly modify the graph and try again. We want a case where the best // destination isn't the one with the smallest distance offset. - destinations.push_back({1, 2}); // D1 will be the closest destination now. + destinations.push_back( + {NodeIndex(1), 2}); // D1 will be the closest destination now. EXPECT_THAT( dijkstra.RunBoundedDijkstraFromMultipleSourcesToMultipleDestinations( sources, destinations, /*num_destinations_to_reach=*/1, /*distance_limit=*/8), // Limit is exclusive. - ElementsAre(1)); - EXPECT_EQ(0, dijkstra.SourceOfShortestPathToNode(1)); - EXPECT_THAT(dijkstra.ArcPathTo(1), ElementsAre(/*0->1*/ 0)); + ElementsAre(NodeIndex(1))); + EXPECT_EQ(NodeIndex(0), dijkstra.SourceOfShortestPathToNode(NodeIndex(1))); + EXPECT_THAT(dijkstra.ArcPathTo(NodeIndex(1)), + ElementsAre(/*0->1*/ ArcIndex(0))); // Corner case: run with no destinations. EXPECT_THAT( @@ -505,8 +548,8 @@ TEST(BoundedDijkstraWrapperTest, MultipleSourcesMultipleDestinations) { // Call Get{Source,Destination}Index() on nodes that aren't sources or // destinations. This returns junk; so we don't check the returned values, // but we do check that it doesn't crash. - dijkstra.GetDestinationIndex(4); - dijkstra.GetSourceIndex(1); + dijkstra.GetDestinationIndex(NodeIndex(4)); + dijkstra.GetSourceIndex(NodeIndex(1)); // Setting num_reached_destinations=1 now should make '1' the only reachable // destination, even if the limit is infinite. @@ -514,85 +557,88 @@ TEST(BoundedDijkstraWrapperTest, MultipleSourcesMultipleDestinations) { dijkstra.RunBoundedDijkstraFromMultipleSourcesToMultipleDestinations( sources, destinations, /*num_destinations_to_reach=*/1, /*distance_limit=*/1000), - ElementsAre(1)); + ElementsAre(NodeIndex(1))); // Verify that if we set the number of destinations to infinity, they're all // explored, and the search still stops before exploring the whole graph. To // do that, we add one extra arc that's beyond the farthest destination's // distance (including its destination offset), i.e. 1 (distance 2+3+7 = 12). - graph.AddArc(5, 6); + graph.AddArc(NodeIndex(5), NodeIndex(6)); arc_lengths.push_back(2); - graph.AddArc(6, 7); + graph.AddArc(NodeIndex(6), NodeIndex(7)); arc_lengths.push_back(0); EXPECT_THAT( dijkstra.RunBoundedDijkstraFromMultipleSourcesToMultipleDestinations( sources, destinations, /*num_destinations_to_reach=*/1000, /*distance_limit=*/1000), - ElementsAre(1, 4, 3)); - EXPECT_GE(dijkstra.distances()[1], 5); - EXPECT_GE(dijkstra.distances()[4], 7); - EXPECT_GE(dijkstra.distances()[3], 6); + ElementsAre(NodeIndex(1), NodeIndex(4), NodeIndex(3))); + EXPECT_GE(dijkstra.distances()[NodeIndex(1)], 5); + EXPECT_GE(dijkstra.distances()[NodeIndex(4)], 7); + EXPECT_GE(dijkstra.distances()[NodeIndex(3)], 6); // To verify that node #7 isn't reached, we can check its distance, which will // still be set to the initialized "distance_limit - min_destination_offset". - EXPECT_GE(dijkstra.distances()[7], 1000 - 1); + EXPECT_GE(dijkstra.distances()[NodeIndex(7)], 1000 - 1); } TEST(BoundedDijkstraWrapperTest, OneToOneShortestPath) { // Since we already tested the multiple sources - multiple destinations // variant, we only need to test the "plumbing" here. - ListGraph<> graph; - std::vector arc_lengths; - graph.AddArc(0, 1); + TestGraph graph; + DijkstraWrapper::ByArc arc_lengths; + graph.AddArc(NodeIndex(0), NodeIndex(1)); arc_lengths.push_back(3); - graph.AddArc(1, 2); + graph.AddArc(NodeIndex(1), NodeIndex(2)); arc_lengths.push_back(2); - BoundedDijkstraWrapper, int> dijkstra(&graph, &arc_lengths); + DijkstraWrapper dijkstra(&graph, &arc_lengths); - EXPECT_TRUE(dijkstra.OneToOneShortestPath(0, 2, 6)); - EXPECT_THAT(dijkstra.ArcPathTo(2), ElementsAre(0, 1)); + EXPECT_TRUE(dijkstra.OneToOneShortestPath(NodeIndex(0), NodeIndex(2), 6)); + EXPECT_THAT(dijkstra.ArcPathTo(NodeIndex(2)), + ElementsAre(ArcIndex(0), ArcIndex(1))); - EXPECT_TRUE(dijkstra.OneToOneShortestPath(0, 0, 1)); - EXPECT_THAT(dijkstra.ArcPathTo(0), ElementsAre()); + EXPECT_TRUE(dijkstra.OneToOneShortestPath(NodeIndex(0), NodeIndex(0), 1)); + EXPECT_THAT(dijkstra.ArcPathTo(NodeIndex(0)), ElementsAre()); - EXPECT_TRUE(dijkstra.OneToOneShortestPath(1, 2, 3)); - EXPECT_THAT(dijkstra.ArcPathTo(2), ElementsAre(1)); + EXPECT_TRUE(dijkstra.OneToOneShortestPath(NodeIndex(1), NodeIndex(2), 3)); + EXPECT_THAT(dijkstra.ArcPathTo(NodeIndex(2)), ElementsAre(ArcIndex(1))); - EXPECT_FALSE(dijkstra.OneToOneShortestPath(0, 2, 5)); - EXPECT_FALSE(dijkstra.OneToOneShortestPath(0, 0, 0)); - EXPECT_FALSE(dijkstra.OneToOneShortestPath(1, 2, 2)); - EXPECT_FALSE(dijkstra.OneToOneShortestPath(2, 1, 1000)); + EXPECT_FALSE(dijkstra.OneToOneShortestPath(NodeIndex(0), NodeIndex(2), 5)); + EXPECT_FALSE(dijkstra.OneToOneShortestPath(NodeIndex(0), NodeIndex(0), 0)); + EXPECT_FALSE(dijkstra.OneToOneShortestPath(NodeIndex(1), NodeIndex(2), 2)); + EXPECT_FALSE(dijkstra.OneToOneShortestPath(NodeIndex(2), NodeIndex(0), 1000)); } TEST(BoundedDijkstraWrapperTest, CustomSettledNodeCallback) { // A small chain: 8 --[3]--> 1 --[2]--> 42 --[3]--> 3 --[2]--> 4. - ListGraph<> graph; - std::vector arc_lengths; - graph.AddArc(8, 1); + TestGraph graph; + DijkstraWrapper::ByArc arc_lengths; + graph.AddArc(NodeIndex(8), NodeIndex(1)); arc_lengths.push_back(3); - graph.AddArc(1, 42); + graph.AddArc(NodeIndex(1), NodeIndex(42)); arc_lengths.push_back(2); - graph.AddArc(42, 3); + graph.AddArc(NodeIndex(42), NodeIndex(3)); arc_lengths.push_back(3); - graph.AddArc(3, 4); + graph.AddArc(NodeIndex(3), NodeIndex(4)); arc_lengths.push_back(2); - typedef BoundedDijkstraWrapper, int> DijkstraType; + typedef DijkstraWrapper DijkstraType; DijkstraType dijkstra(&graph, &arc_lengths); // Tracks each NodeDistance it's called on, and sets the distance limit // to 10 if it gets called on node 42. - std::vector> settled_node_dists; - auto callback = [&settled_node_dists](int node, int distance, + std::vector> settled_node_dists; + auto callback = [&settled_node_dists](NodeIndex node, int distance, int* distance_limit) { settled_node_dists.push_back({node, distance}); - if (node == 42) *distance_limit = 10; + if (node == NodeIndex(42)) *distance_limit = 10; }; - EXPECT_THAT(dijkstra.RunBoundedDijkstraWithSettledNodeCallback({{8, 0}}, - callback, 999), - ElementsAre(8, 1, 42, 3)); + EXPECT_THAT( + dijkstra.RunBoundedDijkstraWithSettledNodeCallback({{NodeIndex(8), 0}}, + callback, 999), + ElementsAre(NodeIndex(8), NodeIndex(1), NodeIndex(42), NodeIndex(3))); EXPECT_THAT(settled_node_dists, - ElementsAre(Pair(8, 0), Pair(1, 3), Pair(42, 5), Pair(3, 8))); + ElementsAre(Pair(NodeIndex(8), 0), Pair(NodeIndex(1), 3), + Pair(NodeIndex(42), 5), Pair(NodeIndex(3), 8))); } TEST(BoundedDisjktraTest, RandomizedStressTest) { @@ -601,49 +647,51 @@ TEST(BoundedDisjktraTest, RandomizedStressTest) { constexpr int kint32max = std::numeric_limits::max(); for (int test = 0; test < kNumTests; ++test) { // Generate a random graph with random weights. - const int num_nodes = absl::Uniform(random, 1, 12); - const int num_arcs = - absl::Uniform(absl::IntervalClosed, random, 0, - std::min(num_nodes * (num_nodes - 1), 15)); - ListGraph<> graph(num_nodes, num_arcs); - for (int a = 0; a < num_arcs; ++a) { - graph.AddArc(absl::Uniform(random, 0, num_nodes), - absl::Uniform(random, 0, num_nodes)); + const NodeIndex num_nodes(absl::Uniform(random, 1, 12)); + const ArcIndex num_arcs(absl::Uniform( + absl::IntervalClosed, random, 0, + std::min(num_nodes.value() * (num_nodes.value() - 1), 15))); + TestGraph graph(num_nodes, num_arcs); + for (ArcIndex a(0); a < num_arcs; ++a) { + graph.AddArc(NodeIndex(absl::Uniform(random, 0, num_nodes.value())), + NodeIndex(absl::Uniform(random, 0, num_nodes.value()))); } - std::vector lengths(num_arcs); + DijkstraWrapper::ByArc lengths(num_arcs); for (int& w : lengths) w = absl::Uniform(random, 0, 5); // Run Floyd-Warshall as a 'reference' shortest path algorithm. - FlatMatrix ref_dist(num_nodes, num_nodes, kint32max); - for (int a = 0; a < num_arcs; ++a) { - int& d = ref_dist[graph.Tail(a)][graph.Head(a)]; + FlatMatrix ref_dist(num_nodes.value(), num_nodes.value(), kint32max); + for (ArcIndex a(0); a < num_arcs; ++a) { + int& d = ref_dist[graph.Tail(a).value()][graph.Head(a).value()]; if (lengths[a] < d) d = lengths[a]; } - for (int node = 0; node < num_nodes; ++node) { - ref_dist[node][node] = 0; + for (NodeIndex node(0); node < num_nodes; ++node) { + ref_dist[node.value()][node.value()] = 0; } - for (int k = 0; k < num_nodes; ++k) { - for (int i = 0; i < num_nodes; ++i) { - for (int j = 0; j < num_nodes; ++j) { + for (NodeIndex k(0); k < num_nodes; ++k) { + for (NodeIndex i(0); i < num_nodes; ++i) { + for (NodeIndex j(0); j < num_nodes; ++j) { const int64_t dist_through_k = - static_cast(ref_dist[i][k]) + ref_dist[k][j]; - if (dist_through_k < ref_dist[i][j]) ref_dist[i][j] = dist_through_k; + static_cast(ref_dist[i.value()][k.value()]) + + ref_dist[k.value()][j.value()]; + if (dist_through_k < ref_dist[i.value()][j.value()]) + ref_dist[i.value()][j.value()] = dist_through_k; } } } // Compute the graph's largest distance below kint32max. int max_distance = 0; - for (int i = 0; i < num_nodes; ++i) { - for (int j = 0; j < num_nodes; ++j) { - const int d = ref_dist[i][j]; + for (NodeIndex i(0); i < num_nodes; ++i) { + for (NodeIndex j(0); j < num_nodes; ++j) { + const int d = ref_dist[i.value()][j.value()]; if (d != kint32max && d > max_distance) max_distance = d; } } // Now, run some Dijkstras and verify that they match. To balance out the // FW (Floyd-Warshall) which is O(N³), we run more than one Dijkstra per FW. - BoundedDijkstraWrapper, int> dijkstra(&graph, &lengths); + DijkstraWrapper dijkstra(&graph, &lengths); for (int num_dijkstra = 0; num_dijkstra < 20; ++num_dijkstra) { // Draw the distance limit. const int limit = @@ -652,33 +700,34 @@ TEST(BoundedDisjktraTest, RandomizedStressTest) { : absl::Uniform(absl::IntervalClosed, random, 0, max_distance); // Draw sources (*with* repetition) with initial distances. const int num_sources = absl::Uniform(random, 1, 5); - std::vector> sources(num_sources); + std::vector> sources(num_sources); for (auto& [s, dist] : sources) { - s = absl::Uniform(random, 0, num_nodes); + s = NodeIndex(absl::Uniform(random, 0, num_nodes.value())); dist = absl::Uniform(absl::IntervalClosed, random, 0, max_distance + 1); } // Precompute the reference minimum distance to each node (using any of // the sources), and the expected reached nodes: any node whose distance // is < limit. That includes the sources: if a source's initial distance // is ≥ limit, it won't be reached.That includes the source themselves. - std::vector node_min_dist(num_nodes, kint32max); - std::vector expected_reached_nodes; - for (int node = 0; node < num_nodes; ++node) { + DijkstraWrapper::ByNode node_min_dist(num_nodes, kint32max); + DijkstraWrapper::ByNode expected_reached_nodes; + for (NodeIndex node(0); node < num_nodes; ++node) { int min_dist = kint32max; for (const auto& [src, dist] : sources) { // Cast to int64_t to avoid overflows. min_dist = std::min( - min_dist, static_cast(ref_dist[src][node]) + dist); + min_dist, + static_cast(ref_dist[src.value()][node.value()]) + dist); } node_min_dist[node] = min_dist; if (min_dist < limit) expected_reached_nodes.push_back(node); } - const std::vector reached_nodes = + const auto reached_nodes = dijkstra.RunBoundedDijkstraFromMultipleSources(sources, limit); EXPECT_THAT(reached_nodes, UnorderedElementsAreArray(expected_reached_nodes)); - for (const int node : reached_nodes) { + for (const NodeIndex node : reached_nodes) { EXPECT_EQ(dijkstra.distances()[node], node_min_dist[node]) << node; } ASSERT_FALSE(HasFailure()) @@ -697,7 +746,8 @@ void BM_GridGraph(benchmark::State& state) { const int kSourceNode = static_cast(kWidth * kHeight / 2); std::unique_ptr graph = util::Create2DGridGraph(/*width=*/kWidth, /*height=*/kHeight); - std::vector arc_lengths(graph->num_arcs(), 0); + BoundedDijkstraWrapper::ByArc arc_lengths( + graph->num_arcs(), 0); const int64_t min_length = arc_lengths_are_discrete ? 0 : 1; const int64_t max_length = arc_lengths_are_discrete ? 2 : 1000000000000000L; std::mt19937 random(12345); diff --git a/ortools/graph/graph.h b/ortools/graph/graph.h index 73af4c944a..c8b7ef0b83 100644 --- a/ortools/graph/graph.h +++ b/ortools/graph/graph.h @@ -420,6 +420,8 @@ class Vector : public std::vector { template class SVector { public: + using value_type = T; + SVector() : base_(nullptr), size_(0), capacity_(0) {} ~SVector() { clear_and_dealloc(); } @@ -434,7 +436,7 @@ class SVector { capacity_ = other.size_; base_ = Allocate(capacity_); CHECK(base_ != nullptr); - base_ += capacity_; + base_ += static_cast(capacity_); } else { // capacity_ >= other.size clear(); } @@ -488,6 +490,9 @@ class SVector { T* data() const { return base_; } + const T* begin() const { return base_; } + const T* end() const { return base_ + static_cast(size_); } + void swap(SVector& x) noexcept { std::swap(base_, x.base_); std::swap(size_, x.size_); @@ -564,8 +569,9 @@ class SVector { // Copies other.base_ to base_ in this SVector. Safe for all types as it uses // constructor for each entry. void CopyInternal(const SVector& other, std::false_type) { - for (int i = -size_; i < size_; ++i) { - new (base_ + i) T(other.base_[i]); + for (IndexT i = -size_; i < size_; ++i) { + new (base_ + static_cast(i)) + T(other.base_[static_cast(i)]); } } @@ -1091,41 +1097,21 @@ class ReverseArcStaticGraph // TODO(user): consider slower but more memory efficient implementations that // follow the cycles of the permutation and use a bitmap to indicate what has // been permuted or to mark the beginning of each cycle. - -// Some compiler do not know typeof(), so we have to use this extra function -// internally. -template -void PermuteWithExplicitElementType(const IntVector& permutation, - Array& array_to_permute, - ElementType unused) { - std::vector temp(permutation.size()); - for (size_t i = 0; i < permutation.size(); ++i) { - temp[i] = array_to_permute[i]; - } - for (size_t i = 0; i < permutation.size(); ++i) { - array_to_permute[static_cast(permutation[i])] = temp[i]; - } -} - template void Permute(const IntVector& permutation, Array* array_to_permute) { if (permutation.empty()) { return; } - PermuteWithExplicitElementType(permutation, *array_to_permute, - (*array_to_permute)[0]); -} - -// We need a specialization for vector, because the default code uses -// (*array_to_permute)[0] as ElementType, which isn't 'bool' in that case. -template -void Permute(const IntVector& permutation, - std::vector* array_to_permute) { - if (permutation.empty()) { - return; + const auto size = permutation.size(); + auto& array = *array_to_permute; + using ElementType = + typename std::iterator_traits::value_type; + std::vector temp(size); + auto array_begin = std::begin(array); + std::copy_n(array_begin, size, temp.begin()); + for (size_t i = 0; i < permutation.size(); ++i) { + *(array_begin + static_cast(permutation[i])) = temp[i]; } - bool unused = false; - PermuteWithExplicitElementType(permutation, *array_to_permute, unused); } // BaseGraph implementation ---------------------------------------------------- diff --git a/ortools/graph/graph_io.h b/ortools/graph/graph_io.h index ffe455028e..82a2002a5e 100644 --- a/ortools/graph/graph_io.h +++ b/ortools/graph/graph_io.h @@ -97,12 +97,12 @@ std::string GraphToString(const Graph& graph, GraphToStringFormat format) { } else { // PRINT_GRAPH_ADJACENCY_LISTS[_SORTED] adj.clear(); for (const typename Graph::ArcIndex arc : graph.OutgoingArcs(node)) { - adj.push_back(graph.Head(arc)); + adj.push_back(static_cast(graph.Head(arc))); } if (format == PRINT_GRAPH_ADJACENCY_LISTS_SORTED) { std::sort(adj.begin(), adj.end()); } - if (node != 0) out += '\n'; + if (node != typename Graph::NodeIndex(0)) out += '\n'; absl::StrAppend(&out, static_cast(node), ": ", absl::StrJoin(adj, " ")); } diff --git a/ortools/graph/graph_test.cc b/ortools/graph/graph_test.cc index 29e85240e2..e690197385 100644 --- a/ortools/graph/graph_test.cc +++ b/ortools/graph/graph_test.cc @@ -24,6 +24,7 @@ #include #include +#include "absl/algorithm/container.h" #include "absl/log/check.h" #include "absl/random/random.h" #include "absl/strings/str_cat.h" @@ -32,9 +33,11 @@ #include "gtest/gtest.h" #include "ortools/base/gmock.h" #include "ortools/base/strong_int.h" +#include "ortools/base/strong_vector.h" namespace util { +using testing::ElementsAre; using testing::Pair; using testing::UnorderedElementsAre; @@ -289,98 +292,144 @@ void ConstructAndCheckGraph( // Return the size of the memory block allocated by malloc when asking for x // bytes. -inline int UpperBoundOfMallocBlockSizeOf(int x) { +template +inline IndexType UpperBoundOfMallocBlockSizeOf(IndexType x) { // Note(user): as of 2012-09, the rule seems to be: round x up to the // next multiple of 16. // WARNING: This may change, and may already be wrong for small values. - return 16 * ((x + 15) / 16); + return IndexType((16 * (static_cast(x) + 15)) / 16); } -TEST(SVectorTest, DynamicGrowth) { - internal::SVector v; - EXPECT_EQ(0, v.size()); - EXPECT_EQ(0, v.capacity()); - for (int i = 0; i < 100; i++) { +template +class SVectorTest : public ::testing::Test {}; + +typedef ::testing::Types, std::pair, + std::pair, + std::pair> + TestSVectorIndexTypes; + +TYPED_TEST_SUITE(SVectorTest, TestSVectorIndexTypes); + +TYPED_TEST(SVectorTest, CopyMoveIterate) { + using IndexT = typename TypeParam::first_type; + using ValueT = typename TypeParam::second_type; + using VectorT = internal::SVector; + VectorT v; + v.resize(IndexT(2)); + v[IndexT(0)] = ValueT(1); + v[IndexT(1)] = ValueT(2); + + { + EXPECT_THAT(VectorT(v), ElementsAre(ValueT(1), ValueT(2))); + VectorT v2 = v; + EXPECT_THAT(v2, ElementsAre(ValueT(1), ValueT(2))); + EXPECT_THAT(v, ElementsAre(ValueT(1), ValueT(2))); + } + + { + VectorT v2 = std::move(v); + EXPECT_THAT(v2, ElementsAre(ValueT(1), ValueT(2))); + EXPECT_THAT(VectorT(std::move(v2)), ElementsAre(ValueT(1), ValueT(2))); + } +} + +TYPED_TEST(SVectorTest, DynamicGrowth) { + using IndexT = typename TypeParam::first_type; + using ValueT = typename TypeParam::second_type; + internal::SVector v; + EXPECT_EQ(IndexT(0), v.size()); + EXPECT_EQ(IndexT(0), v.capacity()); + for (ValueT i(0); i < ValueT(100); i++) { v.grow(-i, i); } - EXPECT_EQ(100, v.size()); - EXPECT_GE(v.capacity(), 100); - EXPECT_LE(v.capacity(), UpperBoundOfMallocBlockSizeOf(100)); - for (int i = 0; i < 100; i++) { - EXPECT_EQ(-i, v[~i]); - EXPECT_EQ(i, v[i]); + EXPECT_EQ(IndexT(100), v.size()); + EXPECT_GE(v.capacity(), IndexT(100)); + EXPECT_LE(v.capacity(), UpperBoundOfMallocBlockSizeOf(IndexT(100))); + for (IndexT i(0); i < IndexT(100); ++i) { + EXPECT_EQ(ValueT(static_cast(-i)), v[~i]); + EXPECT_EQ(ValueT(static_cast(i)), v[i]); } } -TEST(SVectorTest, Reserve) { - internal::SVector v; - v.reserve(100); - EXPECT_EQ(0, v.size()); - EXPECT_GE(v.capacity(), 100); - EXPECT_LE(v.capacity(), UpperBoundOfMallocBlockSizeOf(100)); - for (int i = 0; i < 100; i++) { +TYPED_TEST(SVectorTest, Reserve) { + using IndexT = typename TypeParam::first_type; + using ValueT = typename TypeParam::second_type; + internal::SVector v; + v.reserve(IndexT(100)); + EXPECT_EQ(IndexT(0), v.size()); + EXPECT_GE(v.capacity(), IndexT(100)); + EXPECT_LE(v.capacity(), UpperBoundOfMallocBlockSizeOf(IndexT(100))); + for (ValueT i(0); i < ValueT(100); i++) { v.grow(-i, i); } - EXPECT_EQ(100, v.size()); - EXPECT_GE(v.capacity(), 100); - EXPECT_LE(v.capacity(), UpperBoundOfMallocBlockSizeOf(100)); - for (int i = 0; i < 10; i++) { - EXPECT_EQ(-i, v[~i]); - EXPECT_EQ(i, v[i]); + EXPECT_EQ(IndexT(100), v.size()); + EXPECT_GE(v.capacity(), IndexT(100)); + EXPECT_LE(v.capacity(), UpperBoundOfMallocBlockSizeOf(IndexT(100))); + for (IndexT i(0); i < IndexT(10); i++) { + EXPECT_EQ(ValueT(static_cast(-i)), v[~i]); + EXPECT_EQ(ValueT(static_cast(i)), v[i]); } } -TEST(SVectorTest, Resize) { - internal::SVector v; - v.resize(100); - EXPECT_EQ(100, v.size()); - EXPECT_GE(v.capacity(), 100); - EXPECT_LE(v.capacity(), UpperBoundOfMallocBlockSizeOf(100)); - for (int i = 0; i < 100; i++) { - EXPECT_EQ(0, v[-i - 1]); - EXPECT_EQ(0, v[i]); +TYPED_TEST(SVectorTest, Resize) { + using IndexT = typename TypeParam::first_type; + using ValueT = typename TypeParam::second_type; + internal::SVector v; + v.resize(IndexT(100)); + EXPECT_EQ(IndexT(100), v.size()); + EXPECT_GE(v.capacity(), IndexT(100)); + EXPECT_LE(v.capacity(), UpperBoundOfMallocBlockSizeOf(IndexT(100))); + for (IndexT i(0); i < IndexT(100); ++i) { + EXPECT_EQ(ValueT(0), v[-i - IndexT(1)]); + EXPECT_EQ(ValueT(0), v[i]); } } -TEST(SVectorTest, ResizeToZero) { - internal::SVector s; - s.resize(1); - s.resize(0); - EXPECT_EQ(0, s.size()); +TYPED_TEST(SVectorTest, ResizeToZero) { + using IndexT = typename TypeParam::first_type; + using ValueT = typename TypeParam::second_type; + internal::SVector v; + v.resize(IndexT(1)); + v.resize(IndexT(0)); + EXPECT_EQ(IndexT(0), v.size()); } -TEST(SVectorTest, Swap) { - internal::SVector s; - internal::SVector t; - s.resize(1); - s[0] = 's'; - s[-1] = 's'; - t.resize(2); - for (int i = -2; i <= 1; ++i) { - t[i] = 't'; +TYPED_TEST(SVectorTest, Swap) { + using IndexT = typename TypeParam::first_type; + using ValueT = typename TypeParam::second_type; + internal::SVector s; + internal::SVector t; + s.resize(IndexT(1)); + s[IndexT(0)] = ValueT('s'); + s[IndexT(-1)] = ValueT('s'); + t.resize(IndexT(2)); + for (IndexT i(-2); i <= IndexT(1); ++i) { + t[i] = ValueT('t'); } s.swap(t); - EXPECT_EQ(1, t.size()); - EXPECT_EQ('s', t[-1]); - EXPECT_EQ('s', t[0]); - EXPECT_EQ(2, s.size()); - EXPECT_EQ('t', s[-2]); - EXPECT_EQ('t', s[-1]); - EXPECT_EQ('t', s[0]); - EXPECT_EQ('t', s[1]); + EXPECT_EQ(IndexT(1), t.size()); + EXPECT_EQ(ValueT('s'), t[IndexT(-1)]); + EXPECT_EQ(ValueT('s'), t[IndexT(0)]); + EXPECT_EQ(IndexT(2), s.size()); + EXPECT_EQ(ValueT('t'), s[IndexT(-2)]); + EXPECT_EQ(ValueT('t'), s[IndexT(-1)]); + EXPECT_EQ(ValueT('t'), s[IndexT(0)]); + EXPECT_EQ(ValueT('t'), s[IndexT(1)]); } -TEST(SVectorTest, SwapAndDestroy) { - internal::SVector s; +TYPED_TEST(SVectorTest, SwapAndDestroy) { + using IndexT = typename TypeParam::first_type; + using ValueT = typename TypeParam::second_type; + internal::SVector s; { - internal::SVector t; - t.resize(2); - t[-2] = 42; + internal::SVector t; + t.resize(IndexT(2)); + t[IndexT(-2)] = ValueT(42); t.swap(s); } - EXPECT_EQ(2, s.size()); - EXPECT_EQ(42, s[-2]); - EXPECT_EQ(0, s[1]); + EXPECT_EQ(IndexT(2), s.size()); + EXPECT_EQ(ValueT(42), s[IndexT(-2)]); + EXPECT_EQ(ValueT(0), s[IndexT(1)]); } // Use a more complex type to better check the invocations of @@ -458,7 +507,7 @@ class MoveOnlyObject { int MoveOnlyObject::sequence_ = 1; int MoveOnlyObject::object_count_ = 0; -TEST(SVectorTest, MoveWithMoveOnlyObject) { +TEST(SVectorMoveOnlyTest, MoveWithMoveOnlyObject) { EXPECT_EQ(0, MoveOnlyObject::GetObjectCount()); internal::SVector a; a.resize(10); @@ -472,7 +521,7 @@ TEST(SVectorTest, MoveWithMoveOnlyObject) { EXPECT_EQ(0, a.size()); // NOLINT } -TEST(SVectorTest, ShrinkWithMoveOnlyObject) { +TEST(SVectorMoveOnlyTest, ShrinkWithMoveOnlyObject) { EXPECT_EQ(0, MoveOnlyObject::GetObjectCount()); { internal::SVector a; @@ -484,7 +533,7 @@ TEST(SVectorTest, ShrinkWithMoveOnlyObject) { EXPECT_EQ(0, MoveOnlyObject::GetObjectCount()); } -TEST(SVectorTest, GrowMoveOnlyObject) { +TEST(SVectorMoveOnlyTest, GrowMoveOnlyObject) { EXPECT_EQ(0, MoveOnlyObject::GetObjectCount()); { internal::SVector a; @@ -501,7 +550,7 @@ TEST(SVectorTest, GrowMoveOnlyObject) { EXPECT_EQ(0, MoveOnlyObject::GetObjectCount()); } -TEST(SVectorTest, ReserveMoveOnlyObject) { +TEST(SVectorMoveOnlyTest, ReserveMoveOnlyObject) { EXPECT_EQ(0, MoveOnlyObject::GetObjectCount()); { internal::SVector a; @@ -554,7 +603,7 @@ int TrackedObject::num_destructions = 0; int TrackedObject::num_moves = 0; int TrackedObject::num_copies = 0; -TEST(SVectorTest, CopyConstructor) { +TEST(SVectorTrackingTest, CopyConstructor) { TrackedObject::ResetCounters(); ASSERT_EQ(TrackedObject::Counters(), "constructions: 0, destructions: 0, moves: 0, copies: 0"); @@ -573,7 +622,7 @@ TEST(SVectorTest, CopyConstructor) { ASSERT_EQ(v_copy.size(), 5); } -TEST(SVectorTest, AssignmentOperator) { +TEST(SVectorTrackingTest, AssignmentOperator) { TrackedObject::ResetCounters(); ASSERT_EQ(TrackedObject::Counters(), "constructions: 0, destructions: 0, moves: 0, copies: 0"); @@ -595,7 +644,7 @@ TEST(SVectorTest, AssignmentOperator) { ASSERT_EQ(other.size(), 5); } -TEST(SVectorTest, CopyConstructorIntegralType) { +TEST(SVectorTrackingTest, CopyConstructorIntegralType) { auto v = internal::SVector(); v.resize(3); v[-3] = 1; @@ -613,7 +662,7 @@ TEST(SVectorTest, CopyConstructorIntegralType) { } } -TEST(SVectorTest, AssignmentOperatorIntegralType) { +TEST(SVectorTrackingTest, AssignmentOperatorIntegralType) { internal::SVector other; auto v = internal::SVector(); v.resize(3); @@ -632,7 +681,7 @@ TEST(SVectorTest, AssignmentOperatorIntegralType) { } } -TEST(SVectorTest, MoveConstructor) { +TEST(SVectorTrackingTest, MoveConstructor) { TrackedObject::ResetCounters(); ASSERT_EQ(TrackedObject::Counters(), "constructions: 0, destructions: 0, moves: 0, copies: 0"); @@ -650,7 +699,7 @@ TEST(SVectorTest, MoveConstructor) { ASSERT_EQ(b.size(), 5); } -TEST(SVectorTest, MoveAssignmentOperator) { +TEST(SVectorTrackingTest, MoveAssignmentOperator) { TrackedObject::ResetCounters(); ASSERT_EQ(TrackedObject::Counters(), "constructions: 0, destructions: 0, moves: 0, copies: 0"); @@ -1011,6 +1060,28 @@ TEST(SVector, NoHeapCheckerFalsePositive) { EXPECT_EQ(kVector->size(), 5000); } +TEST(Permute, IntArray) { + int array[] = {4, 5, 6}; + std::vector permutation = {0, 2, 1}; + util::Permute(permutation, &array); + EXPECT_THAT(array, ElementsAre(4, 6, 5)); +} + +TEST(Permute, BoolVector) { + std::vector array = {true, false, true}; + std::vector permutation = {0, 2, 1}; + util::Permute(permutation, &array); + EXPECT_THAT(array, ElementsAre(true, true, false)); +} + +TEST(Permute, StrongVector) { + util_intops::StrongVector array = {4, 5, 6}; + std::vector permutation = {StrongArcId(0), StrongArcId(2), + StrongArcId(1)}; + util::Permute(permutation, &array); + EXPECT_THAT(array, ElementsAre(4, 6, 5)); +} + template static void BM_RandomArcs(benchmark::State& state) { const int kRandomSeed = 0; @@ -1304,4 +1375,23 @@ static void BM_CompleteBipartiteGraphTailHead(benchmark::State& state) { BENCHMARK_TEMPLATE(BM_CompleteBipartiteGraphTailHead, int32_t); BENCHMARK_TEMPLATE(BM_CompleteBipartiteGraphTailHead, int16_t); +template +void BM_Permute(benchmark::State& state) { + const int size = state.range(0); + ArrayT array(size); + + std::vector permutation(size); + absl::c_iota(permutation, IndexT(0)); + + for (const auto s : state) { + util::Permute(permutation, &array); + benchmark::DoNotOptimize(array); + benchmark::DoNotOptimize(permutation); + } +} +BENCHMARK(BM_Permute, StrongArcId>) + ->Arg(128); +BENCHMARK(BM_Permute, int>)->Arg(128); +BENCHMARK(BM_Permute, int>)->Arg(128); + } // namespace util diff --git a/ortools/graph/samples/assignment_linear_sum_assignment.py b/ortools/graph/samples/assignment_linear_sum_assignment.py index 82af30d560..c662741f64 100755 --- a/ortools/graph/samples/assignment_linear_sum_assignment.py +++ b/ortools/graph/samples/assignment_linear_sum_assignment.py @@ -18,6 +18,7 @@ import numpy as np from ortools.graph.python import linear_sum_assignment + # [END import] diff --git a/ortools/graph/samples/assignment_min_flow.py b/ortools/graph/samples/assignment_min_flow.py index 0d55ed20c9..1e4f56387a 100755 --- a/ortools/graph/samples/assignment_min_flow.py +++ b/ortools/graph/samples/assignment_min_flow.py @@ -16,6 +16,7 @@ """Linear assignment example.""" # [START import] from ortools.graph.python import min_cost_flow + # [END import] diff --git a/ortools/graph/samples/balance_min_flow.py b/ortools/graph/samples/balance_min_flow.py index 923ff22a85..688c9c79ad 100755 --- a/ortools/graph/samples/balance_min_flow.py +++ b/ortools/graph/samples/balance_min_flow.py @@ -16,6 +16,7 @@ """Assignment with teams of workers.""" # [START import] from ortools.graph.python import min_cost_flow + # [END import] diff --git a/ortools/graph/samples/dijkstra_directed.cc b/ortools/graph/samples/dijkstra_directed.cc index 046f15d1d9..20c8a508c8 100644 --- a/ortools/graph/samples/dijkstra_directed.cc +++ b/ortools/graph/samples/dijkstra_directed.cc @@ -50,8 +50,8 @@ int main(int argc, char** argv) { // Solve the shortest path problem from 0 to 5. std::pair> result = - operations_research::SimpleOneToOneShortestPath(0, 5, tails, heads, - lengths); + operations_research::SimpleOneToOneShortestPath(0, 5, tails, + heads, lengths); // Print to length of the path and then the nodes in the path. std::cout << "Shortest path length: " << result.first << std::endl; diff --git a/ortools/graph/samples/dijkstra_undirected.cc b/ortools/graph/samples/dijkstra_undirected.cc index f51645691b..84bef36fee 100644 --- a/ortools/graph/samples/dijkstra_undirected.cc +++ b/ortools/graph/samples/dijkstra_undirected.cc @@ -59,8 +59,8 @@ int main(int argc, char** argv) { // Solve the shortest path problem from 0 to 4. std::pair> result = - operations_research::SimpleOneToOneShortestPath(0, 4, tails, heads, - lengths); + operations_research::SimpleOneToOneShortestPath(0, 4, tails, + heads, lengths); // Print to length of the path and then the nodes in the path. std::cout << "Shortest path length: " << result.first << std::endl; diff --git a/ortools/graph/samples/simple_max_flow_program.py b/ortools/graph/samples/simple_max_flow_program.py index 38bd192247..43820f3db8 100755 --- a/ortools/graph/samples/simple_max_flow_program.py +++ b/ortools/graph/samples/simple_max_flow_program.py @@ -18,6 +18,7 @@ import numpy as np from ortools.graph.python import max_flow + # [END import] diff --git a/ortools/graph/samples/simple_min_cost_flow_program.py b/ortools/graph/samples/simple_min_cost_flow_program.py index 4e0e0afd56..390e7bdbae 100755 --- a/ortools/graph/samples/simple_min_cost_flow_program.py +++ b/ortools/graph/samples/simple_min_cost_flow_program.py @@ -18,6 +18,7 @@ import numpy as np from ortools.graph.python import min_cost_flow + # [END import]