graph: export from google3

This commit is contained in:
Corentin Le Molgat
2024-03-11 09:45:07 +01:00
parent 047089b529
commit 4294982e9b
13 changed files with 932 additions and 77 deletions

View File

@@ -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 = [

View File

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

View File

@@ -20,15 +20,14 @@
#include <utility>
#include <vector>
#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;

View File

@@ -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 <algorithm>
#include <iterator>
#include <limits>
#include <queue>
#include <tuple>
#include <utility>
#include <vector>
#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<std::vector<NodeIndex>> paths;
std::vector<PathDistance> 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): 712716, 1971.
// https://doi.org/10.1287%2Fmnsc.17.11.712
template <class GraphType>
KShortestPaths YenKShortestPaths(const GraphType& graph,
const std::vector<PathDistance>& 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<PathDistance>::max() - 1;
const PathDistance kDisconnectedDistance =
std::numeric_limits<PathDistance>::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 <class GraphType>
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 <class GraphType>
std::tuple<std::vector<NodeIndex>, PathDistance> ComputeShortestPath(
const GraphType& graph, const std::vector<PathDistance>& arc_lengths,
const NodeIndex source, const NodeIndex destination) {
BoundedDijkstraWrapper<GraphType, PathDistance> 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<NodeIndex> 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 <class GraphType>
PathDistance ComputePathLength(const GraphType& graph,
const absl::Span<const PathDistance> arc_lengths,
const absl::Span<const NodeIndex> 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<NodeIndex> path)
: path_(std::move(path)), priority_(priority) {}
bool operator<(const PathWithPriority& other) const {
return priority_ < other.priority_;
}
[[nodiscard]] const std::vector<NodeIndex>& path() const { return path_; }
[[nodiscard]] PathDistance priority() const { return priority_; }
private:
std::vector<NodeIndex> 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 Container>
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): 712716, 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 <class GraphType>
KShortestPaths YenKShortestPaths(const GraphType& graph,
const std::vector<PathDistance>& 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<std::vector<NodeIndex>, 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<internal::PathWithPriority>>
variant_path_queue;
for (; k > 0; --k) {
// Generate variant paths from the last shortest path.
const absl::Span<NodeIndex> 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<NodeIndex> 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<PathDistance> arc_lengths_for_detour = arc_lengths;
for (absl::Span<const NodeIndex> 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<std::vector<NodeIndex>, 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<NodeIndex> 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<NodeIndex> 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_

View File

@@ -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 <vector>
#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<PathDistance> 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<PathDistance> 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<PathDistance> 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<PathDistance> 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<PathDistance> 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<PathDistance> 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<PathDistance> 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<PathDistance> lengths{1, 1};
const KShortestPaths paths = YenKShortestPaths(graph, lengths, /*source=*/0,
/*destination=*/2, /*k=*/1);
EXPECT_THAT(paths.paths, ElementsAre(std::vector<int>{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<PathDistance> lengths{1, 1};
const KShortestPaths paths = YenKShortestPaths(graph, lengths, /*source=*/0,
/*destination=*/2, /*k=*/10);
EXPECT_THAT(paths.paths, ElementsAre(std::vector<int>{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<PathDistance> lengths{1, 30, 1};
const KShortestPaths paths = YenKShortestPaths(graph, lengths, /*source=*/0,
/*destination=*/2, /*k=*/10);
EXPECT_THAT(paths.paths,
ElementsAre(std::vector<int>{0, 1, 2}, std::vector<int>{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<PathDistance> 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<int>{0, 1, 2, 3, 4},
std::vector<int>{0, 4}));
EXPECT_THAT(paths.distances, ElementsAre(4, 30));
}
// TODO(user): randomized tests? Check validity with exhaustive
// exploration/IP formulation?
} // namespace
} // namespace operations_research

View File

@@ -19,6 +19,7 @@
#include <vector>
#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<Edge>& ordered_edges) {
void TestMe(const size_t left_nodes, absl::Span<const Edge> ordered_edges) {
std::vector<int64_t> edge_costs;
typedef util::StaticGraph<size_t, size_t> GraphType;
GraphType graph(2 * left_nodes, ordered_edges.size());

View File

@@ -78,10 +78,10 @@ void CheckMSTWithKruskal(const ListGraph<int, int>& graph,
// Helper function to check the expected MST is obtained with Prim.
void CheckMSTWithPrim(const ListGraph<int, int>& graph,
const std::vector<int64_t>& costs,
absl::Span<const int64_t> costs,
const std::vector<int>& expected_arcs) {
const std::vector<int> prim_mst = BuildPrimMinimumSpanningTree(
graph, [&costs](int arc) { return costs[arc]; });
graph, [costs](int arc) { return costs[arc]; });
EXPECT_THAT(expected_arcs, UnorderedElementsAreArray(prim_mst));
}

View File

@@ -335,10 +335,10 @@ int GetNodeMinimizingEdgeCostToSource(const GraphType& graph, int source,
template <typename CostFunction, typename GraphType, typename CostType>
std::vector<int> ComputeOneTree(const GraphType& graph,
const CostFunction& cost,
const std::vector<double>& weights,
const std::vector<int>& sorted_arcs,
absl::Span<const double> weights,
absl::Span<const int> 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.

View File

@@ -248,7 +248,7 @@ std::vector<Edge> 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<Edge>& edges) {
absl::Span<const Edge> edges) {
const std::vector<int>& matches = matcher.Matches();
std::vector<bool> seen(matches.size(), false);
int num_seen = 0;

View File

@@ -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 <algorithm>
#include <cstdint>
#include <memory>
#include <utility>
#include <vector>
#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<StaticGraph<>> CreateGraphMaybeReserved(int num_nodes,
int num_arcs,
bool finalized,
absl::BitGenRef gen) {
std::unique_ptr<StaticGraph<>> 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<StaticGraph<>>(num_nodes, num_arcs);
} else {
graph = std::make_unique<StaticGraph<>>();
graph->AddNode(num_nodes - 1);
}
return graph;
}
} // namespace
std::unique_ptr<StaticGraph<>> GenerateRandomMultiGraph(int num_nodes,
int num_arcs,
bool finalized,
absl::BitGenRef gen) {
std::unique_ptr<StaticGraph<>> 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<StaticGraph<>> 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<int64_t>(num_nodes) * (num_nodes - 1);
CHECK_LE(num_arcs, max_num_arcs);
std::unique_ptr<StaticGraph<>> 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<StaticGraph<>> inverse_graph =
GenerateRandomSimpleGraph(num_nodes, max_num_arcs - num_arcs,
/*finalized=*/true, directed, gen);
std::vector<bool> 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<std::pair<int, int>> 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<int, int> 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<StaticGraph<>> 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<StaticGraph<>> 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

View File

@@ -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 <memory>
#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<StaticGraph<>> 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<StaticGraph<>> 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<StaticGraph<>> GenerateRandomUndirectedSimpleGraph(
int num_nodes, int num_edges, bool finalized, absl::BitGenRef gen);
} // namespace util
#endif // UTIL_GRAPH_RANDOM_GRAPH_H_

View File

@@ -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<NodeIndex>& paths,
const std::vector<NodeIndex>& destinations);
void Initialize(absl::Span<const NodeIndex> paths,
absl::Span<const NodeIndex> 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<NodeIndex>& paths,
const std::vector<NodeIndex>& destinations) {
void PathTree::Initialize(absl::Span<const NodeIndex> paths,
absl::Span<const NodeIndex> destinations) {
const NodeIndex kNilNode = StarGraph::kNilNode;
std::vector<bool> node_explored(paths.size(), false);
const int destination_size = destinations.size();
@@ -256,7 +257,7 @@ class DistanceContainer : public PathContainerImpl {
std::vector<int> reverse_destinations_;
private:
static void ComputeReverse(const std::vector<NodeIndex>& nodes,
static void ComputeReverse(absl::Span<const NodeIndex> nodes,
NodeIndex num_nodes,
std::vector<int>* reverse_nodes) {
CHECK(reverse_nodes != nullptr);

View File

@@ -72,7 +72,7 @@ std::unique_ptr<Graph> CopyGraph(const Graph& graph);
// Note that you can call IsValidPermutation() to check it yourself.
template <class Graph>
std::unique_ptr<Graph> RemapGraph(const Graph& graph,
const std::vector<int>& new_node_index);
absl::Span<const int> 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<Graph> CopyGraph(const Graph& graph) {
template <class Graph>
std::unique_ptr<Graph> RemapGraph(const Graph& old_graph,
const std::vector<int>& new_node_index) {
absl::Span<const int> 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);