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