diff --git a/ortools/graph/BUILD.bazel b/ortools/graph/BUILD.bazel index e1427d5e58..67d996bbc4 100644 --- a/ortools/graph/BUILD.bazel +++ b/ortools/graph/BUILD.bazel @@ -11,7 +11,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -load("@rules_cc//cc:defs.bzl", "cc_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_proto_library") package(default_visibility = ["//visibility:public"]) @@ -65,10 +66,12 @@ cc_library( hdrs = ["bounded_dijkstra.h"], deps = [ ":graph", - "//ortools/base", "//ortools/base:iterator_adaptors", "//ortools/base:threadpool", + "@com_google_absl//absl/algorithm:container", "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/types:span", ], ) @@ -76,8 +79,9 @@ cc_library( name = "multi_dijkstra", hdrs = ["multi_dijkstra.h"], deps = [ - "//ortools/base", - "@com_google_absl//absl/container:flat_hash_set", + "//ortools/base:map_util", + "//ortools/base:types", + "@com_google_absl//absl/container:flat_hash_map", ], ) @@ -86,6 +90,7 @@ cc_library( hdrs = ["bidirectional_dijkstra.h"], deps = [ "//ortools/base", + "//ortools/base:iterator_adaptors", "//ortools/base:threadpool", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/strings", @@ -99,7 +104,7 @@ cc_library( hdrs = ["cliques.h"], deps = [ "//ortools/base", - "//ortools/base:intops", + "//ortools/base:int_type", "//ortools/base:strong_vector", "//ortools/util:time_limit", "@com_google_absl//absl/container:flat_hash_set", @@ -112,6 +117,7 @@ cc_library( hdrs = ["hamiltonian_path.h"], deps = [ "//ortools/base", + "//ortools/base:types", "//ortools/util:bitset", "//ortools/util:saturated_arithmetic", "//ortools/util:vector_or_function", @@ -123,11 +129,16 @@ cc_library( hdrs = ["christofides.h"], deps = [ ":eulerian_path", + ":graph", ":minimum_spanning_tree", ":perfect_matching", "//ortools/base", + "//ortools/base:types", "//ortools/linear_solver", "//ortools/linear_solver:linear_solver_cc_proto", + "//ortools/util:saturated_arithmetic", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", ], ) @@ -144,10 +155,10 @@ cc_library( hdrs = ["minimum_spanning_tree.h"], deps = [ ":connected_components", - ":graph", - "//ortools/base", "//ortools/base:adjustable_priority_queue", + "//ortools/base:types", "//ortools/util:vector_or_function", + "@com_google_absl//absl/types:span", ], ) @@ -157,8 +168,8 @@ cc_library( deps = [ ":christofides", ":minimum_spanning_tree", - "//ortools/base", - "@com_google_absl//absl/strings", + "//ortools/base:types", + "@com_google_absl//absl/types:span", ], ) @@ -167,8 +178,10 @@ cc_library( hdrs = ["ebert_graph.h"], deps = [ "//ortools/base", + "//ortools/base:types", "//ortools/util:permutation", "//ortools/util:zvector", + "@com_google_absl//absl/strings", ], ) @@ -181,12 +194,29 @@ cc_library( ":graph", "//ortools/base", "//ortools/base:adjustable_priority_queue", - "//ortools/base:file", "//ortools/base:map_util", "//ortools/base:stl_util", "//ortools/base:threadpool", "//ortools/base:timer", + "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/functional:bind_front", + "@com_google_absl//absl/log", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/types:span", + ], +) + +cc_library( + name = "k_shortest_paths", + hdrs = ["k_shortest_paths.h"], + deps = [ + ":bounded_dijkstra", + ":ebert_graph", + ":shortest_paths", + "@com_google_absl//absl/algorithm:container", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/types:span", ], ) @@ -212,9 +242,12 @@ cc_library( ":graph", ":graphs", "//ortools/base", + "//ortools/base:types", "//ortools/util:stats", "//ortools/util:zvector", "@com_google_absl//absl/memory", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", ], ) @@ -228,17 +261,15 @@ cc_test( ":graphs", ":max_flow", "//ortools/base", - "//ortools/base:gmock", - "//ortools/base:message_matchers", + "//ortools/base:gmock_main", "//ortools/base:path", - "//ortools/base:status_matchers", "//ortools/linear_solver", "//ortools/util:file_util", "@com_google_absl//absl/flags:flag", "@com_google_absl//absl/random", "@com_google_absl//absl/strings:str_format", "@com_google_benchmark//:benchmark", - "@com_google_googletest//:gtest_main", + "@com_google_protobuf//:protobuf", ], ) @@ -254,7 +285,6 @@ cc_library( "//conditions:default": [], }), deps = [ - ":connected_components", ":ebert_graph", ":graph", ":graphs", @@ -262,9 +292,13 @@ cc_library( "//ortools/base", "//ortools/base:dump_vars", "//ortools/base:mathutil", + "//ortools/base:types", "//ortools/util:saturated_arithmetic", "//ortools/util:stats", "//ortools/util:zvector", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", ], ) @@ -279,7 +313,8 @@ cc_binary( ":min_cost_flow", "//ortools/base", "//ortools/base:file", - "//ortools/base:filesystem", + "//ortools/base:status_macros", + "//ortools/base:timer", "//ortools/util:filelineiter", "//ortools/util:stats", "@com_google_absl//absl/flags:flag", @@ -298,7 +333,7 @@ cc_library( deps = [ ":ebert_graph", ":linear_assignment", - "//ortools/base", + "@com_google_absl//absl/flags:flag", ], ) @@ -314,50 +349,11 @@ cc_library( "//ortools/base:types", "//ortools/util:permutation", "//ortools/util:zvector", - "@com_google_absl//absl/strings", + "@com_google_absl//absl/flags:flag", "@com_google_absl//absl/strings:str_format", ], ) -# Biconnected -#cc_library( -# name = "biconnected", -# srcs = ["biconnected.cc"], -# hdrs = ["biconnected.h"], -# deps = [ -# ":ebert_graph", -# "//ortools/base", -# "//ortools/base:types", -# ], -#) - -# Hopcroft-Karp (Old) -#cc_library( -# name = "hopcroft_karp", -# srcs = ["hopcroft_karp.c"], -# hdrs = ["hopcroft_karp.h"], -#) - -# Hopcroft-Karp (New) -#cc_library( -# name = "bipartite_matching", -# srcs = ["bipartite_matching.cc"], -# hdrs = ["bipartite_matching.h"], -# deps = [ -# "//ortools/base", -# ], -#) - -#cc_library( -# name = "dag_connectivity", -# srcs = ["dag_connectivity.cc"], -# hdrs = ["dag_connectivity.h"], -# deps = [ -# ":topologicalsorter", -# "//ortools/base", -# ], -#) - cc_library( name = "perfect_matching", srcs = ["perfect_matching.cc"], @@ -365,7 +361,7 @@ cc_library( deps = [ "//ortools/base", "//ortools/base:adjustable_priority_queue", - "//ortools/base:intops", + "//ortools/base:int_type", "//ortools/base:strong_vector", "//ortools/base:types", "//ortools/util:saturated_arithmetic", @@ -382,6 +378,8 @@ cc_library( deps = [ ":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", @@ -392,12 +390,16 @@ cc_library( cc_library( name = "dag_constrained_shortest_path", + testonly = True, srcs = ["dag_constrained_shortest_path.cc"], hdrs = ["dag_constrained_shortest_path.h"], deps = [ ":dag_shortest_path", ":graph", ":topologicalsorter", + "@com_google_absl//absl/algorithm:container", + "@com_google_absl//absl/base:log_severity", + "@com_google_absl//absl/log:check", "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/types:span", @@ -441,6 +443,21 @@ cc_library( hdrs = ["iterators.h"], ) +cc_library( + name = "random_graph", + srcs = ["random_graph.cc"], + hdrs = ["random_graph.h"], + deps = [ + ":graph", + "//ortools/base:logging", + "//ortools/base:types", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/memory", + "@com_google_absl//absl/random", + "@com_google_absl//absl/random:bit_gen_ref", + ], +) + cc_library( name = "strongly_connected_components", hdrs = [ diff --git a/ortools/graph/CMakeLists.txt b/ortools/graph/CMakeLists.txt index d9b638e9d7..2b17e37e7b 100644 --- a/ortools/graph/CMakeLists.txt +++ b/ortools/graph/CMakeLists.txt @@ -23,6 +23,7 @@ list(REMOVE_ITEM _SRCS ${CMAKE_CURRENT_SOURCE_DIR}/ebert_graph_test.cc ${CMAKE_CURRENT_SOURCE_DIR}/eulerian_path_test.cc ${CMAKE_CURRENT_SOURCE_DIR}/hamiltonian_path_test.cc + ${CMAKE_CURRENT_SOURCE_DIR}/k_shortest_paths_test.cc ${CMAKE_CURRENT_SOURCE_DIR}/linear_assignment_test.cc ${CMAKE_CURRENT_SOURCE_DIR}/max_flow_test.cc ${CMAKE_CURRENT_SOURCE_DIR}/min_cost_flow_test.cc diff --git a/ortools/graph/README.md b/ortools/graph/README.md index 59d10b728c..e8940279a7 100644 --- a/ortools/graph/README.md +++ b/ortools/graph/README.md @@ -6,8 +6,8 @@ network flow problems. It contains in particular: * well-tuned algorithms (for example, shortest paths and - [Hamiltonian paths](https://en.wikipedia.org/wiki/Hamiltonian_path)). -* hard-to-find algorithms (Hamiltonian paths, push-relabel flow algorithms). + [Hamiltonian paths](https://en.wikipedia.org/wiki/Hamiltonian_path)). +* hard-to-find algorithms (Hamiltonian paths, push-relabel flow algorithms). * other, more common algorithms, that are useful to use with `EbertGraph`. Graph representations: @@ -69,11 +69,11 @@ Flow algorithms: * [`linear_assignment.h`][linear_assignment_h]: entry point for solving linear sum assignment problems (classical assignment problems where the total cost is the sum of the costs of each arc used) on directed graphs with arc costs, - based on the Goldberg-Kennedy push-relabel algorithm. + based on the Goldberg-Kennedy push-relabel algorithm. * [`max_flow.h`][max_flow_h]: entry point for computing maximum flows on - directed graphs with arc capacities, based on the Goldberg-Tarjan - push-relabel algorithm. + directed graphs with arc capacities, based on the Goldberg-Tarjan + push-relabel algorithm. * [`min_cost_flow.h`][min_cost_flow_h]: entry point for computing minimum-cost flows on directed graphs with arc capacities, arc costs, and diff --git a/ortools/graph/bidirectional_dijkstra_test.cc b/ortools/graph/bidirectional_dijkstra_test.cc index 9ce1d12ac1..87598aea41 100644 --- a/ortools/graph/bidirectional_dijkstra_test.cc +++ b/ortools/graph/bidirectional_dijkstra_test.cc @@ -20,15 +20,14 @@ #include #include +#include "absl/base/log_severity.h" #include "absl/container/flat_hash_map.h" #include "absl/random/distributions.h" #include "absl/strings/str_cat.h" #include "gtest/gtest.h" #include "ortools/base/gmock.h" -#include "ortools/base/map_util.h" #include "ortools/graph/bounded_dijkstra.h" #include "ortools/graph/graph.h" -#include "util/tuple/dump_vars.h" namespace operations_research { namespace { @@ -100,9 +99,6 @@ TEST(BidirectionalDijkstraTest, SmallTest) { TEST(BidirectionalDijkstraTest, RandomizedCorrectnessTest) { std::mt19937 random(12345); - // Performance on forge as of 2016-10-05 with these numbers, over 1000 runs: - // - fastbuild: max = 21.9s, avg = 10.7s. - // - opt: max = 23.2s, avg = 10.4s. const int kNumGraphs = DEBUG_MODE ? 100 : 300; const int kNumQueriesPerGraph = DEBUG_MODE ? 10 : 30; const int kNumNodes = 1000; diff --git a/ortools/graph/k_shortest_paths.h b/ortools/graph/k_shortest_paths.h new file mode 100644 index 0000000000..1c017647c2 --- /dev/null +++ b/ortools/graph/k_shortest_paths.h @@ -0,0 +1,444 @@ +// Copyright 2010-2024 Google LLC +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Algorithms to compute k-shortest paths. Currently, only Yen's algorithm is +// implemented. +// +// TODO(user): implement Lawler's modification: +// https://pubsonline.informs.org/doi/abs/10.1287/mnsc.18.7.401 +// +// | Algo. | Neg. weights | Neg.-weight loops | Graph type | Loopless paths | +// |-------|--------------|-------------------|--------------|----------------| +// | Yen | No | No | (Un)directed | Yes | +// +// +// Design choices +// ============== +// +// The design takes some inspiration from `shortest_paths.h` and +// `bounded_dijkstra.h`, but the shortest-path and k-shortest-path problems have +// vastly different structures. +// For instance, a path container that only stores distances, like +// `DistanceContainer` in `shortest_paths.h`, is irrelevant as an output for +// this problem: it can only characterize one path, the shortest one. +// This is why the results are stored in an intermediate structure, containing +// the paths (as a sequence of nodes, just like `PathContainerImpl` subclasses) +// and their distance. +// +// Only the one-to-one k-shortest-path problem is well-defined. Variants with +// multiple sources and/or destinations pose representational challenges whose +// solution is likely to be algorithm-dependent. +// Optimizations of path storage such as `PathTree` are not general enough to +// store k shortest paths: the set of paths for a given index for many +// sources/destinations is not ensured to form a set for each index. (While the +// first paths will form such a tree, storing *different* second paths for each +// source-destination pair may be impossible to do in a tree.) +// +// Unlike the functions in `shortest_paths.h`, the functions in this file +// directly return their result, to follow the current best practices. + +#ifndef OR_TOOLS_GRAPH_K_SHORTEST_PATHS_H_ +#define OR_TOOLS_GRAPH_K_SHORTEST_PATHS_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "absl/algorithm/container.h" +#include "absl/base/optimization.h" +#include "absl/log/check.h" +#include "absl/types/span.h" +#include "ortools/graph/bounded_dijkstra.h" +#include "ortools/graph/ebert_graph.h" +#include "ortools/graph/shortest_paths.h" + +namespace operations_research { + +// Stores the solution to a k-shortest path problem. `paths` contains up to `k` +// paths from `source` to `destination` (these nodes are arguments to the +// algorithm), each having a distance stored in `distances`. +// +// The paths in `paths` start with `origin` and end at `destination`. +// +// If the computations are unsuccessful for any reason, the vectors are empty. +struct KShortestPaths { + // The paths are stored as vectors of nodes, like the other graph algorithms. + // TODO(user): what about vectors of arcs? That might be faster + // (potentially, add a function to transform it into a vector of nodes if the + // user really needs it). It would also have the nice benefit of removing the + // need for `distances` (compute it on the fly), with a reference to the graph + // and the costs. + std::vector> paths; + std::vector distances; +}; + +// Computes up to k shortest paths from the node `source` to the node +// `destination` in the given directed `graph`. The paths are guaranteed not to +// have loops. +// +// Hypotheses on input (which are not checked at runtime): +// - No multigraphs (more than one edge or a pair of nodes). The behavior is +// undefined otherwise. +// - The `arc_lengths` are supposed to be nonnegative. The behavior is +// undefined otherwise. +// TODO(user): relax to "no negative-weight cycles" (no Dijkstra). +// - The graphs might have loops. +// +// This function uses Yen's algorithm, which guarantees to find the first k +// shortest paths in O(k n (m + n log n)) for n nodes and m edges. This +// algorithm is an implementation of the idea of detours. +// +// Yen, Jin Y. "Finding the k Shortest Loopless Paths in a Network". Management +// Science. 17 (11): 712–716, 1971. +// https://doi.org/10.1287%2Fmnsc.17.11.712 +template +KShortestPaths YenKShortestPaths(const GraphType& graph, + const std::vector& arc_lengths, + NodeIndex source, NodeIndex destination, + unsigned k); + +// End of the interface. Below is the implementation. + +// TODO(user): introduce an enum to choose the algorithm. It's useless as +// long as this file only provides Yen. + +namespace internal { + +const PathDistance kMaxDistance = std::numeric_limits::max() - 1; +const PathDistance kDisconnectedDistance = + std::numeric_limits::max(); + +// Determines the arc index from a source to a destination. +// +// This operation requires iterating through the set of outgoing arcs from the +// source node, which might be expensive. +// +// In a multigraph, this function returns an index for one of the edges between +// the source and the destination. +template +ArcIndex FindArcIndex(const GraphType& graph, const NodeIndex source, + const NodeIndex destination) { + const auto outgoing_arcs_iter = graph.OutgoingArcs(source); + const auto arc = + std::find_if(outgoing_arcs_iter.begin(), outgoing_arcs_iter.end(), + [&graph, destination](const ArcIndex arc) { + return graph.Head(arc) == destination; + }); + return (arc != outgoing_arcs_iter.end()) ? *arc : GraphType::kNilArc; +} + +// Determines the shortest path from the given source and destination, returns a +// tuple with the path (as a vector of node indices) and its cost. +template +std::tuple, PathDistance> ComputeShortestPath( + const GraphType& graph, const std::vector& arc_lengths, + const NodeIndex source, const NodeIndex destination) { + BoundedDijkstraWrapper dijkstra(&graph, + &arc_lengths); + dijkstra.RunBoundedDijkstra(source, kMaxDistance); + const PathDistance path_length = dijkstra.distances()[destination]; + + if (path_length >= kMaxDistance) { + // There are shortest paths in this graph, just not from the source to this + // destination. + // This case only happens when some arcs have an infinite length (i.e. + // larger than `kMaxDistance`): `BoundedDijkstraWrapper::NodePathTo` fails + // to return a path, even empty. + return {{}, kDisconnectedDistance}; + } + + if (std::vector path = std::move(dijkstra.NodePathTo(destination)); + !path.empty()) { + return {std::move(path), path_length}; + } else { + return {{}, kDisconnectedDistance}; + } +} + +// Computes the total length of a path. +template +PathDistance ComputePathLength(const GraphType& graph, + const absl::Span arc_lengths, + const absl::Span path) { + PathDistance distance = 0; + for (NodeIndex i = 0; i < path.size() - 1; ++i) { + const ArcIndex arc = internal::FindArcIndex(graph, path[i], path[i + 1]); + DCHECK_NE(arc, GraphType::kNilArc); + distance += arc_lengths[arc]; + } + return distance; +} + +// Stores a path with a priority (typically, the distance), with a comparison +// operator that operates on the priority. +class PathWithPriority { + public: + PathWithPriority(PathDistance priority, std::vector path) + : path_(std::move(path)), priority_(priority) {} + bool operator<(const PathWithPriority& other) const { + return priority_ < other.priority_; + } + + [[nodiscard]] const std::vector& path() const { return path_; } + [[nodiscard]] PathDistance priority() const { return priority_; } + + private: + std::vector path_; + PathDistance priority_; +}; + +// Container adapter to be used with STL container adapters such as +// std::priority_queue. It gives access to the underlying container, which is a +// protected member in a standard STL container adapter. +template +class UnderlyingContainerAdapter : public Container { + public: + typedef typename Container::container_type container_type; + // No mutable version of `container`, so that the user cannot change the data + // within the container: they might destroy the container's invariants. + [[nodiscard]] const container_type& container() const { return this->c; } +}; + +} // namespace internal + +// TODO(user): Yen's algorithm can work with negative weights, but +// Dijkstra cannot. +// +// Yen, Jin Y. "Finding the k Shortest Loopless Paths in a Network". Management +// Science. 17 (11): 712–716, 1971. +// https://doi.org/10.1287%2Fmnsc.17.11.712 +// +// Yen's notations: +// - Source node: (1). +// - Destination node: (N). +// - Path from (1) to (j): (1) - (i) - ... - (j). +// - Cost for following the arc from (i) to (j), potentially negative: d_ij. +// - k-th shortest path: A^k == (1) - (2^k) - (3^k) - ... - (Q_k^k) - (N). +// - Deviation from A^k-1 at (i): A_i^k. This is the shortest path from (1) to +// (N) that is identical to A^k-1 from (1) to (i^k-1), then different from all +// the first k-1 shortest paths {A^1, A^2, ..., A^k-1}. +// - Root of A_i^k: R_i^k. This is the first subpath of A_i^k that coincides +// with A^k-1, i.e. A_i^k until i^k-1. +// - Spur of A_i^k: S_i^k. This is the last subpart of A_i^k with only one node +// coinciding with A_i^k, (i^k-1), i.e. A_i^k from i^k-1 onwards. +// +// Example graph, paths from A to H (more classical notations): +// C - D +// / / \ +// A - B / G - H +// \ / / +// E - F +// Source node: A. Destination node: H. +// Three paths from A to H, say they are ordered from the cheapest to the most +// expensive: +// - 1st path: A - B - C - D - G - H +// - 2nd path: A - B - E - F - G - H +// - 3rd path: A - B - E - D - G - H +// To start with, Yen's algorithm uses the shortest path: +// A^1 = A - B - C - D - G - H +// To compute the second path A^2, compute a detour around A^1. Consider the +// iteration where B is the spur node. +// - Spur node: 2^1 = B. +// - Root of A^1_2: R_1^2 = A - B (including the spur node 2^1 = B). +// - Spur path S_1^2 starts at the spur node 2^1 = B. There are two possible +// spur paths, the cheapest being: +// S_1^2 = B - E - F - G - H +template +KShortestPaths YenKShortestPaths(const GraphType& graph, + const std::vector& arc_lengths, + NodeIndex source, NodeIndex destination, + unsigned k) { + CHECK_GT(internal::kDisconnectedDistance, internal::kMaxDistance); + + CHECK_GE(k, 0) << "k must be nonnegative. Input value: " << k; + CHECK_NE(k, 0) << "k cannot be zero: you are requesting zero paths!"; + + 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_GE(source, 0) << "The source node must be nonnegative. Input value: " + << source; + CHECK_LT(source, graph.num_nodes()) + << "The source node must be a valid node. Input value: " << source + << ". Number of nodes in the input graph: " << graph.num_nodes(); + CHECK_GE(destination, 0) + << "The source node must be nonnegative. Input value: " << destination; + CHECK_LT(destination, graph.num_nodes()) + << "The destination node must be a valid node. Input value: " + << destination + << ". Number of nodes in the input graph: " << graph.num_nodes(); + + KShortestPaths paths; + + // First step: compute the shortest path. + { + std::tuple, PathDistance> first_path = + internal::ComputeShortestPath(graph, arc_lengths, source, destination); + if (std::get<0>(first_path).empty()) return paths; + paths.paths.push_back(std::move(std::get<0>(first_path))); + paths.distances.push_back(std::get<1>(first_path)); + } + + if (k == 1) { + return paths; + } + + // Generate variant paths. + internal::UnderlyingContainerAdapter< + std::priority_queue> + variant_path_queue; + + for (; k > 0; --k) { + // Generate variant paths from the last shortest path. + const absl::Span last_shortest_path = + absl::MakeSpan(paths.paths.back()); + + // TODO(user): think about adding parallelism for this loop to improve + // running times. + for (int spur_node_position = 0; + spur_node_position < last_shortest_path.size() - 1; + ++spur_node_position) { + if (spur_node_position > 0) { + DCHECK_NE(last_shortest_path[spur_node_position], source); + } + DCHECK_NE(last_shortest_path[spur_node_position], destination); + + const NodeIndex spur_node = last_shortest_path[spur_node_position]; + // Consider the part of the last shortest path up to and excluding the + // spur node. If spur_node_position == 0, this span only contains the + // source node. + const absl::Span root_path = + last_shortest_path.subspan(0, spur_node_position + 1); + DCHECK_GE(root_path.length(), 1); + DCHECK_NE(root_path.back(), destination); + + // Simplify the graph to have different paths using infinite lengths: + // copy the weights, set some of them to infinity. There is no need to + // restore the graph to its previous state in this case. + // + // This trick is used in the original article (it's old-fashioned), but + // not in Wikipedia's pseudocode (it prefers mutating the graph, which is + // harder to do without copying the whole graph structure). + // Copying the whole graph might be quite expensive, especially as it is + // not useful for long (computing one shortest path). + std::vector arc_lengths_for_detour = arc_lengths; + for (absl::Span previous_path : paths.paths) { + // Check among the previous paths: if part of the path coincides with + // the first few nodes up to the spur node (included), forbid this part + // of the path in the search for the next shortest path. More + // precisely, in that case, avoid the arc from the spur node to the + // next node in the path. + if (previous_path.size() < spur_node_position) continue; + const bool has_same_prefix_as_root_path = std::equal( + root_path.begin(), root_path.end(), previous_path.begin(), + previous_path.begin() + root_path.length()); + if (has_same_prefix_as_root_path) { + const ArcIndex after_spur_node_arc = + internal::FindArcIndex(graph, previous_path[spur_node_position], + previous_path[spur_node_position + 1]); + arc_lengths_for_detour[after_spur_node_arc] = + internal::kDisconnectedDistance; + } + } + + // Generate a new candidate path from the spur node to the destination + // without using the forbidden arcs. + { + std::tuple, PathDistance> detour_path = + internal::ComputeShortestPath(graph, arc_lengths_for_detour, + spur_node, destination); + + if (std::get<0>(detour_path).empty()) { + // Node unreachable after some arcs are forbidden. + continue; + } + std::vector spur_path = std::move(std::get<0>(detour_path)); + if (ABSL_PREDICT_FALSE(spur_path.empty())) continue; + +#ifndef NDEBUG + CHECK_EQ(root_path.back(), spur_path.front()); + + if (spur_path.size() == 1) { + CHECK_EQ(spur_path.front(), destination); + } else { + // Ensure there is an edge between the end of the root path + // and the beginning of the spur path (knowing that both subpaths + // coincide at the spur node). + const bool root_path_leads_to_spur_path = absl::c_any_of( + graph.OutgoingArcs(root_path.back()), + [&graph, node_after_spur_in_spur_path = + *(spur_path.begin() + 1)](const ArcIndex arc_index) { + return graph.Head(arc_index) == node_after_spur_in_spur_path; + }); + CHECK(root_path_leads_to_spur_path); + } +#endif // !defined(NDEBUG) + + // Assemble the new path. + std::vector new_path; + absl::c_copy(root_path.subspan(0, spur_node_position), + std::back_inserter(new_path)); + absl::c_copy(spur_path, std::back_inserter(new_path)); + + DCHECK_EQ(new_path.front(), source); + DCHECK_EQ(new_path.back(), destination); + + // Ensure the new path is not one of the previously known ones. This + // operation is required, as there are two sources of paths from the + // source to the destination: + // - `paths`, the list of paths that is output by the function: there + // is no possible duplicate due to `arc_lengths_for_detour`, where + // edges that might generate a duplicate path are forbidden. + // - `variant_path_queue`, the list of potential paths, ordered by + // their cost, with no impact on `arc_lengths_for_detour`. + // TODO(user): would it be faster to fingerprint the paths and + // filter by fingerprints? Due to the probability of error with + // fingerprints, still use this slow-but-exact code, but after + // filtering. + const bool is_new_path_already_known = + std::any_of(variant_path_queue.container().cbegin(), + variant_path_queue.container().cend(), + [&new_path](const internal::PathWithPriority& element) { + return element.path() == new_path; + }); + if (is_new_path_already_known) continue; + + const PathDistance path_length = + internal::ComputePathLength(graph, arc_lengths, new_path); + variant_path_queue.emplace( + /*priority=*/path_length, /*path=*/std::move(new_path)); + } + } + + // Add the shortest spur path ever found that has not yet been added. This + // can be a spur path that has just been generated or a previous one, if + // this iteration found no shorter one. + if (variant_path_queue.empty()) break; + + const internal::PathWithPriority& next_shortest_path = + variant_path_queue.top(); + paths.paths.emplace_back(next_shortest_path.path()); + paths.distances.push_back(next_shortest_path.priority()); + variant_path_queue.pop(); + } + + return paths; +} + +} // namespace operations_research + +#endif // OR_TOOLS_GRAPH_K_SHORTEST_PATHS_H_ diff --git a/ortools/graph/k_shortest_paths_test.cc b/ortools/graph/k_shortest_paths_test.cc new file mode 100644 index 0000000000..f1b4808fbf --- /dev/null +++ b/ortools/graph/k_shortest_paths_test.cc @@ -0,0 +1,171 @@ +// Copyright 2010-2024 Google LLC +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ortools/graph/k_shortest_paths.h" + +#include + +#include "gtest/gtest.h" +#include "ortools/base/gmock.h" +#include "ortools/graph/graph.h" +#include "ortools/graph/shortest_paths.h" + +namespace operations_research { +namespace { + +using testing::ElementsAre; +using util::StaticGraph; + +TEST(KShortestPathsYenDeathTest, EmptyGraph) { + StaticGraph<> graph; + std::vector lengths; + + EXPECT_DEATH(YenKShortestPaths(graph, lengths, /*source=*/0, + /*destination=*/1, /*k=*/10), + "graph.num_nodes\\(\\) > 0"); +} + +TEST(KShortestPathsYenDeathTest, NoArcGraph) { + StaticGraph<> graph; + graph.AddNode(1); + (void)graph.Build(); + std::vector lengths; + + EXPECT_DEATH(YenKShortestPaths(graph, lengths, /*source=*/0, + /*destination=*/1, /*k=*/10), + "graph.num_arcs\\(\\) > 0"); +} + +TEST(KShortestPathsYenDeathTest, NonExistingSourceBecauseNegative) { + StaticGraph<> graph; + graph.AddNode(1); + graph.AddArc(0, 1); + (void)graph.Build(); + std::vector lengths{0}; + + EXPECT_DEATH(YenKShortestPaths(graph, lengths, /*source=*/-1, + /*destination=*/1, /*k=*/10), + "source >= 0"); +} + +TEST(KShortestPathsYenDeathTest, NonExistingSourceBecauseTooLarge) { + StaticGraph<> graph; + graph.AddNode(1); + graph.AddArc(0, 1); + (void)graph.Build(); + std::vector lengths{0}; + + EXPECT_DEATH(YenKShortestPaths(graph, lengths, /*source=*/1'000, + /*destination=*/1, /*k=*/10), + "source < graph.num_nodes()"); +} + +TEST(KShortestPathsYenDeathTest, NonExistingDestinationBecauseNegative) { + StaticGraph<> graph; + graph.AddNode(1); + graph.AddArc(0, 1); + (void)graph.Build(); + std::vector lengths{0}; + + EXPECT_DEATH(YenKShortestPaths(graph, lengths, /*source=*/0, + /*destination=*/-1, /*k=*/10), + "destination >= 0"); +} + +TEST(KShortestPathsYenDeathTest, NonExistingDestinationBecauseTooLarge) { + StaticGraph<> graph; + graph.AddNode(1); + graph.AddArc(0, 1); + (void)graph.Build(); + std::vector lengths{0}; + + EXPECT_DEATH(YenKShortestPaths(graph, lengths, /*source=*/0, + /*destination=*/1'000, /*k=*/10), + "destination < graph.num_nodes()"); +} + +TEST(KShortestPathsYenDeathTest, KEqualsZero) { + StaticGraph<> graph; + graph.AddArc(0, 1); + graph.AddArc(1, 2); + (void)graph.Build(); + std::vector lengths{1, 1}; + + EXPECT_DEATH(YenKShortestPaths(graph, lengths, /*source=*/0, + /*destination=*/2, /*k=*/0), + "k != 0"); +} + +TEST(KShortestPathsYenTest, ReducesToShortestPath) { + StaticGraph<> graph; + graph.AddArc(0, 1); + graph.AddArc(1, 2); + (void)graph.Build(); + std::vector lengths{1, 1}; + + const KShortestPaths paths = YenKShortestPaths(graph, lengths, /*source=*/0, + /*destination=*/2, /*k=*/1); + EXPECT_THAT(paths.paths, ElementsAre(std::vector{0, 1, 2})); + EXPECT_THAT(paths.distances, ElementsAre(2)); +} + +TEST(KShortestPathsYenTest, OnlyHasOnePath) { + StaticGraph<> graph; + graph.AddArc(0, 1); + graph.AddArc(1, 2); + (void)graph.Build(); + std::vector lengths{1, 1}; + + const KShortestPaths paths = YenKShortestPaths(graph, lengths, /*source=*/0, + /*destination=*/2, /*k=*/10); + EXPECT_THAT(paths.paths, ElementsAre(std::vector{0, 1, 2})); + EXPECT_THAT(paths.distances, ElementsAre(2)); +} + +TEST(KShortestPathsYenTest, HasTwoPaths) { + StaticGraph<> graph; + graph.AddArc(0, 1); + graph.AddArc(0, 2); + graph.AddArc(1, 2); + (void)graph.Build(); + std::vector lengths{1, 30, 1}; + + const KShortestPaths paths = YenKShortestPaths(graph, lengths, /*source=*/0, + /*destination=*/2, /*k=*/10); + EXPECT_THAT(paths.paths, + ElementsAre(std::vector{0, 1, 2}, std::vector{0, 2})); + EXPECT_THAT(paths.distances, ElementsAre(2, 30)); +} + +TEST(KShortestPathsYenTest, HasTwoPathsWithLongerPath) { + StaticGraph<> graph; + graph.AddArc(0, 1); + graph.AddArc(0, 4); + graph.AddArc(1, 2); + graph.AddArc(2, 3); + graph.AddArc(3, 4); + (void)graph.Build(); + std::vector lengths{1, 30, 1, 1, 1}; + + const KShortestPaths paths = YenKShortestPaths(graph, lengths, /*source=*/0, + /*destination=*/4, /*k=*/10); + EXPECT_THAT(paths.paths, ElementsAre(std::vector{0, 1, 2, 3, 4}, + std::vector{0, 4})); + EXPECT_THAT(paths.distances, ElementsAre(4, 30)); +} + +// TODO(user): randomized tests? Check validity with exhaustive +// exploration/IP formulation? + +} // namespace +} // namespace operations_research diff --git a/ortools/graph/linear_assignment_test.cc b/ortools/graph/linear_assignment_test.cc index 133ce2bf2d..5212ceefd0 100644 --- a/ortools/graph/linear_assignment_test.cc +++ b/ortools/graph/linear_assignment_test.cc @@ -19,6 +19,7 @@ #include #include "absl/random/distributions.h" +#include "absl/types/span.h" #include "benchmark/benchmark.h" #include "gtest/gtest.h" #include "ortools/base/commandlineflags.h" @@ -743,7 +744,7 @@ class ReorderedGraphTest : public testing::Test { ReorderedGraphTest() {} - void TestMe(const size_t left_nodes, const std::vector& ordered_edges) { + void TestMe(const size_t left_nodes, absl::Span ordered_edges) { std::vector edge_costs; typedef util::StaticGraph GraphType; GraphType graph(2 * left_nodes, ordered_edges.size()); diff --git a/ortools/graph/max_flow_test.cc b/ortools/graph/max_flow_test.cc index 96de73f8e5..d08ebf7992 100644 --- a/ortools/graph/max_flow_test.cc +++ b/ortools/graph/max_flow_test.cc @@ -191,8 +191,8 @@ TEST(SimpleMaxFlowTest, ProblematicProblemWithMaxCapacity) { FlowModelProto model, ReadFileToProto( file::JoinPathRespectAbsolute(absl::GetFlag(FLAGS_test_srcdir), - "ortools/graph/" - "testdata/max_flow_test1.pb.txt"))); + "ortools/graph/" + "testdata/max_flow_test1.pb.txt"))); SimpleMaxFlow solver; EXPECT_EQ(SimpleMaxFlow::OPTIMAL, LoadAndSolveFlowModel(model, &solver)); EXPECT_EQ(10290243, solver.OptimalFlow()); diff --git a/ortools/graph/minimum_spanning_tree_test.cc b/ortools/graph/minimum_spanning_tree_test.cc index 88b7930105..257006ceb0 100644 --- a/ortools/graph/minimum_spanning_tree_test.cc +++ b/ortools/graph/minimum_spanning_tree_test.cc @@ -78,10 +78,10 @@ void CheckMSTWithKruskal(const ListGraph& graph, // Helper function to check the expected MST is obtained with Prim. void CheckMSTWithPrim(const ListGraph& graph, - const std::vector& costs, + absl::Span costs, const std::vector& expected_arcs) { const std::vector prim_mst = BuildPrimMinimumSpanningTree( - graph, [&costs](int arc) { return costs[arc]; }); + graph, [costs](int arc) { return costs[arc]; }); EXPECT_THAT(expected_arcs, UnorderedElementsAreArray(prim_mst)); } diff --git a/ortools/graph/one_tree_lower_bound.h b/ortools/graph/one_tree_lower_bound.h index 586f1f2291..dce6895072 100644 --- a/ortools/graph/one_tree_lower_bound.h +++ b/ortools/graph/one_tree_lower_bound.h @@ -335,10 +335,10 @@ int GetNodeMinimizingEdgeCostToSource(const GraphType& graph, int source, template std::vector ComputeOneTree(const GraphType& graph, const CostFunction& cost, - const std::vector& weights, - const std::vector& sorted_arcs, + absl::Span weights, + absl::Span sorted_arcs, CostType* one_tree_cost) { - const auto weighed_cost = [&cost, &weights](int from, int to) { + const auto weighed_cost = [&cost, weights](int from, int to) { return cost(from, to) + weights[from] + weights[to]; }; // Compute MST on graph. diff --git a/ortools/graph/one_tree_lower_bound_test.cc b/ortools/graph/one_tree_lower_bound_test.cc index 6f988a92fe..977b0c5236 100644 --- a/ortools/graph/one_tree_lower_bound_test.cc +++ b/ortools/graph/one_tree_lower_bound_test.cc @@ -23,7 +23,7 @@ #include "gtest/gtest.h" #include "ortools/base/path.h" #include "ortools/base/types.h" -#include "ortools/routing/tsplib_parser.h" +#include "ortools/routing/parsers/tsplib_parser.h" namespace operations_research { namespace { diff --git a/ortools/graph/perfect_matching_test.cc b/ortools/graph/perfect_matching_test.cc index 2485c87684..4602c4c7cc 100644 --- a/ortools/graph/perfect_matching_test.cc +++ b/ortools/graph/perfect_matching_test.cc @@ -248,7 +248,7 @@ std::vector GenerateAndLoadRandomProblem( // condition if really needed. This is a bit involved though, and with the MIP // tests below, we should have a good enough confidence in the code already. void CheckOptimalSolution(const MinCostPerfectMatching& matcher, - const std::vector& edges) { + absl::Span edges) { const std::vector& matches = matcher.Matches(); std::vector seen(matches.size(), false); int num_seen = 0; diff --git a/ortools/graph/random_graph.cc b/ortools/graph/random_graph.cc new file mode 100644 index 0000000000..dc458fff9d --- /dev/null +++ b/ortools/graph/random_graph.cc @@ -0,0 +1,173 @@ +// Copyright 2010-2024 Google LLC +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ortools/graph/random_graph.h" + +#include +#include +#include +#include +#include + +#include "absl/container/flat_hash_set.h" +#include "absl/memory/memory.h" +#include "absl/random/bit_gen_ref.h" +#include "absl/random/random.h" +#include "ortools/base/logging.h" +#include "ortools/base/types.h" + +namespace util { + +namespace { +// This function initializes the graph used by GenerateRandomMultiGraph() and +// GenerateRandomMultiGraph(), given their arguments. +// See the .h for documentation on those arguments. +std::unique_ptr> CreateGraphMaybeReserved(int num_nodes, + int num_arcs, + bool finalized, + absl::BitGenRef gen) { + std::unique_ptr> graph; + // We either "reserve" the number of nodes and arcs or not, depending on + // randomness and on the "finalized" bit (if false, we can't assume that the + // user won't add some nodes or arcs after we return the graph, so we can't + // cap those). + if (finalized && absl::Bernoulli(gen, 1.0 / 2)) { + graph = std::make_unique>(num_nodes, num_arcs); + } else { + graph = std::make_unique>(); + graph->AddNode(num_nodes - 1); + } + return graph; +} +} // namespace + +std::unique_ptr> GenerateRandomMultiGraph(int num_nodes, + int num_arcs, + bool finalized, + absl::BitGenRef gen) { + std::unique_ptr> graph = + CreateGraphMaybeReserved(num_nodes, num_arcs, finalized, gen); + if (num_nodes != 0) { + CHECK_GT(num_nodes, 0); + CHECK_GE(num_arcs, 0); + graph->AddNode(num_nodes - 1); + for (int a = 0; a < num_arcs; ++a) { + graph->AddArc(absl::Uniform(gen, 0, num_nodes), + absl::Uniform(gen, 0, num_nodes)); + } + } else { + CHECK_EQ(num_arcs, 0); + } + if (finalized) graph->Build(); + return graph; +} + +namespace { +// Parameterized method to generate both directed and undirected simple graphs. +std::unique_ptr> GenerateRandomSimpleGraph(int num_nodes, + int num_arcs, + bool finalized, + bool directed, + absl::BitGenRef gen) { + CHECK_GE(num_nodes, 0); + // For an undirected graph, the number of arcs should be even: a->b and b->a. + CHECK(directed || (num_arcs % 2 == 0)); + const int64_t max_num_arcs = + static_cast(num_nodes) * (num_nodes - 1); + CHECK_LE(num_arcs, max_num_arcs); + std::unique_ptr> graph = + CreateGraphMaybeReserved(num_nodes, num_arcs, finalized, gen); + + // If the number of arcs is greater than half the possible arcs of the graph, + // we generate the inverse graph and convert non-arcs to arcs. + if (num_arcs > max_num_arcs / 2) { + std::unique_ptr> inverse_graph = + GenerateRandomSimpleGraph(num_nodes, max_num_arcs - num_arcs, + /*finalized=*/true, directed, gen); + std::vector node_mask(num_nodes, false); + for (int from = 0; from < num_nodes; ++from) { + for (const int to : (*inverse_graph)[from]) { + node_mask[to] = true; + } + for (int to = 0; to < num_nodes; ++to) { + if (node_mask[to]) { + node_mask[to] = false; // So that the mask is reset to all false. + } else if (to != from) { + graph->AddArc(from, to); + } + } + } + if (finalized) graph->Build(); + return graph; + } + + // We use a trivial algorithm: pick an arc at random, uniformly, and add it to + // the graph unless it was already added. As we sometimes have to discard an + // arc, we expect to do this slightly more times than the desired number "m" + // of distinct arcs. But in the worst case, which is when m = M/2 (where M = + // N*(N-1) is the number of possible arcs), the expected number of steps is + // only ln(2)*M ~ 0.69*M, to produce 0.5*M arcs. So it's fine. + // + // Proof: The expected number of steps to get "m" distinct arcs across the M + // possible arcs is M/M + M/(M-1) + M/(M-2) + ... + M/(M-m+1), which is equal + // to M * (H(M) - H(M-m)), where H(x) is the harmonic sum up to x. + // H(M) - H(M-m) converges to ln(M) - ln(M-m) = ln(1 + m/(M-m)) as M grows, + // which stricly grows with m and is equal to ln(2) in the worst case m=M/2. + // + // NOTE(user): If some specialized users want a uniform generation method + // that uses less memory (this one uses a flat hash map on the arcs, which + // uses significant memory), it could be done. Reach out to me. + absl::flat_hash_set> arc_set; + // To detect bad user-provided random number generator which could lead to + // infinite loops, we bound the number of iterations to a value well beyond + // the expected number of iterations (which is less than 0.69 * max_num_arcs). + int64_t num_iterations = 0; + const int64_t max_num_iterations = 1000 + max_num_arcs; + while (graph->num_arcs() < num_arcs) { + ++num_iterations; + CHECK_LE(num_iterations, max_num_iterations) + << "The random number generator supplied to GenerateRandomSimpleGraph()" + << " is likely biased or broken."; + const int tail = absl::Uniform(gen, 0, num_nodes); + const int head = absl::Uniform(gen, 0, num_nodes); + if (tail == head) continue; + if (directed) { + if (!arc_set.insert({tail, head}).second) continue; + graph->AddArc(tail, head); + } else { // undirected + const std::pair arc = { + std::min(tail, head), + std::max(tail, head)}; // Canonic edge representative. + if (!arc_set.insert(arc).second) continue; + graph->AddArc(tail, head); + graph->AddArc(head, tail); + } + } + if (finalized) graph->Build(); + return graph; +} +} // namespace + +std::unique_ptr> GenerateRandomDirectedSimpleGraph( + int num_nodes, int num_arcs, bool finalized, absl::BitGenRef gen) { + return GenerateRandomSimpleGraph(num_nodes, num_arcs, finalized, + /*directed=*/true, gen); +} + +std::unique_ptr> GenerateRandomUndirectedSimpleGraph( + int num_nodes, int num_edges, bool finalized, absl::BitGenRef gen) { + return GenerateRandomSimpleGraph(num_nodes, 2 * num_edges, finalized, + /*directed=*/false, gen); +} + +} // namespace util diff --git a/ortools/graph/random_graph.h b/ortools/graph/random_graph.h new file mode 100644 index 0000000000..b22e012568 --- /dev/null +++ b/ortools/graph/random_graph.h @@ -0,0 +1,51 @@ +// Copyright 2010-2024 Google LLC +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// A collection of functions to be used in unit tests involving the +// ortools/graph/... library. + +#ifndef UTIL_GRAPH_RANDOM_GRAPH_H_ +#define UTIL_GRAPH_RANDOM_GRAPH_H_ + +#include + +#include "absl/random/bit_gen_ref.h" +#include "ortools/graph/graph.h" + +namespace util { + +// Generates a random graph where multi-arcs and self-arcs are allowed (and +// therefore expected): exactly "num_arcs" are generated, each from a node +// picked uniformly at random to another node picked uniformly at random. +// Calls Build() on the graph iff "finalized" is true. +std::unique_ptr> GenerateRandomMultiGraph(int num_nodes, + int num_arcs, + bool finalized, + absl::BitGenRef gen); + +// Like GenerateRandomMultiGraph(), but with neither multi-arcs nor self-arcs: +// the generated graph will have exactly num_arcs arcs. It will be picked +// uniformly at random from the set of all simple graphs with that number of +// nodes and arcs. +std::unique_ptr> GenerateRandomDirectedSimpleGraph( + int num_nodes, int num_arcs, bool finalized, absl::BitGenRef gen); + +// Like GenerateRandomDirectedSimpleGraph(), but where an undirected edge is +// represented by two arcs: a->b and b->a. As a result, the amount of arcs in +// the generated graph is 2*num_edges. +std::unique_ptr> GenerateRandomUndirectedSimpleGraph( + int num_nodes, int num_edges, bool finalized, absl::BitGenRef gen); + +} // namespace util + +#endif // UTIL_GRAPH_RANDOM_GRAPH_H_ diff --git a/ortools/graph/shortest_paths.cc b/ortools/graph/shortest_paths.cc index c4a47c905e..c5978f06e7 100644 --- a/ortools/graph/shortest_paths.cc +++ b/ortools/graph/shortest_paths.cc @@ -21,6 +21,7 @@ #include "absl/container/flat_hash_map.h" #include "absl/functional/bind_front.h" #include "absl/log/check.h" +#include "absl/types/span.h" #include "ortools/base/adjustable_priority_queue-inl.h" #include "ortools/base/adjustable_priority_queue.h" #include "ortools/base/logging.h" @@ -105,8 +106,8 @@ class PathTree { public: PathTree() : nodes_(), parents_() {} - void Initialize(const std::vector& paths, - const std::vector& destinations); + void Initialize(absl::Span paths, + absl::Span destinations); // Returns the parent (predecessor) of 'node' in the tree in // O(log(path_tree_size)), where path_tree_size is the size of nodes_. @@ -126,8 +127,8 @@ class PathTree { // Initializes the tree from a non-sparse representation of the path tree // represented by 'paths'. The tree is reduced to the subtree in which nodes in // 'destinations' are the leafs. -void PathTree::Initialize(const std::vector& paths, - const std::vector& destinations) { +void PathTree::Initialize(absl::Span paths, + absl::Span destinations) { const NodeIndex kNilNode = StarGraph::kNilNode; std::vector node_explored(paths.size(), false); const int destination_size = destinations.size(); @@ -256,7 +257,7 @@ class DistanceContainer : public PathContainerImpl { std::vector reverse_destinations_; private: - static void ComputeReverse(const std::vector& nodes, + static void ComputeReverse(absl::Span nodes, NodeIndex num_nodes, std::vector* reverse_nodes) { CHECK(reverse_nodes != nullptr); diff --git a/ortools/graph/testdata/BUILD.bazel b/ortools/graph/testdata/BUILD.bazel index d7fbe3ea0c..b1ebc2079f 100644 --- a/ortools/graph/testdata/BUILD.bazel +++ b/ortools/graph/testdata/BUILD.bazel @@ -12,5 +12,5 @@ # limitations under the License. exports_files([ - "max_flow_test1.pb.txt", + "max_flow_test1.pb.txt", ]) diff --git a/ortools/graph/util.h b/ortools/graph/util.h index 33e5d46546..da3c0b4cd0 100644 --- a/ortools/graph/util.h +++ b/ortools/graph/util.h @@ -72,7 +72,7 @@ std::unique_ptr CopyGraph(const Graph& graph); // Note that you can call IsValidPermutation() to check it yourself. template std::unique_ptr RemapGraph(const Graph& graph, - const std::vector& new_node_index); + absl::Span new_node_index); // Gets the induced subgraph of "graph" restricted to the nodes in "nodes": // the resulting graph will have exactly nodes.size() nodes, and its @@ -277,7 +277,7 @@ std::unique_ptr CopyGraph(const Graph& graph) { template std::unique_ptr RemapGraph(const Graph& old_graph, - const std::vector& new_node_index) { + absl::Span new_node_index) { DCHECK(IsValidPermutation(new_node_index)) << "Invalid permutation"; const int num_nodes = old_graph.num_nodes(); CHECK_EQ(new_node_index.size(), num_nodes);