OR-Tools  9.6
routing_flow.cc
Go to the documentation of this file.
1 // Copyright 2010-2022 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"
27 #include "ortools/base/int_type.h"
29 #include "ortools/base/logging.h"
30 #include "ortools/base/map_util.h"
34 #include "ortools/constraint_solver/routing_parameters.pb.h"
38 
39 namespace operations_research {
40 
41 namespace {
42 // Compute set of disjunctions involved in a pickup and delivery pair.
43 template <typename Disjunctions>
44 void 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 
147 namespace {
148 struct FlowArc {
149  int64_t tail;
150  int64_t head;
151  int64_t capacity;
152  int64_t cost;
153 };
154 } // namespace
155 
156 bool RoutingModel::SolveMatchingModel(
157  Assignment* assignment, const RoutingSearchParameters& parameters) {
158  if (parameters.disable_scheduling_beware_this_may_degrade_performance()) {
159  // We need to use LocalDimensionCumulOptimizers below, so we return false if
160  // LP scheduling is disabled.
161  return false;
162  }
163  VLOG(2) << "Solving with flow";
164  assignment->Clear();
165 
166  // Collect dimensions with costs.
167  // TODO(user): If the costs are soft cumul upper (resp. lower) bounds only,
168  // do not use the LP model.
169  const std::vector<RoutingDimension*> dimensions =
171  std::vector<LocalDimensionCumulOptimizer> optimizers;
172  optimizers.reserve(dimensions.size());
173  for (RoutingDimension* dimension : dimensions) {
174  optimizers.emplace_back(dimension,
175  parameters.continuous_scheduling_solver());
176  }
177 
178  int num_flow_nodes = 0;
179  std::vector<std::vector<int64_t>> disjunction_to_flow_nodes;
180  std::vector<int64_t> disjunction_penalties;
181  std::vector<bool> in_disjunction(Size(), false);
182  // Create pickup and delivery pair flow nodes.
183  // TODO(user): Check pair alternatives correspond exactly to at most two
184  // disjunctions.
185  absl::flat_hash_map<int, std::pair<int64_t, int64_t>> flow_to_pd;
186  for (const auto& pd_pairs : GetPickupAndDeliveryPairs()) {
187  disjunction_to_flow_nodes.push_back({});
188  absl::flat_hash_set<DisjunctionIndex> disjunctions;
189  AddDisjunctionsFromNodes(*this, pd_pairs.first, &disjunctions);
190  AddDisjunctionsFromNodes(*this, pd_pairs.second, &disjunctions);
191  for (int64_t pickup : pd_pairs.first) {
192  in_disjunction[pickup] = true;
193  for (int64_t delivery : pd_pairs.second) {
194  in_disjunction[delivery] = true;
195  flow_to_pd[num_flow_nodes] = {pickup, delivery};
196  disjunction_to_flow_nodes.back().push_back(num_flow_nodes);
197  num_flow_nodes++;
198  }
199  }
200  DCHECK_LE(disjunctions.size(), 2);
201  int64_t penalty = 0;
202  if (disjunctions.size() < 2) {
203  penalty = kNoPenalty;
204  } else {
205  for (DisjunctionIndex index : disjunctions) {
206  const int64_t d_penalty = GetDisjunctionPenalty(index);
207  if (d_penalty == kNoPenalty) {
208  penalty = kNoPenalty;
209  break;
210  }
211  penalty = CapAdd(penalty, d_penalty);
212  }
213  }
214  disjunction_penalties.push_back(penalty);
215  }
216  // Create non-pickup and delivery flow nodes.
217  absl::flat_hash_map<int, int64_t> flow_to_non_pd;
218  for (int node = 0; node < Size(); ++node) {
219  if (IsStart(node) || in_disjunction[node]) continue;
220  const std::vector<DisjunctionIndex>& disjunctions =
221  GetDisjunctionIndices(node);
222  DCHECK_LE(disjunctions.size(), 1);
223  disjunction_to_flow_nodes.push_back({});
224  disjunction_penalties.push_back(
225  disjunctions.empty() ? kNoPenalty
226  : GetDisjunctionPenalty(disjunctions.back()));
227  if (disjunctions.empty()) {
228  in_disjunction[node] = true;
229  flow_to_non_pd[num_flow_nodes] = node;
230  disjunction_to_flow_nodes.back().push_back(num_flow_nodes);
231  num_flow_nodes++;
232  } else {
233  for (int n : GetDisjunctionNodeIndices(disjunctions.back())) {
234  in_disjunction[n] = true;
235  flow_to_non_pd[num_flow_nodes] = n;
236  disjunction_to_flow_nodes.back().push_back(num_flow_nodes);
237  num_flow_nodes++;
238  }
239  }
240  }
241 
242  std::vector<FlowArc> arcs;
243 
244  // Build a flow node for each disjunction and corresponding arcs.
245  // Each node exits to the sink through a node, for which the outgoing
246  // capacity is one (only one of the nodes in the disjunction is performed).
247  absl::flat_hash_map<int, int> flow_to_disjunction;
248  for (int i = 0; i < disjunction_to_flow_nodes.size(); ++i) {
249  const std::vector<int64_t>& flow_nodes = disjunction_to_flow_nodes[i];
250  if (flow_nodes.size() == 1) {
251  flow_to_disjunction[flow_nodes.back()] = i;
252  } else {
253  flow_to_disjunction[num_flow_nodes] = i;
254  for (int64_t flow_node : flow_nodes) {
255  arcs.push_back({flow_node, num_flow_nodes, 1, 0});
256  }
257  num_flow_nodes++;
258  }
259  }
260 
261  // Build arcs from each vehicle to each non-vehicle flow node; the cost of
262  // each arc corresponds to:
263  // start(vehicle) -> pickup -> delivery -> end(vehicle)
264  // or
265  // start(vehicle) -> node -> end(vehicle)
266  std::vector<int> vehicle_to_flow;
267  absl::flat_hash_map<int, int> flow_to_vehicle;
268  for (int vehicle = 0; vehicle < vehicles(); ++vehicle) {
269  const int64_t start = Start(vehicle);
270  const int64_t end = End(vehicle);
271  for (const std::vector<int64_t>& flow_nodes : disjunction_to_flow_nodes) {
272  for (int64_t flow_node : flow_nodes) {
273  std::pair<int64_t, int64_t> pd_pair;
274  int64_t node = -1;
275  int64_t cost = 0;
276  bool add_arc = false;
277  if (gtl::FindCopy(flow_to_pd, flow_node, &pd_pair)) {
278  const int64_t pickup = pd_pair.first;
279  const int64_t delivery = pd_pair.second;
280  if (IsVehicleAllowedForIndex(vehicle, pickup) &&
281  IsVehicleAllowedForIndex(vehicle, delivery)) {
282  add_arc = true;
283  cost =
284  CapAdd(GetArcCostForVehicle(start, pickup, vehicle),
285  CapAdd(GetArcCostForVehicle(pickup, delivery, vehicle),
286  GetArcCostForVehicle(delivery, end, vehicle)));
287  const absl::flat_hash_map<int64_t, int64_t> nexts = {
288  {start, pickup}, {pickup, delivery}, {delivery, end}};
289  for (LocalDimensionCumulOptimizer& optimizer : optimizers) {
290  int64_t cumul_cost_value = 0;
291  // TODO(user): if the result is RELAXED_OPTIMAL_ONLY, do a
292  // second pass with an MP solver.
293  if (optimizer.ComputeRouteCumulCostWithoutFixedTransits(
294  vehicle,
295  [&nexts](int64_t node) {
296  return nexts.find(node)->second;
297  },
298  &cumul_cost_value) !=
300  cost = CapAdd(cost, cumul_cost_value);
301  } else {
302  add_arc = false;
303  break;
304  }
305  }
306  }
307  } else if (gtl::FindCopy(flow_to_non_pd, flow_node, &node)) {
308  if (IsVehicleAllowedForIndex(vehicle, node)) {
309  add_arc = true;
310  cost = CapAdd(GetArcCostForVehicle(start, node, vehicle),
311  GetArcCostForVehicle(node, end, vehicle));
312  const absl::flat_hash_map<int64_t, int64_t> nexts = {{start, node},
313  {node, end}};
314  for (LocalDimensionCumulOptimizer& optimizer : optimizers) {
315  int64_t cumul_cost_value = 0;
316  // TODO(user): if the result is RELAXED_OPTIMAL_ONLY, do a
317  // second pass with an MP solver.
318  if (optimizer.ComputeRouteCumulCostWithoutFixedTransits(
319  vehicle,
320  [&nexts](int64_t node) {
321  return nexts.find(node)->second;
322  },
323  &cumul_cost_value) !=
325  cost = CapAdd(cost, cumul_cost_value);
326  } else {
327  add_arc = false;
328  break;
329  }
330  }
331  }
332  } else {
333  DCHECK(false);
334  }
335  if (add_arc) {
336  arcs.push_back({num_flow_nodes, flow_node, 1, cost});
337  }
338  }
339  }
340  flow_to_vehicle[num_flow_nodes] = vehicle;
341  vehicle_to_flow.push_back(num_flow_nodes);
342  num_flow_nodes++;
343  }
344  // Create flow source and sink nodes.
345  const int source = num_flow_nodes + 1;
346  const int sink = source + 1;
347  // Source connected to vehicle nodes.
348  for (int vehicle = 0; vehicle < vehicles(); ++vehicle) {
349  arcs.push_back({source, vehicle_to_flow[vehicle], 1, 0});
350  }
351  // Handle unperformed nodes.
352  // Create a node to catch unperformed nodes and connect it to source.
353  const int unperformed = num_flow_nodes;
354  const int64_t flow_supply = disjunction_to_flow_nodes.size();
355  arcs.push_back({source, unperformed, flow_supply, 0});
356  for (const auto& flow_disjunction_element : flow_to_disjunction) {
357  const int flow_node = flow_disjunction_element.first;
358  const int64_t penalty =
359  disjunction_penalties[flow_disjunction_element.second];
360  if (penalty != kNoPenalty) {
361  arcs.push_back({unperformed, flow_node, 1, penalty});
362  }
363  // Connect non-vehicle flow nodes to sinks.
364  arcs.push_back({flow_node, sink, 1, 0});
365  }
366 
367  // Rescale costs for min-cost flow; assuming max cost resulting from the
368  // push-relabel flow algorithm is max_arc_cost * (num_nodes+1) * (num_nodes+1)
369  // (cost-scaling multiplies arc costs by num_nodes+1 and the flow itself can
370  // accumulate num_nodes+1 such arcs (with capacity being 1 for costed arcs)).
371  int64_t scale_factor = 1;
372  const FlowArc& arc_with_max_cost = *std::max_element(
373  arcs.begin(), arcs.end(),
374  [](const FlowArc& a, const FlowArc& b) { return a.cost < b.cost; });
375  // SimpleMinCostFlow adds a source and a sink node, so actual number of
376  // nodes to consider is num_flow_nodes + 3.
377  const int actual_flow_num_nodes = num_flow_nodes + 3;
378  if (log(static_cast<double>(arc_with_max_cost.cost) + 1) +
379  2 * log(actual_flow_num_nodes) >
381  scale_factor = CapProd(actual_flow_num_nodes, actual_flow_num_nodes);
382  }
383 
384  SimpleMinCostFlow flow;
385  // Add arcs to flow.
386  for (const FlowArc& arc : arcs) {
387  flow.AddArcWithCapacityAndUnitCost(arc.tail, arc.head, arc.capacity,
388  arc.cost / scale_factor);
389  }
390 
391  // Set flow supply (number of non-vehicle nodes or pairs).
392  flow.SetNodeSupply(source, flow_supply);
393  flow.SetNodeSupply(sink, -flow_supply);
394 
395  // TODO(user): Take time limit into account.
396  if (flow.Solve() != SimpleMinCostFlow::OPTIMAL) {
397  return false;
398  }
399 
400  // Map the flow result to assignment, only setting next variables.
401  std::vector<bool> used_vehicles(vehicles(), false);
402  absl::flat_hash_set<int> used_nodes;
403  for (int i = 0; i < flow.NumArcs(); ++i) {
404  if (flow.Flow(i) > 0 && flow.Tail(i) != source && flow.Head(i) != sink) {
405  std::vector<int> nodes;
406  std::pair<int64_t, int64_t> pd_pair;
407  int node = -1;
408  int index = -1;
409  if (gtl::FindCopy(flow_to_pd, flow.Head(i), &pd_pair)) {
410  nodes.push_back(pd_pair.first);
411  nodes.push_back(pd_pair.second);
412  } else if (gtl::FindCopy(flow_to_non_pd, flow.Head(i), &node)) {
413  nodes.push_back(node);
414  } else if (gtl::FindCopy(flow_to_disjunction, flow.Head(i), &index)) {
415  for (int64_t flow_node : disjunction_to_flow_nodes[index]) {
416  if (gtl::FindCopy(flow_to_pd, flow_node, &pd_pair)) {
417  nodes.push_back(pd_pair.first);
418  nodes.push_back(pd_pair.second);
419  } else if (gtl::FindCopy(flow_to_non_pd, flow_node, &node)) {
420  nodes.push_back(node);
421  }
422  }
423  }
424  int vehicle = -1;
425  if (flow.Tail(i) == unperformed) {
426  // Head is unperformed.
427  for (int node : nodes) {
428  assignment->Add(NextVar(node))->SetValue(node);
429  used_nodes.insert(node);
430  }
431  } else if (gtl::FindCopy(flow_to_vehicle, flow.Tail(i), &vehicle)) {
432  // Head is performed on a vehicle.
433  used_vehicles[vehicle] = true;
434  int current = Start(vehicle);
435  for (int node : nodes) {
436  assignment->Add(NextVar(current))->SetValue(node);
437  used_nodes.insert(node);
438  current = node;
439  }
440  assignment->Add(NextVar(current))->SetValue(End(vehicle));
441  }
442  }
443  }
444  // Adding unused nodes.
445  for (int node = 0; node < Size(); ++node) {
446  if (!IsStart(node) && used_nodes.count(node) == 0) {
447  assignment->Add(NextVar(node))->SetValue(node);
448  }
449  }
450  // Adding unused vehicles.
451  for (int vehicle = 0; vehicle < vehicles(); ++vehicle) {
452  if (!used_vehicles[vehicle]) {
453  assignment->Add(NextVar(Start(vehicle)))->SetValue(End(vehicle));
454  }
455  }
456  return true;
457 }
458 
459 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
std::vector< int > dimensions
Dimensions represent quantities accumulated at nodes along the routes.
Definition: routing.h:2750
int nodes() const
Sizes and indices Returns the number of nodes in the model.
Definition: routing.h:1650
RoutingTransitCallback1 TransitCallback1
Definition: routing.h:281
bool IsStart(int64_t index) const
Returns true if 'index' represents the first node of a route.
Definition: routing.h:1454
const std::vector< DisjunctionIndex > & GetDisjunctionIndices(int64_t index) const
Returns the indices of the disjunctions to which an index belongs.
Definition: routing.h:791
IntVar * NextVar(int64_t index) const
!defined(SWIGPYTHON)
Definition: routing.h:1485
const std::vector< std::pair< int, int > > & GetDeliveryIndexPairs(int64_t node_index) const
Same as above for deliveries.
Definition: routing.cc:2334
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:812
const TransitCallback1 & UnaryTransitCallbackOrNull(int callback_index) const
Definition: routing.h:551
int64_t Size() const
Returns the number of next variables in the model.
Definition: routing.h:1654
static const int64_t kNoPenalty
Constant used to express a hard constraint instead of a soft penalty.
Definition: routing.h:518
int64_t GetDisjunctionMaxCardinality(DisjunctionIndex index) const
Returns the maximum number of possible active nodes of the node disjunction of index 'index'.
Definition: routing.h:823
std::vector< RoutingDimension * > GetDimensionsWithSoftOrSpanCosts() const
Returns dimensions with soft or vehicle span costs.
Definition: routing.cc:5288
int64_t GetDisjunctionPenalty(DisjunctionIndex index) const
Returns the penalty of the node disjunction of index 'index'.
Definition: routing.h:818
bool IsVehicleAllowedForIndex(int vehicle, int64_t index)
Returns true if a vehicle is allowed to visit a given node.
Definition: routing.h:860
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:4152
const IndexPairs & GetPickupAndDeliveryPairs() const
Returns pickup and delivery pairs currently in the model.
Definition: routing.h:912
int64_t Start(int vehicle) const
Model inspection.
Definition: routing.h:1450
int vehicles() const
Returns the number of vehicle routes in the model.
Definition: routing.h:1652
int GetNumberOfDisjunctions() const
Returns the number of node disjunctions in the model.
Definition: routing.h:827
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:2328
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:1456
RoutingDisjunctionIndex DisjunctionIndex
Definition: routing.h:279
int64_t End(int vehicle) const
Returns the variable index of the ending node of a vehicle route.
Definition: routing.h:1452
int64_t b
int64_t a
SatParameters parameters
GRBmodel * model
int arc
int index
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)
int64_t capacity
int64_t tail
int64_t cost
int64_t head
int nodes
std::optional< int64_t > end
int64_t start
#define VLOG(verboselevel)
Definition: vlog.h:39