OR-Tools  9.6
routing_sat.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 <algorithm>
15 #include <cstdint>
16 #include <functional>
17 #include <limits>
18 #include <map>
19 #include <memory>
20 #include <ostream>
21 #include <utility>
22 #include <vector>
23 
24 #include "absl/container/flat_hash_map.h"
25 #include "absl/time/time.h"
27 #include "ortools/base/logging.h"
28 #include "ortools/base/map_util.h"
31 #include "ortools/constraint_solver/routing_parameters.pb.h"
33 #include "ortools/sat/cp_model.pb.h"
35 #include "ortools/sat/integer.h"
36 #include "ortools/sat/model.h"
37 #include "ortools/sat/sat_parameters.pb.h"
38 #include "ortools/util/optional_boolean.pb.h"
40 
41 namespace operations_research {
42 namespace sat {
43 namespace {
44 
45 // As of 07/2019, TSPs and VRPs with homogeneous fleets of vehicles are
46 // supported.
47 // TODO(user): Support any type of constraints.
48 // TODO(user): Make VRPs properly support optional nodes.
49 bool RoutingModelCanBeSolvedBySat(const RoutingModel& model) {
50  return model.GetVehicleClassesCount() == 1;
51 }
52 
53 // Adds an integer variable to a CpModelProto, returning its index in the proto.
54 int AddVariable(CpModelProto* cp_model, int64_t lb, int64_t ub) {
55  const int index = cp_model->variables_size();
56  IntegerVariableProto* const var = cp_model->add_variables();
57  var->add_domain(lb);
58  var->add_domain(ub);
59  return index;
60 }
61 
62 // Adds a linear constraint, enforcing
63 // enforcement_literals -> lower_bound <= sum variable * coeff <= upper_bound.
64 void AddLinearConstraint(
65  CpModelProto* cp_model, int64_t lower_bound, int64_t upper_bound,
66  const std::vector<std::pair<int, double>>& variable_coeffs,
67  const std::vector<int>& enforcement_literals) {
68  CHECK_LE(lower_bound, upper_bound);
69  ConstraintProto* ct = cp_model->add_constraints();
70  for (const int enforcement_literal : enforcement_literals) {
71  ct->add_enforcement_literal(enforcement_literal);
72  }
73  LinearConstraintProto* arg = ct->mutable_linear();
74  arg->add_domain(lower_bound);
75  arg->add_domain(upper_bound);
76  for (const auto [var, coeff] : variable_coeffs) {
77  arg->add_vars(var);
78  arg->add_coeffs(coeff);
79  }
80 }
81 
82 // Adds a linear constraint, enforcing
83 // lower_bound <= sum variable * coeff <= upper_bound.
84 void AddLinearConstraint(
85  CpModelProto* cp_model, int64_t lower_bound, int64_t upper_bound,
86  const std::vector<std::pair<int, double>>& variable_coeffs) {
87  AddLinearConstraint(cp_model, lower_bound, upper_bound, variable_coeffs, {});
88 }
89 
90 // Returns the unique depot node used in the CP-SAT models (as of 01/2020).
91 int64_t GetDepotFromModel(const RoutingModel& model) { return model.Start(0); }
92 
93 // Structure to keep track of arcs created.
94 struct Arc {
95  int tail;
96  int head;
97 
98  friend bool operator==(const Arc& a, const Arc& b) {
99  return a.tail == b.tail && a.head == b.head;
100  }
101  friend bool operator!=(const Arc& a, const Arc& b) { return !(a == b); }
102  friend bool operator<(const Arc& a, const Arc& b) {
103  return a.tail == b.tail ? a.head < b.head : a.tail < b.tail;
104  }
105  friend std::ostream& operator<<(std::ostream& strm, const Arc& arc) {
106  return strm << "{" << arc.tail << ", " << arc.head << "}";
107  }
108  template <typename H>
109  friend H AbslHashValue(H h, const Arc& a) {
110  return H::combine(std::move(h), a.tail, a.head);
111  }
112 };
113 
114 using ArcVarMap = std::map<Arc, int>; // needs to be stable when iterating
115 
116 // Adds all dimensions to a CpModelProto. Only adds path cumul constraints and
117 // cumul bounds.
118 void AddDimensions(const RoutingModel& model, const ArcVarMap& arc_vars,
119  CpModelProto* cp_model) {
120  for (const RoutingDimension* dimension : model.GetDimensions()) {
121  // Only a single vehicle class.
122  const RoutingModel::TransitCallback2& transit =
123  dimension->transit_evaluator(0);
124  std::vector<int> cumuls(dimension->cumuls().size(), -1);
125  const int64_t min_start = dimension->cumuls()[model.Start(0)]->Min();
126  const int64_t max_end = std::min(dimension->cumuls()[model.End(0)]->Max(),
127  dimension->vehicle_capacities()[0]);
128  for (int i = 0; i < cumuls.size(); ++i) {
129  if (model.IsStart(i) || model.IsEnd(i)) continue;
130  // Reducing bounds supposing the triangular inequality.
131  const int64_t cumul_min =
133  std::max(dimension->cumuls()[i]->Min(),
134  CapAdd(transit(model.Start(0), i), min_start)));
135  const int64_t cumul_max =
137  std::min(dimension->cumuls()[i]->Max(),
138  CapSub(max_end, transit(i, model.End(0)))));
139  cumuls[i] = AddVariable(cp_model, cumul_min, cumul_max);
140  }
141  for (const auto arc_var : arc_vars) {
142  const int tail = arc_var.first.tail;
143  const int head = arc_var.first.head;
144  if (tail == head || model.IsStart(tail) || model.IsStart(head)) continue;
145  // arc[tail][head] -> cumuls[head] >= cumuls[tail] + transit.
146  // This is a relaxation of the model as it does not consider slack max.
147  AddLinearConstraint(
148  cp_model, transit(tail, head), std::numeric_limits<int64_t>::max(),
149  {{cumuls[head], 1}, {cumuls[tail], -1}}, {arc_var.second});
150  }
151  }
152 }
153 
154 std::vector<int> CreateRanks(const RoutingModel& model,
155  const ArcVarMap& arc_vars,
156  CpModelProto* cp_model) {
157  const int depot = GetDepotFromModel(model);
158  const int size = model.Size() + model.vehicles();
159  const int rank_size = model.Size() - model.vehicles();
160  std::vector<int> ranks(size, -1);
161  for (int i = 0; i < size; ++i) {
162  if (model.IsStart(i) || model.IsEnd(i)) continue;
163  ranks[i] = AddVariable(cp_model, 0, rank_size);
164  }
165  ranks[depot] = AddVariable(cp_model, 0, 0);
166  for (const auto arc_var : arc_vars) {
167  const int tail = arc_var.first.tail;
168  const int head = arc_var.first.head;
169  if (tail == head || head == depot) continue;
170  // arc[tail][head] -> ranks[head] == ranks[tail] + 1.
171  AddLinearConstraint(cp_model, 1, 1, {{ranks[head], 1}, {ranks[tail], -1}},
172  {arc_var.second});
173  }
174  return ranks;
175 }
176 
177 // Vehicle variables do not actually represent the index of the vehicle
178 // performing a node, but we ensure that the values of two vehicle variables
179 // are the same if and only if the corresponding nodes are served by the same
180 // vehicle.
181 std::vector<int> CreateVehicleVars(const RoutingModel& model,
182  const ArcVarMap& arc_vars,
183  CpModelProto* cp_model) {
184  const int depot = GetDepotFromModel(model);
185  const int size = model.Size() + model.vehicles();
186  std::vector<int> vehicles(size, -1);
187  for (int i = 0; i < size; ++i) {
188  if (model.IsStart(i) || model.IsEnd(i)) continue;
189  vehicles[i] = AddVariable(cp_model, 0, size - 1);
190  }
191  for (const auto arc_var : arc_vars) {
192  const int tail = arc_var.first.tail;
193  const int head = arc_var.first.head;
194  if (tail == head || head == depot) continue;
195  if (tail == depot) {
196  // arc[depot][head] -> vehicles[head] == head.
197  AddLinearConstraint(cp_model, head, head, {{vehicles[head], 1}},
198  {arc_var.second});
199  continue;
200  }
201  // arc[tail][head] -> vehicles[head] == vehicles[tail].
202  AddLinearConstraint(cp_model, 0, 0,
203  {{vehicles[head], 1}, {vehicles[tail], -1}},
204  {arc_var.second});
205  }
206  return vehicles;
207 }
208 
209 void AddPickupDeliveryConstraints(const RoutingModel& model,
210  const ArcVarMap& arc_vars,
211  CpModelProto* cp_model) {
212  if (model.GetPickupAndDeliveryPairs().empty()) return;
213  const std::vector<int> ranks = CreateRanks(model, arc_vars, cp_model);
214  const std::vector<int> vehicles =
215  CreateVehicleVars(model, arc_vars, cp_model);
216  for (const auto& pairs : model.GetPickupAndDeliveryPairs()) {
217  const int64_t pickup = pairs.first[0];
218  const int64_t delivery = pairs.second[0];
219  // ranks[pickup] + 1 <= ranks[delivery].
220  AddLinearConstraint(cp_model, 1, std::numeric_limits<int64_t>::max(),
221  {{ranks[delivery], 1}, {ranks[pickup], -1}});
222  // vehicles[pickup] == vehicles[delivery]
223  AddLinearConstraint(cp_model, 0, 0,
224  {{vehicles[delivery], 1}, {vehicles[pickup], -1}});
225  }
226 }
227 
228 // Converts a RoutingModel to CpModelProto for models with multiple vehicles.
229 // All non-start/end nodes have the same index in both models. Start/end nodes
230 // map to a single depot index; its value is arbitrarly the index of the start
231 // node of the first vehicle in the RoutingModel.
232 // The map between CPModelProto arcs and their corresponding arc variable is
233 // returned.
234 ArcVarMap PopulateMultiRouteModelFromRoutingModel(const RoutingModel& model,
235  CpModelProto* cp_model) {
236  ArcVarMap arc_vars;
237  const int num_nodes = model.Nexts().size();
238  const int depot = GetDepotFromModel(model);
239 
240  // Create "arc" variables and set their cost.
241  for (int tail = 0; tail < num_nodes; ++tail) {
242  const int tail_index = model.IsStart(tail) ? depot : tail;
243  std::unique_ptr<IntVarIterator> iter(
244  model.NextVar(tail)->MakeDomainIterator(false));
245  for (int head : InitAndGetValues(iter.get())) {
246  // Vehicle start and end nodes are represented as a single node in the
247  // CP-SAT model. We choose the start index of the first vehicle to
248  // represent both. We can also skip any head representing a vehicle start
249  // as the CP solver will reject those.
250  if (model.IsStart(head)) continue;
251  const int head_index = model.IsEnd(head) ? depot : head;
252  if (head_index == tail_index && head_index == depot) continue;
253  const int64_t cost = tail != head ? model.GetHomogeneousCost(tail, head)
254  : model.UnperformedPenalty(tail);
255  if (cost == std::numeric_limits<int64_t>::max()) continue;
256  const Arc arc = {tail_index, head_index};
257  if (gtl::ContainsKey(arc_vars, arc)) continue;
258  const int index = AddVariable(cp_model, 0, 1);
259  gtl::InsertOrDie(&arc_vars, arc, index);
260  cp_model->mutable_objective()->add_vars(index);
261  cp_model->mutable_objective()->add_coeffs(cost);
262  }
263  }
264 
265  // Limit the number of routes to the maximum number of vehicles.
266  {
267  std::vector<std::pair<int, double>> variable_coeffs;
268  for (int node = 0; node < num_nodes; ++node) {
269  if (model.IsStart(node) || model.IsEnd(node)) continue;
270  int* const var = gtl::FindOrNull(arc_vars, {depot, node});
271  if (var == nullptr) continue;
272  variable_coeffs.push_back({*var, 1});
273  }
274  AddLinearConstraint(
275  cp_model, 0,
276  std::min(model.vehicles(), model.GetMaximumNumberOfActiveVehicles()),
277  variable_coeffs);
278  }
279 
280  AddPickupDeliveryConstraints(model, arc_vars, cp_model);
281 
282  AddDimensions(model, arc_vars, cp_model);
283 
284  // Create Routes constraint, ensuring circuits from and to the depot.
285  // This one is a bit tricky, because we need to remap the depot to zero.
286  // TODO(user): Make Routes constraints support optional nodes.
287  RoutesConstraintProto* routes_ct =
288  cp_model->add_constraints()->mutable_routes();
289  for (const auto arc_var : arc_vars) {
290  const int tail = arc_var.first.tail;
291  const int head = arc_var.first.head;
292  routes_ct->add_tails(tail == 0 ? depot : tail == depot ? 0 : tail);
293  routes_ct->add_heads(head == 0 ? depot : head == depot ? 0 : head);
294  routes_ct->add_literals(arc_var.second);
295  }
296 
297  // Add demands and capacities to improve the LP relaxation and cuts. These are
298  // based on the first "unary" dimension in the model if it exists.
299  // TODO(user): We might want to try to get demand lower bounds from
300  // non-unary dimensions if no unary exist.
301  const RoutingDimension* primary_dimension = nullptr;
302  for (const RoutingDimension* dimension : model.GetDimensions()) {
303  // Only a single vehicle class is supported.
304  if (dimension->GetUnaryTransitEvaluator(0) != nullptr) {
305  primary_dimension = dimension;
306  break;
307  }
308  }
309  if (primary_dimension != nullptr) {
310  const RoutingModel::TransitCallback1& transit =
311  primary_dimension->GetUnaryTransitEvaluator(0);
312  for (int node = 0; node < num_nodes; ++node) {
313  // Tricky: demand is added for all nodes in the sat model; this means
314  // start/end nodes other than the one used for the depot must be ignored.
315  if (!model.IsEnd(node) && (!model.IsStart(node) || node == depot)) {
316  routes_ct->add_demands(transit(node));
317  }
318  }
319  DCHECK_EQ(routes_ct->demands_size(), num_nodes + 1 - model.vehicles());
320  routes_ct->set_capacity(primary_dimension->vehicle_capacities()[0]);
321  }
322  return arc_vars;
323 }
324 
325 // Converts a RoutingModel with a single vehicle to a CpModelProto.
326 // The mapping between CPModelProto arcs and their corresponding arc variables
327 // is returned.
328 ArcVarMap PopulateSingleRouteModelFromRoutingModel(const RoutingModel& model,
329  CpModelProto* cp_model) {
330  ArcVarMap arc_vars;
331  const int num_nodes = model.Nexts().size();
332  CircuitConstraintProto* circuit =
333  cp_model->add_constraints()->mutable_circuit();
334  for (int tail = 0; tail < num_nodes; ++tail) {
335  std::unique_ptr<IntVarIterator> iter(
336  model.NextVar(tail)->MakeDomainIterator(false));
337  for (int head : InitAndGetValues(iter.get())) {
338  // Vehicle start and end nodes are represented as a single node in the
339  // CP-SAT model. We choose the start index to represent both. We can also
340  // skip any head representing a vehicle start as the CP solver will reject
341  // those.
342  if (model.IsStart(head)) continue;
343  if (model.IsEnd(head)) head = model.Start(0);
344  const int64_t cost = tail != head ? model.GetHomogeneousCost(tail, head)
345  : model.UnperformedPenalty(tail);
346  if (cost == std::numeric_limits<int64_t>::max()) continue;
347  const int index = AddVariable(cp_model, 0, 1);
348  circuit->add_literals(index);
349  circuit->add_tails(tail);
350  circuit->add_heads(head);
351  cp_model->mutable_objective()->add_vars(index);
352  cp_model->mutable_objective()->add_coeffs(cost);
353  gtl::InsertOrDie(&arc_vars, {tail, head}, index);
354  }
355  }
356  AddPickupDeliveryConstraints(model, arc_vars, cp_model);
357  AddDimensions(model, arc_vars, cp_model);
358  return arc_vars;
359 }
360 
361 // Converts a RoutingModel to a CpModelProto.
362 // The mapping between CPModelProto arcs and their corresponding arc variables
363 // is returned.
364 ArcVarMap PopulateModelFromRoutingModel(const RoutingModel& model,
365  CpModelProto* cp_model) {
366  if (model.vehicles() == 1) {
367  return PopulateSingleRouteModelFromRoutingModel(model, cp_model);
368  }
369  return PopulateMultiRouteModelFromRoutingModel(model, cp_model);
370 }
371 
372 // Converts a CpSolverResponse to an Assignment containing next variables.
373 bool ConvertToSolution(const CpSolverResponse& response,
374  const RoutingModel& model, const ArcVarMap& arc_vars,
375  Assignment* solution) {
376  if (response.status() != CpSolverStatus::OPTIMAL &&
378  return false;
379  const int depot = GetDepotFromModel(model);
380  int vehicle = 0;
381  for (const auto& arc_var : arc_vars) {
382  if (response.solution(arc_var.second) != 0) {
383  const int tail = arc_var.first.tail;
384  const int head = arc_var.first.head;
385  if (head == depot) continue;
386  if (tail != depot) {
387  solution->Add(model.NextVar(tail))->SetValue(head);
388  } else {
389  solution->Add(model.NextVar(model.Start(vehicle)))->SetValue(head);
390  ++vehicle;
391  }
392  }
393  }
394  // Close open routes.
395  for (int v = 0; v < model.vehicles(); ++v) {
396  int current = model.Start(v);
397  while (solution->Contains(model.NextVar(current))) {
398  current = solution->Value(model.NextVar(current));
399  }
400  solution->Add(model.NextVar(current))->SetValue(model.End(v));
401  }
402  return true;
403 }
404 
405 // Adds dimensions to a CpModelProto for heterogeneous fleet. Adds path
406 // cumul constraints and cumul bounds.
407 void AddGeneralizedDimensions(
408  const RoutingModel& model, const ArcVarMap& arc_vars,
409  const std::vector<absl::flat_hash_map<int, int>>& vehicle_performs_node,
410  const std::vector<absl::flat_hash_map<int, int>>&
411  vehicle_class_performs_arc,
412  CpModelProto* cp_model) {
413  const int num_cp_nodes = model.Nexts().size() + model.vehicles() + 1;
414  for (const RoutingDimension* dimension : model.GetDimensions()) {
415  // Initialize cumuls.
416  std::vector<int> cumuls(num_cp_nodes, -1);
417  for (int cp_node = 1; cp_node < num_cp_nodes; ++cp_node) {
418  const int node = cp_node - 1;
419  int64_t cumul_min = dimension->cumuls()[node]->Min();
420  int64_t cumul_max = dimension->cumuls()[node]->Max();
421  if (model.IsStart(node) || model.IsEnd(node)) {
422  const int vehicle = model.VehicleIndex(node);
423  cumul_max =
424  std::min(cumul_max, dimension->vehicle_capacities()[vehicle]);
425  }
426  cumuls[cp_node] = AddVariable(cp_model, cumul_min, cumul_max);
427  }
428 
429  // Constrain cumuls with vehicle capacities.
430  for (int vehicle = 0; vehicle < model.vehicles(); vehicle++) {
431  for (int cp_node = 1; cp_node < num_cp_nodes; cp_node++) {
432  if (!vehicle_performs_node[vehicle].contains(cp_node)) continue;
433  const int64_t vehicle_capacity =
434  dimension->vehicle_capacities()[vehicle];
435  AddLinearConstraint(cp_model, std::numeric_limits<int64_t>::min(),
436  vehicle_capacity, {{cumuls[cp_node], 1}},
437  {vehicle_performs_node[vehicle].at(cp_node)});
438  }
439  }
440 
441  for (auto vehicle_class = RoutingVehicleClassIndex(0);
442  vehicle_class < model.GetVehicleClassesCount(); vehicle_class++) {
443  std::vector<int> slack(num_cp_nodes, -1);
444  const int64_t span_cost =
445  dimension->GetSpanCostCoefficientForVehicleClass(vehicle_class);
446  for (const auto [arc, arc_var] : arc_vars) {
447  const auto [cp_tail, cp_head] = arc;
448  if (cp_tail == cp_head || cp_tail == 0 || cp_head == 0) continue;
449  if (!vehicle_class_performs_arc[vehicle_class.value()].contains(
450  arc_var)) {
451  continue;
452  }
453  // Create slack variable and add span cost to the objective.
454  if (slack[cp_tail] == -1) {
455  const int64_t slack_max =
456  cp_tail - 1 < dimension->slacks().size()
457  ? dimension->slacks()[cp_tail - 1]->Max()
458  : 0;
459  slack[cp_tail] = AddVariable(cp_model, 0, slack_max);
460  if (slack_max > 0 && span_cost > 0) {
461  cp_model->mutable_objective()->add_vars(slack[cp_tail]);
462  cp_model->mutable_objective()->add_coeffs(span_cost);
463  }
464  }
465  const int64_t transit = dimension->class_transit_evaluator(
466  vehicle_class)(cp_tail - 1, cp_head - 1);
467  // vehicle_class_performs_arc[vehicle][arc_var] = 1 ->
468  // cumuls[cp_head] - cumuls[cp_tail] - slack[cp_tail] = transit
469  AddLinearConstraint(
470  cp_model, transit, transit,
471  {{cumuls[cp_head], 1}, {cumuls[cp_tail], -1}, {slack[cp_tail], -1}},
472  {vehicle_class_performs_arc[vehicle_class.value()].at(arc_var)});
473  }
474  }
475 
476  // Constrain cumuls with span limits.
477  for (int vehicle = 0; vehicle < model.vehicles(); vehicle++) {
478  const int64_t span_limit =
479  dimension->vehicle_span_upper_bounds()[vehicle];
480  if (span_limit == std::numeric_limits<int64_t>::max()) continue;
481  int cp_start = model.Start(vehicle) + 1;
482  int cp_end = model.End(vehicle) + 1;
483  AddLinearConstraint(cp_model, std::numeric_limits<int64_t>::min(),
484  span_limit,
485  {{cumuls[cp_end], 1}, {cumuls[cp_start], -1}});
486  }
487 
488  // Set soft span upper bound costs.
489  if (dimension->HasSoftSpanUpperBounds()) {
490  for (int vehicle = 0; vehicle < model.vehicles(); vehicle++) {
491  const auto [bound, cost] =
492  dimension->GetSoftSpanUpperBoundForVehicle(vehicle);
493  const int cp_start = model.Start(vehicle) + 1;
494  const int cp_end = model.End(vehicle) + 1;
495  const int extra =
496  AddVariable(cp_model, 0,
497  std::min(dimension->cumuls()[model.End(vehicle)]->Max(),
498  dimension->vehicle_capacities()[vehicle]));
499  // -inf <= cumuls[cp_end] - cumuls[cp_start] - extra <= bound
500  AddLinearConstraint(
502  {{cumuls[cp_end], 1}, {cumuls[cp_start], -1}, {extra, -1}});
503  // Add extra * cost to objective.
504  cp_model->mutable_objective()->add_vars(extra);
505  cp_model->mutable_objective()->add_coeffs(cost);
506  }
507  }
508  }
509 }
510 
511 std::vector<int> CreateGeneralizedRanks(const RoutingModel& model,
512  const ArcVarMap& arc_vars,
513  const std::vector<int>& is_unperformed,
514  CpModelProto* cp_model) {
515  const int depot = 0;
516  const int num_cp_nodes = model.Nexts().size() + model.vehicles() + 1;
517  // Maximum length of a single route (excluding the depot & vehicle end nodes).
518  const int max_rank = num_cp_nodes - 2 * model.vehicles();
519  std::vector<int> ranks(num_cp_nodes, -1);
520  ranks[depot] = AddVariable(cp_model, 0, 0);
521  for (int cp_node = 1; cp_node < num_cp_nodes; cp_node++) {
522  if (model.IsEnd(cp_node - 1)) continue;
523  ranks[cp_node] = AddVariable(cp_model, 0, max_rank);
524  // For unperformed nodes rank is 0.
525  AddLinearConstraint(cp_model, 0, 0, {{ranks[cp_node], 1}},
526  {is_unperformed[cp_node]});
527  }
528  for (const auto [arc, arc_var] : arc_vars) {
529  const auto [cp_tail, cp_head] = arc;
530  if (cp_head == 0 || model.IsEnd(cp_head - 1)) continue;
531  if (cp_tail == cp_head || cp_head == depot) continue;
532  // arc[tail][head] -> ranks[head] == ranks[tail] + 1.
533  AddLinearConstraint(cp_model, 1, 1,
534  {{ranks[cp_head], 1}, {ranks[cp_tail], -1}}, {arc_var});
535  }
536  return ranks;
537 }
538 
539 void AddGeneralizedPickupDeliveryConstraints(
540  const RoutingModel& model, const ArcVarMap& arc_vars,
541  const std::vector<absl::flat_hash_map<int, int>>& vehicle_performs_node,
542  const std::vector<int>& is_unperformed, CpModelProto* cp_model) {
543  if (model.GetPickupAndDeliveryPairs().empty()) return;
544  const std::vector<int> ranks =
545  CreateGeneralizedRanks(model, arc_vars, is_unperformed, cp_model);
546  for (const auto& pairs : model.GetPickupAndDeliveryPairs()) {
547  for (const int delivery : pairs.second) {
548  const int cp_delivery = delivery + 1;
549  for (int vehicle = 0; vehicle < model.vehicles(); vehicle++) {
550  const Arc vehicle_start_delivery_arc = {
551  static_cast<int>(model.Start(vehicle) + 1), cp_delivery};
552  if (gtl::ContainsKey(arc_vars, vehicle_start_delivery_arc)) {
553  // Forbid vehicle_start -> delivery arc.
554  AddLinearConstraint(cp_model, 0, 0,
555  {{arc_vars.at(vehicle_start_delivery_arc), 1}});
556  }
557  }
558 
559  for (const int pickup : pairs.first) {
560  const int cp_pickup = pickup + 1;
561  const Arc delivery_pickup_arc = {cp_delivery, cp_pickup};
562  if (gtl::ContainsKey(arc_vars, delivery_pickup_arc)) {
563  // Forbid delivery -> pickup arc.
564  AddLinearConstraint(cp_model, 0, 0,
565  {{arc_vars.at(delivery_pickup_arc), 1}});
566  }
567 
568  DCHECK_GE(is_unperformed[cp_delivery], 0);
569  DCHECK_GE(is_unperformed[cp_pickup], 0);
570  // A negative index i refers to NOT the literal at index -i - 1.
571  // -i - 1 ~ NOT i, if value of i in [0, 1] (boolean).
572  const int delivery_performed = -is_unperformed[cp_delivery] - 1;
573  const int pickup_performed = -is_unperformed[cp_pickup] - 1;
574  // The same vehicle performs pickup and delivery.
575  for (int vehicle = 0; vehicle < model.vehicles(); vehicle++) {
576  // delivery_performed & pickup_performed ->
577  // vehicle_performs_node[vehicle][cp_delivery] -
578  // vehicle_performs_node[vehicle][cp_pickup] = 0
579  AddLinearConstraint(
580  cp_model, 0, 0,
581  {{vehicle_performs_node[vehicle].at(cp_delivery), 1},
582  {vehicle_performs_node[vehicle].at(cp_pickup), -1}},
583  {delivery_performed, pickup_performed});
584  }
585  }
586  }
587 
588  std::vector<std::pair<int, double>> ranks_difference;
589  // -SUM(pickup)ranks[pickup].
590  for (const int pickup : pairs.first) {
591  const int cp_pickup = pickup + 1;
592  ranks_difference.push_back({ranks[cp_pickup], -1});
593  }
594  // SUM(delivery)ranks[delivery].
595  for (const int delivery : pairs.second) {
596  const int cp_delivery = delivery + 1;
597  ranks_difference.push_back({ranks[cp_delivery], 1});
598  }
599  // SUM(delivery)ranks[delivery] - SUM(pickup)ranks[pickup] >= 1
600  AddLinearConstraint(cp_model, 1, std::numeric_limits<int64_t>::max(),
601  ranks_difference);
602  }
603 }
604 
605 // Converts a RoutingModel to CpModelProto for models with multiple
606 // vehicles. The node 0 is depot. All nodes in CpModel have index increased
607 // by 1 in comparison to the RoutingModel. Each start node has only 1
608 // incoming arc (from depot), each end node has only 1 outgoing arc (to
609 // depot). The mapping from CPModelProto arcs to their corresponding arc
610 // variable is returned.
611 ArcVarMap PopulateGeneralizedRouteModelFromRoutingModel(
612  const RoutingModel& model, CpModelProto* cp_model) {
613  ArcVarMap arc_vars;
614  const int depot = 0;
615  const int num_nodes = model.Nexts().size();
616  const int num_cp_nodes = num_nodes + model.vehicles() + 1;
617  // vehicle_performs_node[vehicle][node] equals to 1 if the vehicle performs
618  // the node, and 0 otherwise.
619  std::vector<absl::flat_hash_map<int, int>> vehicle_performs_node(
620  model.vehicles());
621  // Connect vehicles start and end nodes to depot.
622  for (int vehicle = 0; vehicle < model.vehicles(); vehicle++) {
623  const int cp_start = model.Start(vehicle) + 1;
624  const Arc start_arc = {depot, cp_start};
625  const int start_arc_var = AddVariable(cp_model, 1, 1);
626  DCHECK(!gtl::ContainsKey(arc_vars, start_arc));
627  arc_vars.insert({start_arc, start_arc_var});
628 
629  const int cp_end = model.End(vehicle) + 1;
630  const Arc end_arc = {cp_end, depot};
631  const int end_arc_var = AddVariable(cp_model, 1, 1);
632  DCHECK(!gtl::ContainsKey(arc_vars, end_arc));
633  arc_vars.insert({end_arc, end_arc_var});
634 
635  vehicle_performs_node[vehicle][cp_start] = start_arc_var;
636  vehicle_performs_node[vehicle][cp_end] = end_arc_var;
637  }
638 
639  // is_unperformed[node] variable equals to 1 if visit is unperformed, and 0
640  // otherwise.
641  std::vector<int> is_unperformed(num_cp_nodes, -1);
642  // Initialize is_unperformed variables for nodes that must be performed.
643  for (int node = 0; node < num_nodes; node++) {
644  const int cp_node = node + 1;
645  // Forced active and nodes that are not involved in any disjunctions are
646  // always performed.
647  const std::vector<RoutingDisjunctionIndex>& disjunction_indices =
648  model.GetDisjunctionIndices(node);
649  if (disjunction_indices.empty() || model.ActiveVar(node)->Min() == 1) {
650  is_unperformed[cp_node] = AddVariable(cp_model, 0, 0);
651  continue;
652  }
653  // Check if the node is in a forced active disjunction.
654  for (RoutingDisjunctionIndex disjunction_index : disjunction_indices) {
655  const int num_nodes =
656  model.GetDisjunctionNodeIndices(disjunction_index).size();
657  const int64_t penalty = model.GetDisjunctionPenalty(disjunction_index);
658  const int64_t max_cardinality =
659  model.GetDisjunctionMaxCardinality(disjunction_index);
660  if (num_nodes == max_cardinality &&
661  (penalty < 0 || penalty == std::numeric_limits<int64_t>::max())) {
662  // Nodes in this disjunction are forced active.
663  is_unperformed[cp_node] = AddVariable(cp_model, 0, 0);
664  break;
665  }
666  }
667  }
668  // Add alternative visits. Create self-looped arc variables. Set penalty for
669  // not performing disjunctions.
670  for (RoutingDisjunctionIndex disjunction_index(0);
671  disjunction_index < model.GetNumberOfDisjunctions();
672  disjunction_index++) {
673  const std::vector<int64_t>& disjunction_indices =
674  model.GetDisjunctionNodeIndices(disjunction_index);
675  const int disjunction_size = disjunction_indices.size();
676  const int64_t penalty = model.GetDisjunctionPenalty(disjunction_index);
677  const int64_t max_cardinality =
678  model.GetDisjunctionMaxCardinality(disjunction_index);
679  // Case when disjunction involves only 1 node, the node is only present in
680  // this disjunction, and the node can be unperformed.
681  if (disjunction_size == 1 &&
682  model.GetDisjunctionIndices(disjunction_indices[0]).size() == 1 &&
683  is_unperformed[disjunction_indices[0] + 1] == -1) {
684  const int cp_node = disjunction_indices[0] + 1;
685  const Arc arc = {cp_node, cp_node};
686  DCHECK(!gtl::ContainsKey(arc_vars, arc));
687  is_unperformed[cp_node] = AddVariable(cp_model, 0, 1);
688  arc_vars.insert({arc, is_unperformed[cp_node]});
689  cp_model->mutable_objective()->add_vars(is_unperformed[cp_node]);
690  cp_model->mutable_objective()->add_coeffs(penalty);
691  continue;
692  }
693  // num_performed + SUM(node)is_unperformed[node] = disjunction_size
694  const int num_performed = AddVariable(cp_model, 0, max_cardinality);
695  std::vector<std::pair<int, double>> var_coeffs;
696  var_coeffs.push_back({num_performed, 1});
697  for (const int node : disjunction_indices) {
698  const int cp_node = node + 1;
699  // Node can be unperformed.
700  if (is_unperformed[cp_node] == -1) {
701  const Arc arc = {cp_node, cp_node};
702  DCHECK(!gtl::ContainsKey(arc_vars, arc));
703  is_unperformed[cp_node] = AddVariable(cp_model, 0, 1);
704  arc_vars.insert({arc, is_unperformed[cp_node]});
705  }
706  var_coeffs.push_back({is_unperformed[cp_node], 1});
707  }
708  AddLinearConstraint(cp_model, disjunction_size, disjunction_size,
709  var_coeffs);
710  // When penalty is negative or max int64_t (forced active), num_violated is
711  // 0.
712  if (penalty < 0 || penalty == std::numeric_limits<int64_t>::max()) {
713  AddLinearConstraint(cp_model, max_cardinality, max_cardinality,
714  {{num_performed, 1}});
715  continue;
716  }
717  // If number of active indices is less than max_cardinality, then for each
718  // violated index 'penalty' is paid.
719  const int num_violated = AddVariable(cp_model, 0, max_cardinality);
720  cp_model->mutable_objective()->add_vars(num_violated);
721  cp_model->mutable_objective()->add_coeffs(penalty);
722  // num_performed + num_violated = max_cardinality
723  AddLinearConstraint(cp_model, max_cardinality, max_cardinality,
724  {{num_performed, 1}, {num_violated, 1}});
725  }
726  // Create "arc" variables.
727  for (int tail = 0; tail < num_nodes; ++tail) {
728  const int cp_tail = tail + 1;
729  std::unique_ptr<IntVarIterator> iter(
730  model.NextVar(tail)->MakeDomainIterator(false));
731  for (int head : InitAndGetValues(iter.get())) {
732  const int cp_head = head + 1;
733  if (model.IsStart(head)) continue;
734  // Arcs for unperformed visits have already been created.
735  if (tail == head) continue;
736  // Direct arcs from start to end nodes should exist only if they are
737  // for the same vehicle.
738  if (model.IsStart(tail) && model.IsEnd(head) &&
739  model.VehicleIndex(tail) != model.VehicleIndex(head)) {
740  continue;
741  }
742 
743  bool feasible = false;
744  for (int vehicle = 0; vehicle < model.vehicles(); vehicle++) {
745  if (model.GetArcCostForVehicle(tail, head, vehicle) !=
747  feasible = true;
748  break;
749  }
750  }
751  if (!feasible) continue;
752 
753  const Arc arc = {cp_tail, cp_head};
754  DCHECK(!gtl::ContainsKey(arc_vars, arc));
755  const int arc_var = AddVariable(cp_model, 0, 1);
756  arc_vars.insert({arc, arc_var});
757  }
758  }
759 
760  // Set literals for vehicle performing node.
761  for (int cp_node = 1; cp_node < num_cp_nodes; cp_node++) {
762  // For starts and ends nodes vehicle_performs_node variables already set.
763  if (model.IsStart(cp_node - 1) || model.IsEnd(cp_node - 1)) continue;
764  // Each node should be performed by 1 vehicle, or be unperformed.
765  // SUM(vehicle)(vehicle_performs_node[vehicle][cp_node]) + loop(cp_node) = 1
766  std::vector<std::pair<int, double>> var_coeffs;
767  for (int vehicle = 0; vehicle < model.vehicles(); vehicle++) {
768  vehicle_performs_node[vehicle][cp_node] = AddVariable(cp_model, 0, 1);
769  var_coeffs.push_back({vehicle_performs_node[vehicle][cp_node], 1});
770  }
771  var_coeffs.push_back({is_unperformed[cp_node], 1});
772  AddLinearConstraint(cp_model, 1, 1, var_coeffs);
773  }
774  const int num_vehicle_classes = model.GetVehicleClassesCount();
775  // vehicle_class_performs_node[vehicle_class][node] equals to 1 if the
776  // vehicle of vehicle_class performs the node, and 0 otherwise.
777  std::vector<absl::flat_hash_map<int, int>> vehicle_class_performs_node(
778  num_vehicle_classes);
779  for (int cp_node = 1; cp_node < num_cp_nodes; cp_node++) {
780  const int node = cp_node - 1;
781  for (int vehicle_class = 0; vehicle_class < num_vehicle_classes;
782  vehicle_class++) {
783  if (model.IsStart(node) || model.IsEnd(node)) {
784  const int vehicle = model.VehicleIndex(node);
785  vehicle_class_performs_node[vehicle_class][cp_node] =
786  vehicle_class ==
787  model.GetVehicleClassIndexOfVehicle(vehicle).value()
788  ? AddVariable(cp_model, 1, 1)
789  : AddVariable(cp_model, 0, 0);
790  continue;
791  }
792  vehicle_class_performs_node[vehicle_class][cp_node] =
793  AddVariable(cp_model, 0, 1);
794  std::vector<std::pair<int, double>> var_coeffs;
795  for (int vehicle = 0; vehicle < model.vehicles(); vehicle++) {
796  if (model.GetVehicleClassIndexOfVehicle(vehicle).value() ==
797  vehicle_class) {
798  var_coeffs.push_back({vehicle_performs_node[vehicle][cp_node], 1});
799  // vehicle_performs_node -> vehicle_class_performs_node
800  AddLinearConstraint(
801  cp_model, 1, 1,
802  {{vehicle_class_performs_node[vehicle_class][cp_node], 1}},
803  {vehicle_performs_node[vehicle][cp_node]});
804  }
805  }
806  // vehicle_class_performs_node -> exactly one vehicle from this class
807  // performs node.
808  AddLinearConstraint(
809  cp_model, 1, 1, var_coeffs,
810  {vehicle_class_performs_node[vehicle_class][cp_node]});
811  }
812  }
813  // vehicle_class_performs_arc[vehicle_class][arc_var] equals to 1 if the
814  // vehicle of vehicle_class performs the arc, and 0 otherwise.
815  std::vector<absl::flat_hash_map<int, int>> vehicle_class_performs_arc(
816  num_vehicle_classes);
817  // Set "arc" costs.
818  for (const auto [arc, arc_var] : arc_vars) {
819  const auto [cp_tail, cp_head] = arc;
820  if (cp_tail == depot || cp_head == depot) continue;
821  const int tail = cp_tail - 1;
822  const int head = cp_head - 1;
823  // Costs for unperformed arcs have already been set.
824  if (tail == head) continue;
825  for (int vehicle = 0; vehicle < model.vehicles(); vehicle++) {
826  // The arc can't be performed by the vehicle when vehicle can't perform
827  // arc nodes.
828  if (!vehicle_performs_node[vehicle].contains(cp_tail) ||
829  !vehicle_performs_node[vehicle].contains(cp_head)) {
830  continue;
831  }
832  int64_t cost = model.GetArcCostForVehicle(tail, head, vehicle);
833  // Arcs with int64_t's max cost are infeasible.
834  if (cost == std::numeric_limits<int64_t>::max()) continue;
835  const int vehicle_class =
836  model.GetVehicleClassIndexOfVehicle(vehicle).value();
837  if (!vehicle_class_performs_arc[vehicle_class].contains(arc_var)) {
838  vehicle_class_performs_arc[vehicle_class][arc_var] =
839  AddVariable(cp_model, 0, 1);
840  // Create constraints to set vehicle_class_performs_arc.
841  // vehicle_class_performs_arc ->
842  // vehicle_class_performs_tail & vehicle_class_performs_head &
843  // arc_is_performed
844  ConstraintProto* ct = cp_model->add_constraints();
845  ct->add_enforcement_literal(
846  vehicle_class_performs_arc[vehicle_class][arc_var]);
847  BoolArgumentProto* bool_and = ct->mutable_bool_and();
848  bool_and->add_literals(
849  vehicle_class_performs_node[vehicle_class][cp_tail]);
850  bool_and->add_literals(
851  vehicle_class_performs_node[vehicle_class][cp_head]);
852  bool_and->add_literals(arc_var);
853  // Don't add arcs with zero cost to the objective.
854  if (cost != 0) {
855  cp_model->mutable_objective()->add_vars(
856  vehicle_class_performs_arc[vehicle_class][arc_var]);
857  cp_model->mutable_objective()->add_coeffs(cost);
858  }
859  }
860  // (arc_is_performed & vehicle_performs_tail) ->
861  // (vehicle_class_performs_arc & vehicle_performs_head)
862  ConstraintProto* ct_arc_tail = cp_model->add_constraints();
863  ct_arc_tail->add_enforcement_literal(arc_var);
864  ct_arc_tail->add_enforcement_literal(
865  vehicle_performs_node[vehicle][cp_tail]);
866  ct_arc_tail->mutable_bool_and()->add_literals(
867  vehicle_class_performs_arc[vehicle_class][arc_var]);
868  ct_arc_tail->mutable_bool_and()->add_literals(
869  vehicle_performs_node[vehicle][cp_head]);
870  // (arc_is_performed & vehicle_performs_head) ->
871  // (vehicle_class_performs_arc & vehicle_performs_tail)
872  ConstraintProto* ct_arc_head = cp_model->add_constraints();
873  ct_arc_head->add_enforcement_literal(arc_var);
874  ct_arc_head->add_enforcement_literal(
875  vehicle_performs_node[vehicle][cp_head]);
876  ct_arc_head->mutable_bool_and()->add_literals(
877  vehicle_class_performs_arc[vehicle_class][arc_var]);
878  ct_arc_head->mutable_bool_and()->add_literals(
879  vehicle_performs_node[vehicle][cp_tail]);
880  }
881  }
882 
883  AddGeneralizedPickupDeliveryConstraints(
884  model, arc_vars, vehicle_performs_node, is_unperformed, cp_model);
885 
886  AddGeneralizedDimensions(model, arc_vars, vehicle_performs_node,
887  vehicle_class_performs_arc, cp_model);
888 
889  // Create Routes constraint, ensuring circuits from and to the depot.
890  RoutesConstraintProto* routes_ct =
891  cp_model->add_constraints()->mutable_routes();
892  for (const auto [arc, arc_var] : arc_vars) {
893  const int tail = arc.tail;
894  const int head = arc.head;
895  routes_ct->add_tails(tail);
896  routes_ct->add_heads(head);
897  routes_ct->add_literals(arc_var);
898  }
899 
900  // Add demands and capacities to improve the LP relaxation and cuts. These
901  // are based on the first "unary" dimension in the model if it exists.
902  // TODO(user): We might want to try to get demand lower bounds from
903  // non-unary dimensions if no unary exist.
904  const RoutingDimension* primary_dimension = nullptr;
905  for (const RoutingDimension* dimension : model.GetDimensions()) {
906  bool is_unary = true;
907  for (int vehicle = 0; vehicle < model.vehicles(); vehicle++) {
908  if (dimension->GetUnaryTransitEvaluator(vehicle) == nullptr) {
909  is_unary = false;
910  break;
911  }
912  }
913  if (is_unary) {
914  primary_dimension = dimension;
915  break;
916  }
917  }
918  if (primary_dimension != nullptr) {
919  for (int cp_node = 0; cp_node < num_cp_nodes; ++cp_node) {
920  int64_t min_transit = std::numeric_limits<int64_t>::max();
921  if (cp_node != 0 && !model.IsEnd(cp_node - 1)) {
922  for (int vehicle = 0; vehicle < model.vehicles(); vehicle++) {
923  const RoutingModel::TransitCallback1& transit =
924  primary_dimension->GetUnaryTransitEvaluator(vehicle);
925  min_transit = std::min(min_transit, transit(cp_node - 1));
926  }
927  } else {
928  min_transit = 0;
929  }
930  routes_ct->add_demands(min_transit);
931  }
932  DCHECK_EQ(routes_ct->demands_size(), num_cp_nodes);
933  int64_t max_capacity = std::numeric_limits<int64_t>::min();
934  for (int vehicle = 0; vehicle < model.vehicles(); vehicle++) {
935  max_capacity = std::max(max_capacity,
936  primary_dimension->vehicle_capacities()[vehicle]);
937  }
938  routes_ct->set_capacity(max_capacity);
939  }
940  return arc_vars;
941 }
942 
943 // Converts a CpSolverResponse to an Assignment containing next variables.
944 bool ConvertGeneralizedResponseToSolution(const CpSolverResponse& response,
945  const RoutingModel& model,
946  const ArcVarMap& arc_vars,
947  Assignment* solution) {
948  if (response.status() != CpSolverStatus::OPTIMAL &&
949  response.status() != CpSolverStatus::FEASIBLE) {
950  return false;
951  }
952  const int depot = 0;
953  for (const auto [arc, arc_var] : arc_vars) {
954  if (response.solution(arc_var) == 0) continue;
955  const auto [tail, head] = arc;
956  if (head == depot || tail == depot) continue;
957  solution->Add(model.NextVar(tail - 1))->SetValue(head - 1);
958  }
959  return true;
960 }
961 
962 // Uses CP solution as hint for CP-SAT.
963 void AddSolutionAsHintToGeneralizedModel(const Assignment* solution,
964  const RoutingModel& model,
965  const ArcVarMap& arc_vars,
966  CpModelProto* cp_model) {
967  if (solution == nullptr) return;
968  PartialVariableAssignment* const hint = cp_model->mutable_solution_hint();
969  hint->Clear();
970  const int num_nodes = model.Nexts().size();
971  for (int tail = 0; tail < num_nodes; ++tail) {
972  const int cp_tail = tail + 1;
973  const int cp_head = solution->Value(model.NextVar(tail)) + 1;
974  const int* const arc_var = gtl::FindOrNull(arc_vars, {cp_tail, cp_head});
975  // Arcs with a cost of max int64_t are not added to the model (considered as
976  // infeasible). In some rare cases CP solutions might contain such arcs in
977  // which case they are skipped here and a partial solution is used as a
978  // hint.
979  if (arc_var == nullptr) continue;
980  hint->add_vars(*arc_var);
981  hint->add_values(1);
982  }
983 }
984 
985 void AddSolutionAsHintToModel(const Assignment* solution,
986  const RoutingModel& model,
987  const ArcVarMap& arc_vars,
988  CpModelProto* cp_model) {
989  if (solution == nullptr) return;
990  PartialVariableAssignment* const hint = cp_model->mutable_solution_hint();
991  hint->Clear();
992  const int depot = GetDepotFromModel(model);
993  const int num_nodes = model.Nexts().size();
994  for (int tail = 0; tail < num_nodes; ++tail) {
995  const int tail_index = model.IsStart(tail) ? depot : tail;
996  const int head = solution->Value(model.NextVar(tail));
997  const int head_index = model.IsEnd(head) ? depot : head;
998  if (tail_index == depot && head_index == depot) continue;
999  const int* const var_index =
1000  gtl::FindOrNull(arc_vars, {tail_index, head_index});
1001  // Arcs with a cost of kint64max are not added to the model (considered as
1002  // infeasible). In some rare cases CP solutions might contain such arcs in
1003  // which case they are skipped here and a partial solution is used as a
1004  // hint.
1005  if (var_index == nullptr) continue;
1006  hint->add_vars(*var_index);
1007  hint->add_values(1);
1008  }
1009 }
1010 
1011 // Configures a CP-SAT solver and solves the given (routing) model using it.
1012 // Returns the response of the search.
1013 CpSolverResponse SolveRoutingModel(
1014  const CpModelProto& cp_model, absl::Duration remaining_time,
1015  const RoutingSearchParameters& search_parameters,
1016  const std::function<void(const CpSolverResponse& response)>& observer) {
1017  // Copying to set remaining time.
1018  SatParameters sat_parameters = search_parameters.sat_parameters();
1019  if (!sat_parameters.has_max_time_in_seconds()) {
1020  sat_parameters.set_max_time_in_seconds(
1021  absl::ToDoubleSeconds(remaining_time));
1022  } else {
1023  sat_parameters.set_max_time_in_seconds(
1024  std::min(absl::ToDoubleSeconds(remaining_time),
1025  sat_parameters.max_time_in_seconds()));
1026  }
1027  Model model;
1028  model.Add(NewSatParameters(sat_parameters));
1029  if (observer != nullptr) {
1030  model.Add(NewFeasibleSolutionObserver(observer));
1031  }
1032  // TODO(user): Add an option to dump the CP-SAT model or check if the
1033  // cp_model_dump_file flag in cp_model_solver.cc is good enough.
1034  return SolveCpModel(cp_model, &model);
1035 }
1036 
1037 // Check if all the nodes are present in arcs. Otherwise, CP-SAT solver may
1038 // fail.
1039 bool IsFeasibleArcVarMap(const ArcVarMap& arc_vars, int max_node_index) {
1040  Bitset64<> present_in_arcs(max_node_index + 1);
1041  for (const auto [arc, _] : arc_vars) {
1042  present_in_arcs.Set(arc.head);
1043  present_in_arcs.Set(arc.tail);
1044  }
1045  for (int i = 0; i <= max_node_index; i++) {
1046  if (!present_in_arcs[i]) return false;
1047  }
1048  return true;
1049 }
1050 
1051 } // namespace
1052 } // namespace sat
1053 
1054 // Solves a RoutingModel using the CP-SAT solver. Returns false if no solution
1055 // was found.
1057  const RoutingSearchParameters& search_parameters,
1058  const Assignment* initial_solution,
1059  Assignment* solution) {
1060  sat::CpModelProto cp_model;
1061  cp_model.mutable_objective()->set_scaling_factor(
1062  search_parameters.log_cost_scaling_factor());
1063  cp_model.mutable_objective()->set_offset(search_parameters.log_cost_offset());
1064  if (search_parameters.use_generalized_cp_sat() == BOOL_TRUE) {
1065  const sat::ArcVarMap arc_vars =
1066  sat::PopulateGeneralizedRouteModelFromRoutingModel(model, &cp_model);
1067  const int max_node_index = model.Nexts().size() + model.vehicles();
1068  if (!sat::IsFeasibleArcVarMap(arc_vars, max_node_index)) return false;
1069  sat::AddSolutionAsHintToGeneralizedModel(initial_solution, model, arc_vars,
1070  &cp_model);
1071  return sat::ConvertGeneralizedResponseToSolution(
1072  sat::SolveRoutingModel(cp_model, model.RemainingTime(),
1073  search_parameters, nullptr),
1074  model, arc_vars, solution);
1075  }
1076  if (!sat::RoutingModelCanBeSolvedBySat(model)) return false;
1077  const sat::ArcVarMap arc_vars =
1078  sat::PopulateModelFromRoutingModel(model, &cp_model);
1079  sat::AddSolutionAsHintToModel(initial_solution, model, arc_vars, &cp_model);
1080  return sat::ConvertToSolution(
1081  sat::SolveRoutingModel(cp_model, model.RemainingTime(), search_parameters,
1082  nullptr),
1083  model, arc_vars, solution);
1084 }
1085 
1086 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int max_rank
Definition: alldiff_cst.cc:142
int64_t min
Definition: alldiff_cst.cc:139
An Assignment is a variable -> domains mapping, used to report solutions to the user.
RoutingTransitCallback1 TransitCallback1
Definition: routing.h:281
RoutingTransitCallback2 TransitCallback2
Definition: routing.h:282
int64_t b
int64_t a
SharedResponseManager * response
const Constraint * ct
int64_t value
IntVar * var
Definition: expr_array.cc:1874
GRBmodel * model
int arc
int head_index
int index
int tail_index
void InsertOrDie(Collection *const collection, const typename Collection::value_type &value)
Definition: map_util.h:154
bool ContainsKey(const Collection &collection, const Key &key)
Definition: map_util.h:200
const Collection::value_type::second_type * FindOrNull(const Collection &collection, const typename Collection::value_type::first_type &key)
Definition: map_util.h:60
bool operator!=(const IndicatorConstraint &lhs, const IndicatorConstraint &rhs)
std::function< void(Model *)> NewFeasibleSolutionObserver(const std::function< void(const CpSolverResponse &response)> &observer)
Creates a solution observer with the model with model.Add(NewFeasibleSolutionObserver([](response){....
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
std::ostream & operator<<(std::ostream &os, const BoolVar &var)
Definition: cp_model.cc:88
std::function< SatParameters(Model *)> NewSatParameters(const std::string &params)
Creates parameters for the solver, which you can add to the model with.
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
CpSolverResponse SolveCpModel(const CpModelProto &model_proto, Model *model)
Solves the given CpModelProto.
H AbslHashValue(H h, const IntVar &i)
Definition: cp_model.h:510
Collection of objects used to extend the Constraint Solver library.
bool SolveModelWithSat(const RoutingModel &model, const RoutingSearchParameters &search_parameters, const Assignment *initial_solution, Assignment *solution)
Attempts to solve the model using the cp-sat solver.
int64_t CapAdd(int64_t x, int64_t y)
int64_t CapSub(int64_t x, int64_t y)
LinearRange operator==(const LinearExpr &lhs, const LinearExpr &rhs)
Definition: linear_expr.cc:184
IntVar * upper_bound
Definition: routing.cc:1087
IntVar * lower_bound
Definition: routing.cc:1086
int64_t bound
int64_t cost
int head
Definition: routing_sat.cc:96
int tail
Definition: routing_sat.cc:95
int vehicle_class