|
| 1 | +// Copyright 2010-2024 Google LLC |
| 2 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 3 | +// you may not use this file except in compliance with the License. |
| 4 | +// You may obtain a copy of the License at |
| 5 | +// |
| 6 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 7 | +// |
| 8 | +// Unless required by applicable law or agreed to in writing, software |
| 9 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 10 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 11 | +// See the License for the specific language governing permissions and |
| 12 | +// limitations under the License. |
| 13 | + |
| 14 | +#include "ortools/graph/bidirectional_dijkstra.h" |
| 15 | + |
| 16 | +#include <cmath> |
| 17 | +#include <limits> |
| 18 | +#include <random> |
| 19 | +#include <string> |
| 20 | +#include <utility> |
| 21 | +#include <vector> |
| 22 | + |
| 23 | +#include "absl/container/flat_hash_map.h" |
| 24 | +#include "absl/random/distributions.h" |
| 25 | +#include "absl/strings/str_cat.h" |
| 26 | +#include "gmock/gmock.h" |
| 27 | +#include "gtest/gtest.h" |
| 28 | +#include "ortools/base/map_util.h" |
| 29 | +#include "ortools/graph/bounded_dijkstra.h" |
| 30 | +#include "ortools/graph/graph.h" |
| 31 | +#include "util/tuple/dump_vars.h" |
| 32 | + |
| 33 | +namespace operations_research { |
| 34 | +namespace { |
| 35 | + |
| 36 | +using ::testing::AnyOf; |
| 37 | +using ::testing::ElementsAre; |
| 38 | +using ::testing::ElementsAreArray; |
| 39 | +using ::testing::IsEmpty; |
| 40 | +using ::util::ListGraph; |
| 41 | +using ::util::StaticGraph; |
| 42 | + |
| 43 | +TEST(BidirectionalDijkstraTest, EmptyPathInspection) { |
| 44 | + ListGraph<> empty_graph; |
| 45 | + std::vector<double> empty_vector; |
| 46 | + BidirectionalDijkstra<ListGraph<>, double> dijkstra( |
| 47 | + &empty_graph, &empty_vector, &empty_graph, &empty_vector); |
| 48 | + const auto path = dijkstra.SetToSetShortestPath({}, {}); |
| 49 | + ASSERT_EQ(path.meeting_point, -1); |
| 50 | + EXPECT_THAT(dijkstra.PathToNodePath(path), IsEmpty()); |
| 51 | + EXPECT_EQ(dijkstra.PathDebugString(path), "<NO PATH>"); |
| 52 | +} |
| 53 | + |
| 54 | +TEST(BidirectionalDijkstraTest, SmallTest) { |
| 55 | + // Build a small "grid" graph. Arc indices and lengths of the forward graph |
| 56 | + // are in (); and the backward graph has the same, but reversed arcs. |
| 57 | + // |
| 58 | + // 0 --(#0:0.1)--> 1 --(#1:1.1)--> 2 |
| 59 | + // | | | |
| 60 | + // (#2:0.1) (#3:0.19) (#4:0.3) |
| 61 | + // | | | |
| 62 | + // v v v |
| 63 | + // 3 --(#5:0.2)--> 4 --(#6:1.2)--> 5 |
| 64 | + ListGraph<> forward_graph; |
| 65 | + std::vector<double> arc_lengths; |
| 66 | + forward_graph.AddArc(0, 1); |
| 67 | + arc_lengths.push_back(0.1); |
| 68 | + forward_graph.AddArc(1, 2); |
| 69 | + arc_lengths.push_back(1.1); |
| 70 | + forward_graph.AddArc(0, 3); |
| 71 | + arc_lengths.push_back(0.1); |
| 72 | + forward_graph.AddArc(1, 4); |
| 73 | + arc_lengths.push_back(0.19); |
| 74 | + forward_graph.AddArc(2, 5); |
| 75 | + arc_lengths.push_back(0.3); |
| 76 | + forward_graph.AddArc(3, 4); |
| 77 | + arc_lengths.push_back(0.2); |
| 78 | + forward_graph.AddArc(4, 5); |
| 79 | + arc_lengths.push_back(1.2); |
| 80 | + ListGraph<> backward_graph; |
| 81 | + for (int arc = 0; arc < forward_graph.num_arcs(); ++arc) { |
| 82 | + backward_graph.AddArc(forward_graph.Head(arc), forward_graph.Tail(arc)); |
| 83 | + } |
| 84 | + BidirectionalDijkstra<ListGraph<>, double> dijkstra( |
| 85 | + &forward_graph, &arc_lengths, &backward_graph, &arc_lengths); |
| 86 | + // Since the meeting point may vary, depending on which search direction goes |
| 87 | + // faster, we run it many times to try and exercise more code paths. |
| 88 | + const int kNumPaths = 1000; |
| 89 | + for (int i = 0; i < kNumPaths; ++i) { |
| 90 | + SCOPED_TRACE(absl::StrCat("On attempt #", i)); |
| 91 | + const auto path = dijkstra.OneToOneShortestPath(0, 5); |
| 92 | + EXPECT_THAT(dijkstra.PathToNodePath(path), ElementsAre(0, 1, 4, 5)); |
| 93 | + ASSERT_THAT(dijkstra.PathDebugString(path), |
| 94 | + AnyOf("0 --(#0:0.1)--> 1 --(#3:0.19)--> 4 --(#6:1.2)--> [5]", |
| 95 | + "0 --(#0:0.1)--> 1 --(#3:0.19)--> [4] <--(#6:1.2)-- 5", |
| 96 | + "0 --(#0:0.1)--> [1] <--(#3:0.19)-- 4 <--(#6:1.2)-- 5", |
| 97 | + "[0] <--(#0:0.1)-- 1 <--(#3:0.19)-- 4 <--(#6:1.2)-- 5")); |
| 98 | + } |
| 99 | +} |
| 100 | + |
| 101 | +TEST(BidirectionalDijkstraTest, RandomizedCorrectnessTest) { |
| 102 | + std::mt19937 random(12345); |
| 103 | + // Performance on forge as of 2016-10-05 with these numbers, over 1000 runs: |
| 104 | + // - fastbuild: max = 21.9s, avg = 10.7s. |
| 105 | + // - opt: max = 23.2s, avg = 10.4s. |
| 106 | + const int kNumGraphs = DEBUG_MODE ? 100 : 300; |
| 107 | + const int kNumQueriesPerGraph = DEBUG_MODE ? 10 : 30; |
| 108 | + const int kNumNodes = 1000; |
| 109 | + const int kNumArcs = 10000; |
| 110 | + for (int graph_iter = 0; graph_iter < kNumGraphs; ++graph_iter) { |
| 111 | + // Build the random graphs. |
| 112 | + StaticGraph<> forward_graph; |
| 113 | + StaticGraph<> backward_graph; |
| 114 | + std::vector<double> forward_lengths; |
| 115 | + std::vector<double> backward_lengths; |
| 116 | + std::vector<int> forward_arc_of_backward_arc; |
| 117 | + forward_graph.AddNode(kNumNodes - 1); |
| 118 | + backward_graph.AddNode(kNumNodes - 1); |
| 119 | + for (int i = 0; i < kNumArcs; ++i) { |
| 120 | + const int a = absl::Uniform(random, 0, kNumNodes); |
| 121 | + const int b = absl::Uniform(random, 0, kNumNodes); |
| 122 | + forward_graph.AddArc(a, b); |
| 123 | + backward_graph.AddArc(b, a); |
| 124 | + forward_arc_of_backward_arc.push_back(i); |
| 125 | + const double length = absl::Uniform<double>(random, 0.0, 1.0); |
| 126 | + forward_lengths.push_back(length); |
| 127 | + backward_lengths.push_back(length); |
| 128 | + } |
| 129 | + std::vector<int> forward_perm; |
| 130 | + forward_graph.Build(&forward_perm); |
| 131 | + util::Permute(forward_perm, &forward_lengths); |
| 132 | + for (int& a : forward_arc_of_backward_arc) { |
| 133 | + a = forward_perm[a]; |
| 134 | + } |
| 135 | + std::vector<int> backward_perm; |
| 136 | + backward_graph.Build(&backward_perm); |
| 137 | + util::Permute(backward_perm, &backward_lengths); |
| 138 | + util::Permute(backward_perm, &forward_arc_of_backward_arc); |
| 139 | + |
| 140 | + // Initialize the tested Dijkstra and the reference Dijkstra. |
| 141 | + typedef BidirectionalDijkstra<StaticGraph<>, double> Dijkstra; |
| 142 | + Dijkstra tested_dijkstra(&forward_graph, &forward_lengths, &backward_graph, |
| 143 | + &backward_lengths); |
| 144 | + BoundedDijkstraWrapper<StaticGraph<>, double> ref_dijkstra( |
| 145 | + &forward_graph, &forward_lengths); |
| 146 | + |
| 147 | + // To print some debugging info in case the test fails. |
| 148 | + auto print_arc_path = [&](const std::vector<int>& arc_path) -> std::string { |
| 149 | + if (arc_path.empty()) return "<EMPTY>"; |
| 150 | + std::string out = absl::StrCat(forward_graph.Tail(arc_path[0])); |
| 151 | + double total_length = 0.0; |
| 152 | + for (const int arc : arc_path) { |
| 153 | + absl::StrAppend(&out, "--(#", arc, ":", (forward_lengths[arc]), ")-->", |
| 154 | + forward_graph.Head(arc)); |
| 155 | + total_length += forward_lengths[arc]; |
| 156 | + } |
| 157 | + absl::StrAppend(&out, "; Total length=", (total_length)); |
| 158 | + return out; |
| 159 | + }; |
| 160 | + auto print_node_distances = |
| 161 | + [&](const std::vector<Dijkstra::NodeDistance>& nds) -> std::string { |
| 162 | + std::string out = "{"; |
| 163 | + for (const Dijkstra::NodeDistance& nd : nds) { |
| 164 | + absl::StrAppend(&out, " #", nd.node, " dist=", (nd.distance), ","); |
| 165 | + } |
| 166 | + if (!nds.empty()) out.back() = ' '; // Replace the last ','. |
| 167 | + out += '}'; |
| 168 | + return out; |
| 169 | + }; |
| 170 | + |
| 171 | + // Run random Dijkstra queries. |
| 172 | + for (int q = 0; q < kNumQueriesPerGraph; ++q) { |
| 173 | + const int num_src = ceil(absl::Exponential<double>(random)); |
| 174 | + const int num_dst = ceil(absl::Exponential<double>(random)); |
| 175 | + std::vector<std::pair<int, double>> ref_srcs; |
| 176 | + std::vector<std::pair<int, double>> ref_dsts; |
| 177 | + std::vector<Dijkstra::NodeDistance> srcs; |
| 178 | + std::vector<Dijkstra::NodeDistance> dsts; |
| 179 | + for (int i = 0; i < num_src; ++i) { |
| 180 | + const int src = absl::Uniform(random, 0, kNumNodes); |
| 181 | + // Try costs < 0. |
| 182 | + const double dist = absl::Uniform<double>(random, 1.0, 2.0); |
| 183 | + ref_srcs.push_back({src, dist}); |
| 184 | + srcs.push_back({src, dist}); |
| 185 | + } |
| 186 | + for (int i = 0; i < num_dst; ++i) { |
| 187 | + const int dst = absl::Uniform(random, 0, kNumNodes); |
| 188 | + const double dist = absl::Uniform<double>(random, 1.0, 2.0); |
| 189 | + ref_dsts.push_back({dst, dist}); |
| 190 | + dsts.push_back({dst, dist}); |
| 191 | + } |
| 192 | + const std::vector<int> ref_dests = |
| 193 | + ref_dijkstra |
| 194 | + .RunBoundedDijkstraFromMultipleSourcesToMultipleDestinations( |
| 195 | + ref_srcs, ref_dsts, |
| 196 | + /*num_destinations_to_reach=*/1, |
| 197 | + std::numeric_limits<double>::infinity()); |
| 198 | + if (ref_dests.empty()) { |
| 199 | + // No path. Verify that. |
| 200 | + EXPECT_EQ( |
| 201 | + tested_dijkstra.SetToSetShortestPath(srcs, dsts).meeting_point, -1); |
| 202 | + continue; |
| 203 | + } |
| 204 | + const std::vector<int> ref_arc_path = |
| 205 | + ref_dijkstra.ArcPathToNode(ref_dests[0]); |
| 206 | + const auto path = tested_dijkstra.SetToSetShortestPath(srcs, dsts); |
| 207 | + std::vector<int> arc_path = path.forward_arc_path; |
| 208 | + for (const int arc : gtl::reversed_view(path.backward_arc_path)) { |
| 209 | + arc_path.push_back(forward_arc_of_backward_arc[arc]); |
| 210 | + } |
| 211 | + ASSERT_THAT(arc_path, ElementsAreArray(ref_arc_path)) |
| 212 | + << "On graph #" << graph_iter << ", query #" << q |
| 213 | + << "\nSources : " << print_node_distances(srcs) |
| 214 | + << "\nDestinations: " << print_node_distances(dsts) |
| 215 | + << "\nReference path : " << print_arc_path(ref_arc_path) |
| 216 | + << "\nObtained path : " << print_arc_path(arc_path); |
| 217 | + } |
| 218 | + } |
| 219 | +} |
| 220 | + |
| 221 | +} // namespace |
| 222 | +} // namespace operations_research |
0 commit comments