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