graph: backport from main
This commit is contained in:
@@ -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 = [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
444
ortools/graph/k_shortest_paths.h
Normal file
444
ortools/graph/k_shortest_paths.h
Normal 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): 712–716, 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): 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 <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_
|
||||
171
ortools/graph/k_shortest_paths_test.cc
Normal file
171
ortools/graph/k_shortest_paths_test.cc
Normal 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
|
||||
@@ -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());
|
||||
|
||||
@@ -191,8 +191,8 @@ TEST(SimpleMaxFlowTest, ProblematicProblemWithMaxCapacity) {
|
||||
FlowModelProto model,
|
||||
ReadFileToProto<FlowModelProto>(
|
||||
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());
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
173
ortools/graph/random_graph.cc
Normal file
173
ortools/graph/random_graph.cc
Normal 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
|
||||
51
ortools/graph/random_graph.h
Normal file
51
ortools/graph/random_graph.h
Normal 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_
|
||||
@@ -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);
|
||||
|
||||
2
ortools/graph/testdata/BUILD.bazel
vendored
2
ortools/graph/testdata/BUILD.bazel
vendored
@@ -12,5 +12,5 @@
|
||||
# limitations under the License.
|
||||
|
||||
exports_files([
|
||||
"max_flow_test1.pb.txt",
|
||||
"max_flow_test1.pb.txt",
|
||||
])
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user