Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
routing_flow.cc
Go to the documentation of this file.
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 <math.h>
15
16#include <algorithm>
17#include <cstdint>
18#include <functional>
19#include <limits>
20#include <utility>
21#include <vector>
22
23#include "absl/algorithm/container.h"
24#include "absl/container/flat_hash_map.h"
25#include "absl/container/flat_hash_set.h"
26#include "absl/types/span.h"
33#include "ortools/constraint_solver/routing_parameters.pb.h"
36
37namespace operations_research {
38
39namespace {
40// Compute set of disjunctions involved in a pickup and delivery pair.
41template <typename Disjunctions>
42void AddDisjunctionsFromNodes(const RoutingModel& model,
43 absl::Span<const int64_t> nodes,
44 Disjunctions* disjunctions) {
45 for (int64_t node : nodes) {
46 for (const auto disjunction : model.GetDisjunctionIndices(node)) {
47 disjunctions->insert(disjunction);
48 }
49 }
50}
51} // namespace
52
53bool RoutingModel::IsMatchingModel() const {
54 // TODO(user): Support overlapping disjunctions and disjunctions with
55 // a cardinality > 1.
56 absl::flat_hash_set<int> disjunction_nodes;
57 for (DisjunctionIndex i(0); i < GetNumberOfDisjunctions(); ++i) {
58 if (GetDisjunctionMaxCardinality(i) > 1) return false;
59 for (int64_t node : GetDisjunctionNodeIndices(i)) {
60 if (!disjunction_nodes.insert(node).second) return false;
61 }
62 }
63 for (const auto& [pickups, deliveries] : GetPickupAndDeliveryPairs()) {
64 absl::flat_hash_set<DisjunctionIndex> disjunctions;
65 AddDisjunctionsFromNodes(*this, pickups, &disjunctions);
66 AddDisjunctionsFromNodes(*this, deliveries, &disjunctions);
67 // Pairs involving more than 2 disjunctions are not supported.
68 if (disjunctions.size() > 2) return false;
69 }
70 // Detect if a "unary" dimension prevents from having more than a single
71 // non-start/end node (or a single pickup and delivery pair) on a route.
72 // Binary dimensions are not considered because they would result in a
73 // quadratic check.
74 for (const RoutingDimension* const dimension : dimensions_) {
75 // TODO(user): Support vehicle-dependent dimension callbacks.
76 if (dimension->class_evaluators_.size() != 1) {
77 continue;
78 }
79 const TransitCallback1& transit =
80 UnaryTransitCallbackOrNull(dimension->class_evaluators_[0]);
81 if (transit == nullptr) {
82 continue;
83 }
84 int64_t max_vehicle_capacity = 0;
85 for (int64_t vehicle_capacity : dimension->vehicle_capacities()) {
86 max_vehicle_capacity = std::max(max_vehicle_capacity, vehicle_capacity);
87 }
88 std::vector<int64_t> transits(nexts_.size(),
89 std::numeric_limits<int64_t>::max());
90 for (int i = 0; i < nexts_.size(); ++i) {
91 if (!IsStart(i) && !IsEnd(i)) {
92 transits[i] = std::min(transits[i], transit(i));
93 }
94 }
95 int64_t min_transit = std::numeric_limits<int64_t>::max();
96 // Find the minimal accumulated value resulting from a pickup and delivery
97 // pair.
98 for (const auto& [pickups, deliveries] : GetPickupAndDeliveryPairs()) {
99 const auto transit_cmp = [&transits](int i, int j) {
100 return transits[i] < transits[j];
101 };
102 min_transit =
103 std::min(min_transit,
104 // Min transit from pickup.
105 transits[*absl::c_min_element(pickups, transit_cmp)] +
106 // Min transit from delivery.
107 transits[*absl::c_min_element(deliveries, transit_cmp)]);
108 }
109 // Find the minimal accumulated value resulting from a non-pickup/delivery
110 // node.
111 for (int i = 0; i < transits.size(); ++i) {
112 if (!IsPickup(i) && !IsDelivery(i)) {
113 min_transit = std::min(min_transit, transits[i]);
114 }
115 }
116 // If there cannot be more than one node or pickup and delivery, a matching
117 // problem has been detected.
118 if (CapProd(min_transit, 2) > max_vehicle_capacity) return true;
119 }
120 return false;
121}
122
123// Solve matching model using a min-cost flow. Here is the underlyihg flow:
124//
125// ---------- Source -------------
126// | (1,0) | (N,0)
127// V V
128// (vehicles) unperformed
129// | (1,cost) |
130// V |
131// (nodes/pickup/deliveries) | (1,penalty)
132// | (1,0) |
133// V |
134// disjunction <---------
135// | (1, 0)
136// V
137// Sink
138//
139// On arcs, (,) represents (capacity, cost).
140// N: number of disjunctions
141//
142
143namespace {
144struct FlowArc {
145 int64_t tail;
146 int64_t head;
147 int64_t capacity;
148 int64_t cost;
149};
150} // namespace
151
152bool RoutingModel::SolveMatchingModel(
153 Assignment* assignment, const RoutingSearchParameters& parameters) {
154 if (parameters.disable_scheduling_beware_this_may_degrade_performance()) {
155 // We need to use LocalDimensionCumulOptimizers below, so we return false if
156 // LP scheduling is disabled.
157 return false;
158 }
159 VLOG(2) << "Solving with flow";
160 assignment->Clear();
161
162 // Collect dimensions with costs.
163 // TODO(user): If the costs are soft cumul upper (resp. lower) bounds only,
164 // do not use the LP model.
165 const std::vector<RoutingDimension*> dimensions =
166 GetDimensionsWithSoftOrSpanCosts();
167 std::vector<LocalDimensionCumulOptimizer> optimizers;
168 optimizers.reserve(dimensions.size());
169 for (RoutingDimension* dimension : dimensions) {
170 optimizers.emplace_back(dimension,
171 parameters.continuous_scheduling_solver());
172 }
173
174 int num_flow_nodes = 0;
175 std::vector<std::vector<int64_t>> disjunction_to_flow_nodes;
176 std::vector<int64_t> disjunction_penalties;
177 std::vector<bool> in_disjunction(Size(), false);
178 // Create pickup and delivery pair flow nodes.
179 // TODO(user): Check pair alternatives correspond exactly to at most two
180 // disjunctions.
181 absl::flat_hash_map<int, std::pair<int64_t, int64_t>> flow_to_pd;
182 for (const auto& [pickups, deliveries] : GetPickupAndDeliveryPairs()) {
183 disjunction_to_flow_nodes.push_back({});
184 absl::flat_hash_set<DisjunctionIndex> disjunctions;
185 AddDisjunctionsFromNodes(*this, pickups, &disjunctions);
186 AddDisjunctionsFromNodes(*this, deliveries, &disjunctions);
187 for (int64_t pickup : pickups) {
188 in_disjunction[pickup] = true;
189 for (int64_t delivery : deliveries) {
190 in_disjunction[delivery] = true;
191 flow_to_pd[num_flow_nodes] = {pickup, delivery};
192 disjunction_to_flow_nodes.back().push_back(num_flow_nodes);
193 num_flow_nodes++;
194 }
195 }
196 DCHECK_LE(disjunctions.size(), 2);
197 int64_t penalty = 0;
198 if (disjunctions.size() < 2) {
199 penalty = kNoPenalty;
200 } else {
201 for (DisjunctionIndex index : disjunctions) {
202 const int64_t d_penalty = GetDisjunctionPenalty(index);
203 if (d_penalty == kNoPenalty) {
204 penalty = kNoPenalty;
205 break;
206 }
207 penalty = CapAdd(penalty, d_penalty);
208 }
209 }
210 disjunction_penalties.push_back(penalty);
211 }
212 // Create non-pickup and delivery flow nodes.
213 absl::flat_hash_map<int, int64_t> flow_to_non_pd;
214 for (int node = 0; node < Size(); ++node) {
215 if (IsStart(node) || in_disjunction[node]) continue;
216 const std::vector<DisjunctionIndex>& disjunctions =
217 GetDisjunctionIndices(node);
218 DCHECK_LE(disjunctions.size(), 1);
219 disjunction_to_flow_nodes.push_back({});
220 disjunction_penalties.push_back(
221 disjunctions.empty() ? kNoPenalty
222 : GetDisjunctionPenalty(disjunctions.back()));
223 if (disjunctions.empty()) {
224 in_disjunction[node] = true;
225 flow_to_non_pd[num_flow_nodes] = node;
226 disjunction_to_flow_nodes.back().push_back(num_flow_nodes);
227 num_flow_nodes++;
228 } else {
229 for (int n : GetDisjunctionNodeIndices(disjunctions.back())) {
230 in_disjunction[n] = true;
231 flow_to_non_pd[num_flow_nodes] = n;
232 disjunction_to_flow_nodes.back().push_back(num_flow_nodes);
233 num_flow_nodes++;
234 }
235 }
236 }
237
238 std::vector<FlowArc> arcs;
239
240 // Build a flow node for each disjunction and corresponding arcs.
241 // Each node exits to the sink through a node, for which the outgoing
242 // capacity is one (only one of the nodes in the disjunction is performed).
243 absl::flat_hash_map<int, int> flow_to_disjunction;
244 for (int i = 0; i < disjunction_to_flow_nodes.size(); ++i) {
245 const std::vector<int64_t>& flow_nodes = disjunction_to_flow_nodes[i];
246 if (flow_nodes.size() == 1) {
247 flow_to_disjunction[flow_nodes.back()] = i;
248 } else {
249 flow_to_disjunction[num_flow_nodes] = i;
250 for (int64_t flow_node : flow_nodes) {
251 arcs.push_back({flow_node, num_flow_nodes, 1, 0});
252 }
253 num_flow_nodes++;
254 }
255 }
256
257 // Build arcs from each vehicle to each non-vehicle flow node; the cost of
258 // each arc corresponds to:
259 // start(vehicle) -> pickup -> delivery -> end(vehicle)
260 // or
261 // start(vehicle) -> node -> end(vehicle)
262 std::vector<int> vehicle_to_flow;
263 absl::flat_hash_map<int, int> flow_to_vehicle;
264 for (int vehicle = 0; vehicle < vehicles(); ++vehicle) {
265 const int64_t start = Start(vehicle);
266 const int64_t end = End(vehicle);
267 for (const std::vector<int64_t>& flow_nodes : disjunction_to_flow_nodes) {
268 for (int64_t flow_node : flow_nodes) {
269 std::pair<int64_t, int64_t> pd_pair;
270 int64_t node = -1;
271 int64_t cost = 0;
272 bool add_arc = false;
273 if (gtl::FindCopy(flow_to_pd, flow_node, &pd_pair)) {
274 const int64_t pickup = pd_pair.first;
275 const int64_t delivery = pd_pair.second;
276 if (IsVehicleAllowedForIndex(vehicle, pickup) &&
277 IsVehicleAllowedForIndex(vehicle, delivery)) {
278 add_arc = true;
279 cost =
280 CapAdd(GetArcCostForVehicle(start, pickup, vehicle),
281 CapAdd(GetArcCostForVehicle(pickup, delivery, vehicle),
282 GetArcCostForVehicle(delivery, end, vehicle)));
283 const absl::flat_hash_map<int64_t, int64_t> nexts = {
284 {start, pickup}, {pickup, delivery}, {delivery, end}};
285 for (LocalDimensionCumulOptimizer& optimizer : optimizers) {
286 int64_t cumul_cost_value = 0;
287 // TODO(user): if the result is RELAXED_OPTIMAL_ONLY, do a
288 // second pass with an MP solver.
289 if (optimizer.ComputeRouteCumulCostWithoutFixedTransits(
290 vehicle,
291 [&nexts](int64_t node) {
292 return nexts.find(node)->second;
293 },
294 &cumul_cost_value) !=
296 cost = CapAdd(cost, cumul_cost_value);
297 } else {
298 add_arc = false;
299 break;
300 }
301 }
302 }
303 } else if (gtl::FindCopy(flow_to_non_pd, flow_node, &node)) {
304 if (IsVehicleAllowedForIndex(vehicle, node)) {
305 add_arc = true;
306 cost = CapAdd(GetArcCostForVehicle(start, node, vehicle),
307 GetArcCostForVehicle(node, end, vehicle));
308 const absl::flat_hash_map<int64_t, int64_t> nexts = {{start, node},
309 {node, end}};
310 for (LocalDimensionCumulOptimizer& optimizer : optimizers) {
311 int64_t cumul_cost_value = 0;
312 // TODO(user): if the result is RELAXED_OPTIMAL_ONLY, do a
313 // second pass with an MP solver.
314 if (optimizer.ComputeRouteCumulCostWithoutFixedTransits(
315 vehicle,
316 [&nexts](int64_t node) {
317 return nexts.find(node)->second;
318 },
319 &cumul_cost_value) !=
321 cost = CapAdd(cost, cumul_cost_value);
322 } else {
323 add_arc = false;
324 break;
325 }
326 }
327 }
328 } else {
329 DCHECK(false);
330 }
331 if (add_arc) {
332 arcs.push_back({num_flow_nodes, flow_node, 1, cost});
333 }
334 }
335 }
336 flow_to_vehicle[num_flow_nodes] = vehicle;
337 vehicle_to_flow.push_back(num_flow_nodes);
338 num_flow_nodes++;
339 }
340 // Create flow source and sink nodes.
341 const int source = num_flow_nodes + 1;
342 const int sink = source + 1;
343 // Source connected to vehicle nodes.
344 for (int vehicle = 0; vehicle < vehicles(); ++vehicle) {
345 arcs.push_back({source, vehicle_to_flow[vehicle], 1, 0});
346 }
347 // Handle unperformed nodes.
348 // Create a node to catch unperformed nodes and connect it to source.
349 const int unperformed = num_flow_nodes;
350 const int64_t flow_supply = disjunction_to_flow_nodes.size();
351 arcs.push_back({source, unperformed, flow_supply, 0});
352 for (const auto& flow_disjunction_element : flow_to_disjunction) {
353 const int flow_node = flow_disjunction_element.first;
354 const int64_t penalty =
355 disjunction_penalties[flow_disjunction_element.second];
356 if (penalty != kNoPenalty) {
357 arcs.push_back({unperformed, flow_node, 1, penalty});
358 }
359 // Connect non-vehicle flow nodes to sinks.
360 arcs.push_back({flow_node, sink, 1, 0});
361 }
362
363 // Rescale costs for min-cost flow; assuming max cost resulting from the
364 // push-relabel flow algorithm is max_arc_cost * (num_nodes+1) * (num_nodes+1)
365 // (cost-scaling multiplies arc costs by num_nodes+1 and the flow itself can
366 // accumulate num_nodes+1 such arcs (with capacity being 1 for costed arcs)).
367 int64_t scale_factor = 1;
368 const FlowArc& arc_with_max_cost = *std::max_element(
369 arcs.begin(), arcs.end(),
370 [](const FlowArc& a, const FlowArc& b) { return a.cost < b.cost; });
371 // SimpleMinCostFlow adds a source and a sink node, so actual number of
372 // nodes to consider is num_flow_nodes + 3.
373 const int actual_flow_num_nodes = num_flow_nodes + 3;
374 if (log(static_cast<double>(arc_with_max_cost.cost) + 1) +
375 2 * log(actual_flow_num_nodes) >
376 log(std::numeric_limits<int64_t>::max())) {
377 scale_factor = CapProd(actual_flow_num_nodes, actual_flow_num_nodes);
378 }
379
380 SimpleMinCostFlow flow;
381 // Add arcs to flow.
382 for (const FlowArc& arc : arcs) {
383 flow.AddArcWithCapacityAndUnitCost(arc.tail, arc.head, arc.capacity,
384 arc.cost / scale_factor);
385 }
386
387 // Set flow supply (number of non-vehicle nodes or pairs).
388 flow.SetNodeSupply(source, flow_supply);
389 flow.SetNodeSupply(sink, -flow_supply);
390
391 // TODO(user): Take time limit into account.
392 if (flow.Solve() != SimpleMinCostFlow::OPTIMAL) {
393 return false;
394 }
395
396 // Map the flow result to assignment, only setting next variables.
397 std::vector<bool> used_vehicles(vehicles(), false);
398 absl::flat_hash_set<int> used_nodes;
399 for (int i = 0; i < flow.NumArcs(); ++i) {
400 if (flow.Flow(i) > 0 && flow.Tail(i) != source && flow.Head(i) != sink) {
401 std::vector<int> nodes;
402 std::pair<int64_t, int64_t> pd_pair;
403 int node = -1;
404 int index = -1;
405 if (gtl::FindCopy(flow_to_pd, flow.Head(i), &pd_pair)) {
406 nodes.push_back(pd_pair.first);
407 nodes.push_back(pd_pair.second);
408 } else if (gtl::FindCopy(flow_to_non_pd, flow.Head(i), &node)) {
409 nodes.push_back(node);
410 } else if (gtl::FindCopy(flow_to_disjunction, flow.Head(i), &index)) {
411 for (int64_t flow_node : disjunction_to_flow_nodes[index]) {
412 if (gtl::FindCopy(flow_to_pd, flow_node, &pd_pair)) {
413 nodes.push_back(pd_pair.first);
414 nodes.push_back(pd_pair.second);
415 } else if (gtl::FindCopy(flow_to_non_pd, flow_node, &node)) {
416 nodes.push_back(node);
417 }
418 }
419 }
420 int vehicle = -1;
421 if (flow.Tail(i) == unperformed) {
422 // Head is unperformed.
423 for (int node : nodes) {
424 assignment->Add(NextVar(node))->SetValue(node);
425 used_nodes.insert(node);
426 }
427 } else if (gtl::FindCopy(flow_to_vehicle, flow.Tail(i), &vehicle)) {
428 // Head is performed on a vehicle.
429 used_vehicles[vehicle] = true;
430 int current = Start(vehicle);
431 for (int node : nodes) {
432 assignment->Add(NextVar(current))->SetValue(node);
433 used_nodes.insert(node);
434 current = node;
435 }
436 assignment->Add(NextVar(current))->SetValue(End(vehicle));
437 }
438 }
439 }
440 // Adding unused nodes.
441 for (int node = 0; node < Size(); ++node) {
442 if (!IsStart(node) && used_nodes.count(node) == 0) {
443 assignment->Add(NextVar(node))->SetValue(node);
444 }
445 }
446 // Adding unused vehicles.
447 for (int vehicle = 0; vehicle < vehicles(); ++vehicle) {
448 if (!used_vehicles[vehicle]) {
449 assignment->Add(NextVar(Start(vehicle)))->SetValue(End(vehicle));
450 }
451 }
452 return true;
453}
454
455} // namespace operations_research
std::vector< int > dimensions
IntVarElement * Add(IntVar *var)
int64_t b
Definition table.cc:45
int64_t a
Definition table.cc:44
SatParameters parameters
GRBmodel * model
int arc
int index
bool FindCopy(const Collection &collection, const Key &key, Value *const value)
Definition map_util.h:190
In SWIG mode, we don't want anything besides these top-level includes.
int64_t CapAdd(int64_t x, int64_t y)
@ INFEASIBLE
A solution could not be found.
int64_t CapProd(int64_t x, int64_t y)
int nodes
std::optional< int64_t > end
int64_t start