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