1 // Copyright 2010-2021 Google LLC
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 #include "ortools/constraint_solver/routing.h"
15
16 #include <limits.h>
17
18 #include <algorithm>
19 #include <cmath>
20 #include <cstddef>
21 #include <cstdint>
22 #include <cstring>
23 #include <deque>
24 #include <functional>
25 #include <iterator>
26 #include <limits>
27 #include <map>
28 #include <memory>
29 #include <set>
30 #include <string>
31 #include <tuple>
32 #include <type_traits>
33 #include <utility>
34 #include <vector>
35
36 #include "absl/container/flat_hash_map.h"
37 #include "absl/container/flat_hash_set.h"
38 #include "absl/flags/flag.h"
39 #include "absl/functional/bind_front.h"
40 #include "absl/memory/memory.h"
41 #include "absl/meta/type_traits.h"
42 #include "absl/status/statusor.h"
43 #include "absl/strings/str_cat.h"
44 #include "absl/strings/str_format.h"
45 #include "absl/strings/str_join.h"
46 #include "absl/strings/string_view.h"
47 #include "absl/time/time.h"
48 #include "ortools/base/int_type.h"
49 #include "ortools/base/integral_types.h"
50 #include "ortools/base/logging.h"
51 #include "ortools/base/map_util.h"
52 #include "ortools/base/mathutil.h"
53 #include "ortools/base/protoutil.h"
54 #include "ortools/base/stl_util.h"
55 #include "ortools/base/strong_vector.h"
56 #include "ortools/base/thorough_hash.h"
57 #include "ortools/constraint_solver/constraint_solver.h"
58 #include "ortools/constraint_solver/constraint_solveri.h"
59 #include "ortools/constraint_solver/routing_enums.pb.h"
60 #include "ortools/constraint_solver/routing_filters.h"
61 #include "ortools/constraint_solver/routing_index_manager.h"
62 #include "ortools/constraint_solver/routing_lp_scheduling.h"
63 #include "ortools/constraint_solver/routing_neighborhoods.h"
64 #include "ortools/constraint_solver/routing_parameters.h"
65 #include "ortools/constraint_solver/routing_parameters.pb.h"
66 #include "ortools/constraint_solver/routing_search.h"
67 #include "ortools/constraint_solver/routing_types.h"
68 #include "ortools/constraint_solver/solver_parameters.pb.h"
69 #include "ortools/graph/connected_components.h"
70 #include "ortools/graph/ebert_graph.h"
71 #include "ortools/graph/graph.h"
72 #include "ortools/graph/linear_assignment.h"
73 #include "ortools/util/bitset.h"
74 #include "ortools/util/optional_boolean.pb.h"
75 #include "ortools/util/piecewise_linear_function.h"
76 #include "ortools/util/range_query_function.h"
77 #include "ortools/util/saturated_arithmetic.h"
78 #include "ortools/util/sorted_interval_list.h"
79 #include "ortools/util/stats.h"
80
81 namespace operations_research {
82 class Cross;
83 class Exchange;
84 class ExtendedSwapActiveOperator;
85 class LocalSearchPhaseParameters;
86 class MakeActiveAndRelocate;
87 class MakeActiveOperator;
88 class MakeChainInactiveOperator;
89 class MakeInactiveOperator;
90 class Relocate;
91 class RelocateAndMakeActiveOperator;
92 class SwapActiveOperator;
93 class TwoOpt;
94 } // namespace operations_research
95
96 // Trace settings
97
98 // TODO(user): Move most of the following settings to a model parameter
99 // proto.
100
101 namespace operations_research {
102
103 namespace {
104 using ResourceGroup = RoutingModel::ResourceGroup;
105
106 // A decision builder which tries to assign values to variables as close as
107 // possible to target values first.
108 // TODO(user): Move to CP solver.
109 class SetValuesFromTargets : public DecisionBuilder {
110 public:
SetValuesFromTargets(std::vector<IntVar * > variables,std::vector<int64_t> targets)111 SetValuesFromTargets(std::vector<IntVar*> variables,
112 std::vector<int64_t> targets)
113 : variables_(std::move(variables)),
114 targets_(std::move(targets)),
115 index_(0),
116 steps_(variables_.size(), 0) {
117 DCHECK_EQ(variables_.size(), targets_.size());
118 }
Next(Solver * const solver)119 Decision* Next(Solver* const solver) override {
120 int index = index_.Value();
121 while (index < variables_.size() && variables_[index]->Bound()) {
122 ++index;
123 }
124 index_.SetValue(solver, index);
125 if (index >= variables_.size()) return nullptr;
126 const int64_t variable_min = variables_[index]->Min();
127 const int64_t variable_max = variables_[index]->Max();
128 // Target can be before, inside, or after the variable range.
129 // We do a trichotomy on this for clarity.
130 if (targets_[index] <= variable_min) {
131 return solver->MakeAssignVariableValue(variables_[index], variable_min);
132 } else if (targets_[index] >= variable_max) {
133 return solver->MakeAssignVariableValue(variables_[index], variable_max);
134 } else {
135 int64_t step = steps_[index];
136 int64_t value = CapAdd(targets_[index], step);
137 // If value is out of variable's range, we can remove the interval of
138 // values already explored (which can make the solver fail) and
139 // recall Next() to get back into the trichotomy above.
140 if (value < variable_min || variable_max < value) {
141 step = GetNextStep(step);
142 value = CapAdd(targets_[index], step);
143 if (step > 0) {
144 // Values in [variable_min, value) were already explored.
145 variables_[index]->SetMin(value);
146 } else {
147 // Values in (value, variable_max] were already explored.
148 variables_[index]->SetMax(value);
149 }
150 return Next(solver);
151 }
152 steps_.SetValue(solver, index, GetNextStep(step));
153 return solver->MakeAssignVariableValueOrDoNothing(variables_[index],
154 value);
155 }
156 }
157
158 private:
GetNextStep(int64_t step) const159 int64_t GetNextStep(int64_t step) const {
160 return (step > 0) ? -step : CapSub(1, step);
161 }
162 const std::vector<IntVar*> variables_;
163 const std::vector<int64_t> targets_;
164 Rev<int> index_;
165 RevArray<int64_t> steps_;
166 };
167
168 } // namespace
169
MakeSetValuesFromTargets(Solver * solver,std::vector<IntVar * > variables,std::vector<int64_t> targets)170 DecisionBuilder* MakeSetValuesFromTargets(Solver* solver,
171 std::vector<IntVar*> variables,
172 std::vector<int64_t> targets) {
173 return solver->RevAlloc(
174 new SetValuesFromTargets(std::move(variables), std::move(targets)));
175 }
176
177 namespace {
178
DimensionFixedTransitsEqualTransitEvaluatorForVehicle(const RoutingDimension & dimension,int vehicle)179 bool DimensionFixedTransitsEqualTransitEvaluatorForVehicle(
180 const RoutingDimension& dimension, int vehicle) {
181 const RoutingModel* const model = dimension.model();
182 int node = model->Start(vehicle);
183 while (!model->IsEnd(node)) {
184 if (!model->NextVar(node)->Bound()) {
185 return false;
186 }
187 const int next = model->NextVar(node)->Value();
188 if (dimension.transit_evaluator(vehicle)(node, next) !=
189 dimension.FixedTransitVar(node)->Value()) {
190 return false;
191 }
192 node = next;
193 }
194 return true;
195 }
196
DimensionFixedTransitsEqualTransitEvaluators(const RoutingDimension & dimension)197 bool DimensionFixedTransitsEqualTransitEvaluators(
198 const RoutingDimension& dimension) {
199 for (int vehicle = 0; vehicle < dimension.model()->vehicles(); vehicle++) {
200 if (!DimensionFixedTransitsEqualTransitEvaluatorForVehicle(dimension,
201 vehicle)) {
202 return false;
203 }
204 }
205 return true;
206 }
207
208 // Concatenates cumul_values and break_values into 'values', and generates the
209 // corresponding 'variables' vector.
ConcatenateRouteCumulAndBreakVarAndValues(const RoutingDimension & dimension,int vehicle,const std::vector<int64_t> & cumul_values,const std::vector<int64_t> & break_values,std::vector<IntVar * > * variables,std::vector<int64_t> * values)210 void ConcatenateRouteCumulAndBreakVarAndValues(
211 const RoutingDimension& dimension, int vehicle,
212 const std::vector<int64_t>& cumul_values,
213 const std::vector<int64_t>& break_values, std::vector<IntVar*>* variables,
214 std::vector<int64_t>* values) {
215 *values = cumul_values;
216 variables->clear();
217 const RoutingModel& model = *dimension.model();
218 {
219 int current = model.Start(vehicle);
220 while (true) {
221 variables->push_back(dimension.CumulVar(current));
222 if (!model.IsEnd(current)) {
223 current = model.NextVar(current)->Value();
224 } else {
225 break;
226 }
227 }
228 }
229 // Setting the cumuls of path start/end first is more efficient than
230 // setting the cumuls in order of path appearance, because setting start
231 // and end cumuls gives an opportunity to fix all cumuls with two
232 // decisions instead of |path| decisions.
233 // To this effect, we put end cumul just after the start cumul.
234 std::swap(variables->at(1), variables->back());
235 std::swap(values->at(1), values->back());
236 if (dimension.HasBreakConstraints()) {
237 for (IntervalVar* interval :
238 dimension.GetBreakIntervalsOfVehicle(vehicle)) {
239 variables->push_back(interval->SafeStartExpr(0)->Var());
240 variables->push_back(interval->SafeEndExpr(0)->Var());
241 }
242 values->insert(values->end(), break_values.begin(), break_values.end());
243 }
244 // Value kint64min signals an unoptimized variable, set to min instead.
245 for (int j = 0; j < values->size(); ++j) {
246 if (values->at(j) == std::numeric_limits<int64_t>::min()) {
247 values->at(j) = variables->at(j)->Min();
248 }
249 }
250 DCHECK_EQ(variables->size(), values->size());
251 }
252
253 class SetCumulsFromLocalDimensionCosts : public DecisionBuilder {
254 public:
SetCumulsFromLocalDimensionCosts(const std::vector<std::unique_ptr<LocalDimensionCumulOptimizer>> * local_optimizers,const std::vector<std::unique_ptr<LocalDimensionCumulOptimizer>> * local_mp_optimizers,const std::function<bool (const RoutingDimension *)> & set_dimension_cumuls,SearchMonitor * monitor,bool optimize_and_pack=false)255 SetCumulsFromLocalDimensionCosts(
256 const std::vector<std::unique_ptr<LocalDimensionCumulOptimizer>>*
257 local_optimizers,
258 const std::vector<std::unique_ptr<LocalDimensionCumulOptimizer>>*
259 local_mp_optimizers,
260 const std::function<bool(const RoutingDimension*)>& set_dimension_cumuls,
261 SearchMonitor* monitor, bool optimize_and_pack = false)
262 : monitor_(monitor), optimize_and_pack_(optimize_and_pack) {
263 for (int i = 0; i < local_optimizers->size(); i++) {
264 if (!set_dimension_cumuls(local_optimizers->at(i)->dimension())) {
265 continue;
266 }
267 const auto& optimizer = local_optimizers->at(i);
268 local_optimizers_.push_back(optimizer.get());
269 local_mp_optimizers_.push_back(local_mp_optimizers->at(i).get());
270
271 const RoutingDimension* const dimension = optimizer->dimension();
272 const std::vector<int>& resource_groups =
273 dimension->model()->GetDimensionResourceGroupIndices(dimension);
274 DCHECK_LE(resource_groups.size(), optimize_and_pack ? 1 : 0);
275 resource_group_index_.push_back(
276 resource_groups.empty() ? -1 : resource_groups[0]);
277 }
278 }
Next(Solver * const solver)279 Decision* Next(Solver* const solver) override {
280 // The following boolean variable indicates if the solver should fail, in
281 // order to postpone the Fail() call until after the internal for loop, so
282 // there are no memory leaks related to the cumul_values vector.
283 bool should_fail = false;
284 for (int i = 0; i < local_optimizers_.size(); ++i) {
285 LocalDimensionCumulOptimizer* const local_optimizer =
286 local_optimizers_[i];
287 const RoutingDimension& dimension = *local_optimizer->dimension();
288 RoutingModel* const model = dimension.model();
289 const auto next = [model](int64_t n) {
290 return model->NextVar(n)->Value();
291 };
292 const int rg_index = resource_group_index_[i];
293 const auto compute_cumul_values =
294 [this, &model, &next, rg_index](
295 LocalDimensionCumulOptimizer* optimizer, int vehicle,
296 std::vector<int64_t>* cumul_values,
297 std::vector<int64_t>* break_start_end_values) {
298 if (optimize_and_pack_) {
299 const int resource_index =
300 rg_index < 0 ? -1
301 : model->ResourceVar(vehicle, rg_index)->Value();
302 const Resource* const resource =
303 resource_index < 0
304 ? nullptr
305 : &model->GetResourceGroup(rg_index)->GetResource(
306 resource_index);
307 return optimizer->ComputePackedRouteCumuls(
308 vehicle, next, resource, cumul_values,
309 break_start_end_values);
310 } else {
311 return optimizer->ComputeRouteCumuls(vehicle, next, cumul_values,
312 break_start_end_values);
313 }
314 };
315 for (int vehicle = 0; vehicle < model->vehicles(); ++vehicle) {
316 solver->TopPeriodicCheck();
317 // TODO(user): Investigate if we should skip unused vehicles.
318 DCHECK(DimensionFixedTransitsEqualTransitEvaluatorForVehicle(dimension,
319 vehicle));
320 const bool vehicle_has_break_constraint =
321 dimension.HasBreakConstraints() &&
322 !dimension.GetBreakIntervalsOfVehicle(vehicle).empty();
323 LocalDimensionCumulOptimizer* const optimizer =
324 vehicle_has_break_constraint ? local_mp_optimizers_[i]
325 : local_optimizer;
326 DCHECK(optimizer != nullptr);
327 std::vector<int64_t> cumul_values;
328 std::vector<int64_t> break_start_end_values;
329 const DimensionSchedulingStatus status = compute_cumul_values(
330 optimizer, vehicle, &cumul_values, &break_start_end_values);
331 if (status == DimensionSchedulingStatus::INFEASIBLE) {
332 should_fail = true;
333 break;
334 }
335 // If relaxation is not feasible, try the MILP optimizer.
336 if (status == DimensionSchedulingStatus::RELAXED_OPTIMAL_ONLY) {
337 cumul_values.clear();
338 break_start_end_values.clear();
339 DCHECK(local_mp_optimizers_[i] != nullptr);
340 if (compute_cumul_values(local_mp_optimizers_[i], vehicle,
341 &cumul_values, &break_start_end_values) ==
342 DimensionSchedulingStatus::INFEASIBLE) {
343 should_fail = true;
344 break;
345 }
346 } else {
347 DCHECK(status == DimensionSchedulingStatus::OPTIMAL);
348 }
349 // Concatenate cumul_values and break_start_end_values into cp_values,
350 // generate corresponding cp_variables vector.
351 std::vector<IntVar*> cp_variables;
352 std::vector<int64_t> cp_values;
353 ConcatenateRouteCumulAndBreakVarAndValues(
354 dimension, vehicle, cumul_values, break_start_end_values,
355 &cp_variables, &cp_values);
356 if (!solver->SolveAndCommit(
357 MakeSetValuesFromTargets(solver, std::move(cp_variables),
358 std::move(cp_values)),
359 monitor_)) {
360 should_fail = true;
361 break;
362 }
363 }
364 if (should_fail) {
365 break;
366 }
367 }
368 if (should_fail) {
369 solver->Fail();
370 }
371 return nullptr;
372 }
373
374 private:
375 using Resource = RoutingModel::ResourceGroup::Resource;
376
377 std::vector<LocalDimensionCumulOptimizer*> local_optimizers_;
378 std::vector<LocalDimensionCumulOptimizer*> local_mp_optimizers_;
379 // Stores the resource group index of dimensions in local_[mp_]optimizers_.
380 std::vector<int> resource_group_index_;
381 SearchMonitor* const monitor_;
382 const bool optimize_and_pack_;
383 };
384
385 class SetCumulsFromGlobalDimensionCosts : public DecisionBuilder {
386 public:
SetCumulsFromGlobalDimensionCosts(const std::vector<std::unique_ptr<GlobalDimensionCumulOptimizer>> * global_optimizers,const std::vector<std::unique_ptr<GlobalDimensionCumulOptimizer>> * global_mp_optimizers,SearchMonitor * monitor,bool optimize_and_pack=false)387 SetCumulsFromGlobalDimensionCosts(
388 const std::vector<std::unique_ptr<GlobalDimensionCumulOptimizer>>*
389 global_optimizers,
390 const std::vector<std::unique_ptr<GlobalDimensionCumulOptimizer>>*
391 global_mp_optimizers,
392 SearchMonitor* monitor, bool optimize_and_pack = false)
393 : monitor_(monitor), optimize_and_pack_(optimize_and_pack) {
394 for (int i = 0; i < global_optimizers->size(); i++) {
395 global_optimizers_.push_back(global_optimizers->at(i).get());
396 global_mp_optimizers_.push_back(global_mp_optimizers->at(i).get());
397 }
398 }
Next(Solver * const solver)399 Decision* Next(Solver* const solver) override {
400 // The following boolean variable indicates if the solver should fail, in
401 // order to postpone the Fail() call until after the for loop, so there are
402 // no memory leaks related to the cumul_values vector.
403 bool should_fail = false;
404 for (int i = 0; i < global_optimizers_.size(); ++i) {
405 GlobalDimensionCumulOptimizer* const global_optimizer =
406 global_optimizers_[i];
407 const RoutingDimension* dimension = global_optimizer->dimension();
408 RoutingModel* const model = dimension->model();
409
410 const auto next = [model](int64_t n) {
411 return model->NextVar(n)->Value();
412 };
413
414 DCHECK(DimensionFixedTransitsEqualTransitEvaluators(*dimension));
415
416 std::vector<int64_t> cumul_values;
417 std::vector<int64_t> break_start_end_values;
418 std::vector<std::vector<int>> resource_indices_per_group;
419 const DimensionSchedulingStatus status =
420 !model->GetDimensionResourceGroupIndices(dimension).empty()
421 ? DimensionSchedulingStatus::RELAXED_OPTIMAL_ONLY
422 : optimize_and_pack_
423 ? global_optimizer->ComputePackedCumuls(
424 next, &cumul_values, &break_start_end_values,
425 &resource_indices_per_group)
426 : global_optimizer->ComputeCumuls(next, &cumul_values,
427 &break_start_end_values,
428 &resource_indices_per_group);
429
430 if (status == DimensionSchedulingStatus::INFEASIBLE) {
431 should_fail = true;
432 break;
433 }
434 // If relaxation is not feasible, try the MILP optimizer.
435 if (status == DimensionSchedulingStatus::RELAXED_OPTIMAL_ONLY) {
436 cumul_values.clear();
437 break_start_end_values.clear();
438 DCHECK(global_mp_optimizers_[i] != nullptr);
439 const DimensionSchedulingStatus mp_status =
440 optimize_and_pack_
441 ? global_mp_optimizers_[i]->ComputePackedCumuls(
442 next, &cumul_values, &break_start_end_values,
443 &resource_indices_per_group)
444 : global_mp_optimizers_[i]->ComputeCumuls(
445 next, &cumul_values, &break_start_end_values,
446 &resource_indices_per_group);
447 if (mp_status != DimensionSchedulingStatus::OPTIMAL) {
448 should_fail = true;
449 break;
450 }
451 } else {
452 DCHECK(status == DimensionSchedulingStatus::OPTIMAL);
453 }
454 // Concatenate cumul_values and break_start_end_values into cp_values,
455 // generate corresponding cp_variables vector.
456 std::vector<IntVar*> cp_variables = dimension->cumuls();
457 std::vector<int64_t> cp_values;
458 std::swap(cp_values, cumul_values);
459 if (dimension->HasBreakConstraints()) {
460 const int num_vehicles = model->vehicles();
461 for (int vehicle = 0; vehicle < num_vehicles; ++vehicle) {
462 for (IntervalVar* interval :
463 dimension->GetBreakIntervalsOfVehicle(vehicle)) {
464 cp_variables.push_back(interval->SafeStartExpr(0)->Var());
465 cp_variables.push_back(interval->SafeEndExpr(0)->Var());
466 }
467 }
468 cp_values.insert(cp_values.end(), break_start_end_values.begin(),
469 break_start_end_values.end());
470 }
471 for (int rg_index : model->GetDimensionResourceGroupIndices(dimension)) {
472 const std::vector<int>& resource_values =
473 resource_indices_per_group[rg_index];
474 DCHECK(!resource_values.empty());
475 cp_values.insert(cp_values.end(), resource_values.begin(),
476 resource_values.end());
477 const std::vector<IntVar*>& resource_vars =
478 model->ResourceVars(rg_index);
479 DCHECK_EQ(resource_vars.size(), resource_values.size());
480 cp_variables.insert(cp_variables.end(), resource_vars.begin(),
481 resource_vars.end());
482 }
483 // Value kint64min signals an unoptimized variable, set to min instead.
484 for (int j = 0; j < cp_values.size(); ++j) {
485 if (cp_values[j] == std::numeric_limits<int64_t>::min()) {
486 cp_values[j] = cp_variables[j]->Min();
487 }
488 }
489 if (!solver->SolveAndCommit(
490 MakeSetValuesFromTargets(solver, std::move(cp_variables),
491 std::move(cp_values)),
492 monitor_)) {
493 should_fail = true;
494 break;
495 }
496 }
497 if (should_fail) {
498 solver->Fail();
499 }
500 return nullptr;
501 }
502
503 private:
504 std::vector<GlobalDimensionCumulOptimizer*> global_optimizers_;
505 std::vector<GlobalDimensionCumulOptimizer*> global_mp_optimizers_;
506 SearchMonitor* const monitor_;
507 const bool optimize_and_pack_;
508 };
509
510 class SetCumulsFromResourceAssignmentCosts : public DecisionBuilder {
511 public:
SetCumulsFromResourceAssignmentCosts(LocalDimensionCumulOptimizer * optimizer,LocalDimensionCumulOptimizer * mp_optimizer,SearchMonitor * monitor)512 SetCumulsFromResourceAssignmentCosts(
513 LocalDimensionCumulOptimizer* optimizer,
514 LocalDimensionCumulOptimizer* mp_optimizer, SearchMonitor* monitor)
515 : model_(*optimizer->dimension()->model()),
516 dimension_(*optimizer->dimension()),
517 rg_index_(model_.GetDimensionResourceGroupIndex(&dimension_)),
518 resource_group_(*model_.GetResourceGroup(rg_index_)),
519 resource_assignment_optimizer_(&resource_group_, optimizer,
520 mp_optimizer),
521 monitor_(monitor) {}
522
Next(Solver * const solver)523 Decision* Next(Solver* const solver) override {
524 bool should_fail = false;
525 {
526 const int num_vehicles = model_.vehicles();
527 std::vector<std::vector<int64_t>> assignment_costs(num_vehicles);
528 std::vector<std::vector<std::vector<int64_t>>> cumul_values(num_vehicles);
529 std::vector<std::vector<std::vector<int64_t>>> break_values(num_vehicles);
530
531 const auto next = [&model = model_](int64_t n) {
532 return model.NextVar(n)->Value();
533 };
534 DCHECK(DimensionFixedTransitsEqualTransitEvaluators(dimension_));
535
536 for (int v : resource_group_.GetVehiclesRequiringAResource()) {
537 if (!resource_assignment_optimizer_.ComputeAssignmentCostsForVehicle(
538 v, next, /*optimize_vehicle_costs*/ true, &assignment_costs[v],
539 &cumul_values[v], &break_values[v])) {
540 should_fail = true;
541 break;
542 }
543 }
544
545 std::vector<int> resource_indices;
546 should_fail = should_fail ||
547 resource_assignment_optimizer_.ComputeBestAssignmentCost(
548 assignment_costs, assignment_costs,
549 [](int) { return true; }, &resource_indices) < 0;
550
551 if (!should_fail) {
552 DCHECK_EQ(resource_indices.size(), num_vehicles);
553 const int num_resources = resource_group_.Size();
554 for (int v : resource_group_.GetVehiclesRequiringAResource()) {
555 if (next(model_.Start(v)) == model_.End(v) &&
556 !model_.IsVehicleUsedWhenEmpty(v)) {
557 continue;
558 }
559 const int resource_index = resource_indices[v];
560 DCHECK_GE(resource_index, 0);
561 DCHECK_EQ(cumul_values[v].size(), num_resources);
562 DCHECK_EQ(break_values[v].size(), num_resources);
563 const std::vector<int64_t>& optimal_cumul_values =
564 cumul_values[v][resource_index];
565 const std::vector<int64_t>& optimal_break_values =
566 break_values[v][resource_index];
567 std::vector<IntVar*> cp_variables;
568 std::vector<int64_t> cp_values;
569 ConcatenateRouteCumulAndBreakVarAndValues(
570 dimension_, v, optimal_cumul_values, optimal_break_values,
571 &cp_variables, &cp_values);
572
573 const std::vector<IntVar*>& resource_vars =
574 model_.ResourceVars(rg_index_);
575 DCHECK_EQ(resource_vars.size(), resource_indices.size());
576 cp_variables.insert(cp_variables.end(), resource_vars.begin(),
577 resource_vars.end());
578 cp_values.insert(cp_values.end(), resource_indices.begin(),
579 resource_indices.end());
580 if (!solver->SolveAndCommit(
581 MakeSetValuesFromTargets(solver, std::move(cp_variables),
582 std::move(cp_values)),
583 monitor_)) {
584 should_fail = true;
585 break;
586 }
587 }
588 }
589 }
590 if (should_fail) {
591 solver->Fail();
592 }
593 return nullptr;
594 }
595
596 private:
597 const RoutingModel& model_;
598 const RoutingDimension& dimension_;
599 const int rg_index_;
600 const ResourceGroup& resource_group_;
601 ResourceAssignmentOptimizer resource_assignment_optimizer_;
602 SearchMonitor* const monitor_;
603 };
604
605 } // namespace
606
PackCumulsOfOptimizerDimensionsFromAssignment(const Assignment * original_assignment,absl::Duration duration_limit)607 const Assignment* RoutingModel::PackCumulsOfOptimizerDimensionsFromAssignment(
608 const Assignment* original_assignment, absl::Duration duration_limit) {
609 CHECK(closed_);
610 if (original_assignment == nullptr) return nullptr;
611 if (duration_limit <= absl::ZeroDuration()) return original_assignment;
612 if (global_dimension_optimizers_.empty() &&
613 local_dimension_optimizers_.empty()) {
614 DCHECK(local_dimension_mp_optimizers_.empty());
615 return original_assignment;
616 }
617 RegularLimit* const limit = GetOrCreateLimit();
618 limit->UpdateLimits(duration_limit, std::numeric_limits<int64_t>::max(),
619 std::numeric_limits<int64_t>::max(),
620 std::numeric_limits<int64_t>::max());
621
622 // Initialize the packed_assignment with the Next values in the
623 // original_assignment.
624 Assignment* packed_assignment = solver_->MakeAssignment();
625 packed_assignment->Add(Nexts());
626 // Also keep the Resource values for dimensions with a single resource group.
627 for (const RoutingDimension* const dimension : dimensions_) {
628 const std::vector<int>& resource_groups =
629 GetDimensionResourceGroupIndices(dimension);
630 if (resource_groups.size() == 1) {
631 DCHECK_NE(GetMutableLocalCumulOptimizer(*dimension), nullptr);
632 packed_assignment->Add(resource_vars_[resource_groups[0]]);
633 }
634 }
635 packed_assignment->CopyIntersection(original_assignment);
636
637 std::vector<DecisionBuilder*> decision_builders;
638 decision_builders.push_back(solver_->MakeRestoreAssignment(preassignment_));
639 decision_builders.push_back(
640 solver_->MakeRestoreAssignment(packed_assignment));
641 decision_builders.push_back(
642 solver_->RevAlloc(new SetCumulsFromLocalDimensionCosts(
643 &local_dimension_optimizers_, &local_dimension_mp_optimizers_,
644 [this](const RoutingDimension* dim) {
645 // Don't set cumuls of dimensions with a global optimizer.
646 return GetMutableGlobalCumulOptimizer(*dim) == nullptr;
647 },
648 GetOrCreateLargeNeighborhoodSearchLimit(),
649 /*optimize_and_pack=*/true)));
650 decision_builders.push_back(
651 solver_->RevAlloc(new SetCumulsFromGlobalDimensionCosts(
652 &global_dimension_optimizers_, &global_dimension_mp_optimizers_,
653 GetOrCreateLargeNeighborhoodSearchLimit(),
654 /*optimize_and_pack=*/true)));
655 decision_builders.push_back(
656 CreateFinalizerForMinimizedAndMaximizedVariables());
657
658 DecisionBuilder* restore_pack_and_finalize =
659 solver_->Compose(decision_builders);
660 solver_->Solve(restore_pack_and_finalize,
661 packed_dimensions_assignment_collector_, limit);
662
663 if (packed_dimensions_assignment_collector_->solution_count() != 1) {
664 LOG(ERROR) << "The given assignment is not valid for this model, or cannot "
665 "be packed.";
666 return nullptr;
667 }
668
669 packed_assignment->Copy(original_assignment);
670 packed_assignment->CopyIntersection(
671 packed_dimensions_assignment_collector_->solution(0));
672
673 return packed_assignment;
674 }
675
SetSweepArranger(SweepArranger * sweep_arranger)676 void RoutingModel::SetSweepArranger(SweepArranger* sweep_arranger) {
677 sweep_arranger_.reset(sweep_arranger);
678 }
679
sweep_arranger() const680 SweepArranger* RoutingModel::sweep_arranger() const {
681 return sweep_arranger_.get();
682 }
683
684 namespace {
685 // Constraint which ensures that var != values.
686 class DifferentFromValues : public Constraint {
687 public:
DifferentFromValues(Solver * solver,IntVar * var,std::vector<int64_t> values)688 DifferentFromValues(Solver* solver, IntVar* var, std::vector<int64_t> values)
689 : Constraint(solver), var_(var), values_(std::move(values)) {}
Post()690 void Post() override {}
InitialPropagate()691 void InitialPropagate() override { var_->RemoveValues(values_); }
DebugString() const692 std::string DebugString() const override { return "DifferentFromValues"; }
Accept(ModelVisitor * const visitor) const693 void Accept(ModelVisitor* const visitor) const override {
694 visitor->BeginVisitConstraint(RoutingModelVisitor::kRemoveValues, this);
695 visitor->VisitIntegerVariableArrayArgument(ModelVisitor::kVarsArgument,
696 {var_});
697 visitor->VisitIntegerArrayArgument(ModelVisitor::kValuesArgument, values_);
698 visitor->EndVisitConstraint(RoutingModelVisitor::kRemoveValues, this);
699 }
700
701 private:
702 IntVar* const var_;
703 const std::vector<int64_t> values_;
704 };
705
706 // Set of "light" constraints, well-suited for use within Local Search.
707 // These constraints are "checking" constraints, only triggered on WhenBound
708 // events. The provide very little (or no) domain filtering.
709 // TODO(user): Move to core constraintsolver library.
710
711 // Light one-dimension function-based element constraint ensuring:
712 // var == values(index).
713 // Doesn't perform bound reduction of the resulting variable until the index
714 // variable is bound.
715 // If deep_serialize returns false, the model visitor will not extract all
716 // possible values from the values function.
717 template <typename F>
718 class LightFunctionElementConstraint : public Constraint {
719 public:
LightFunctionElementConstraint(Solver * const solver,IntVar * const var,IntVar * const index,F values,std::function<bool ()> deep_serialize)720 LightFunctionElementConstraint(Solver* const solver, IntVar* const var,
721 IntVar* const index, F values,
722 std::function<bool()> deep_serialize)
723 : Constraint(solver),
724 var_(var),
725 index_(index),
726 values_(std::move(values)),
727 deep_serialize_(std::move(deep_serialize)) {}
~LightFunctionElementConstraint()728 ~LightFunctionElementConstraint() override {}
729
Post()730 void Post() override {
731 Demon* demon = MakeConstraintDemon0(
732 solver(), this, &LightFunctionElementConstraint::IndexBound,
733 "IndexBound");
734 index_->WhenBound(demon);
735 }
736
InitialPropagate()737 void InitialPropagate() override {
738 if (index_->Bound()) {
739 IndexBound();
740 }
741 }
742
DebugString() const743 std::string DebugString() const override {
744 return "LightFunctionElementConstraint";
745 }
746
Accept(ModelVisitor * const visitor) const747 void Accept(ModelVisitor* const visitor) const override {
748 visitor->BeginVisitConstraint(RoutingModelVisitor::kLightElement, this);
749 visitor->VisitIntegerExpressionArgument(ModelVisitor::kTargetArgument,
750 var_);
751 visitor->VisitIntegerExpressionArgument(ModelVisitor::kIndexArgument,
752 index_);
753 // Warning: This will expand all values into a vector.
754 if (deep_serialize_()) {
755 visitor->VisitInt64ToInt64Extension(values_, index_->Min(),
756 index_->Max());
757 }
758 visitor->EndVisitConstraint(RoutingModelVisitor::kLightElement, this);
759 }
760
761 private:
IndexBound()762 void IndexBound() { var_->SetValue(values_(index_->Min())); }
763
764 IntVar* const var_;
765 IntVar* const index_;
766 F values_;
767 std::function<bool()> deep_serialize_;
768 };
769
770 template <typename F>
MakeLightElement(Solver * const solver,IntVar * const var,IntVar * const index,F values,std::function<bool ()> deep_serialize)771 Constraint* MakeLightElement(Solver* const solver, IntVar* const var,
772 IntVar* const index, F values,
773 std::function<bool()> deep_serialize) {
774 return solver->RevAlloc(new LightFunctionElementConstraint<F>(
775 solver, var, index, std::move(values), std::move(deep_serialize)));
776 }
777
778 // Light two-dimension function-based element constraint ensuring:
779 // var == values(index1, index2).
780 // Doesn't perform bound reduction of the resulting variable until the index
781 // variables are bound.
782 // Ownership of the 'values' callback is taken by the constraint.
783 template <typename F>
784 class LightFunctionElement2Constraint : public Constraint {
785 public:
LightFunctionElement2Constraint(Solver * const solver,IntVar * const var,IntVar * const index1,IntVar * const index2,F values,std::function<bool ()> deep_serialize)786 LightFunctionElement2Constraint(Solver* const solver, IntVar* const var,
787 IntVar* const index1, IntVar* const index2,
788 F values,
789 std::function<bool()> deep_serialize)
790 : Constraint(solver),
791 var_(var),
792 index1_(index1),
793 index2_(index2),
794 values_(std::move(values)),
795 deep_serialize_(std::move(deep_serialize)) {}
~LightFunctionElement2Constraint()796 ~LightFunctionElement2Constraint() override {}
Post()797 void Post() override {
798 Demon* demon = MakeConstraintDemon0(
799 solver(), this, &LightFunctionElement2Constraint::IndexBound,
800 "IndexBound");
801 index1_->WhenBound(demon);
802 index2_->WhenBound(demon);
803 }
InitialPropagate()804 void InitialPropagate() override { IndexBound(); }
805
DebugString() const806 std::string DebugString() const override {
807 return "LightFunctionElement2Constraint";
808 }
809
Accept(ModelVisitor * const visitor) const810 void Accept(ModelVisitor* const visitor) const override {
811 visitor->BeginVisitConstraint(RoutingModelVisitor::kLightElement2, this);
812 visitor->VisitIntegerExpressionArgument(ModelVisitor::kTargetArgument,
813 var_);
814 visitor->VisitIntegerExpressionArgument(ModelVisitor::kIndexArgument,
815 index1_);
816 visitor->VisitIntegerExpressionArgument(ModelVisitor::kIndex2Argument,
817 index2_);
818 // Warning: This will expand all values into a vector.
819 const int64_t index1_min = index1_->Min();
820 const int64_t index1_max = index1_->Max();
821 visitor->VisitIntegerArgument(ModelVisitor::kMinArgument, index1_min);
822 visitor->VisitIntegerArgument(ModelVisitor::kMaxArgument, index1_max);
823 if (deep_serialize_()) {
824 for (int i = index1_min; i <= index1_max; ++i) {
825 visitor->VisitInt64ToInt64Extension(
826 [this, i](int64_t j) { return values_(i, j); }, index2_->Min(),
827 index2_->Max());
828 }
829 }
830 visitor->EndVisitConstraint(RoutingModelVisitor::kLightElement2, this);
831 }
832
833 private:
IndexBound()834 void IndexBound() {
835 if (index1_->Bound() && index2_->Bound()) {
836 var_->SetValue(values_(index1_->Min(), index2_->Min()));
837 }
838 }
839
840 IntVar* const var_;
841 IntVar* const index1_;
842 IntVar* const index2_;
843 Solver::IndexEvaluator2 values_;
844 std::function<bool()> deep_serialize_;
845 };
846
847 template <typename F>
MakeLightElement2(Solver * const solver,IntVar * const var,IntVar * const index1,IntVar * const index2,F values,std::function<bool ()> deep_serialize)848 Constraint* MakeLightElement2(Solver* const solver, IntVar* const var,
849 IntVar* const index1, IntVar* const index2,
850 F values, std::function<bool()> deep_serialize) {
851 return solver->RevAlloc(new LightFunctionElement2Constraint<F>(
852 solver, var, index1, index2, std::move(values),
853 std::move(deep_serialize)));
854 }
855
856 // Evaluators
857 template <class A, class B>
ReturnZero(A a,B b)858 static int64_t ReturnZero(A a, B b) {
859 return 0;
860 }
861
TransitCallbackPositive(const RoutingTransitCallback2 & callback,int size1,int size2)862 bool TransitCallbackPositive(const RoutingTransitCallback2& callback, int size1,
863 int size2) {
864 for (int i = 0; i < size1; i++) {
865 for (int j = 0; j < size2; j++) {
866 if (callback(i, j) < 0) {
867 return false;
868 }
869 }
870 }
871 return true;
872 }
873
874 } // namespace
875
876 // ----- Routing model -----
877
878 static const int kUnassigned = -1;
879 const int64_t RoutingModel::kNoPenalty = -1;
880
881 const RoutingModel::DisjunctionIndex RoutingModel::kNoDisjunction(-1);
882
883 const RoutingModel::DimensionIndex RoutingModel::kNoDimension(-1);
884
RoutingModel(const RoutingIndexManager & index_manager)885 RoutingModel::RoutingModel(const RoutingIndexManager& index_manager)
886 : RoutingModel(index_manager, DefaultRoutingModelParameters()) {}
887
RoutingModel(const RoutingIndexManager & index_manager,const RoutingModelParameters & parameters)888 RoutingModel::RoutingModel(const RoutingIndexManager& index_manager,
889 const RoutingModelParameters& parameters)
890 : nodes_(index_manager.num_nodes()),
891 vehicles_(index_manager.num_vehicles()),
892 max_active_vehicles_(vehicles_),
893 fixed_cost_of_vehicle_(vehicles_, 0),
894 cost_class_index_of_vehicle_(vehicles_, CostClassIndex(-1)),
895 linear_cost_factor_of_vehicle_(vehicles_, 0),
896 quadratic_cost_factor_of_vehicle_(vehicles_, 0),
897 vehicle_amortized_cost_factors_set_(false),
898 vehicle_used_when_empty_(vehicles_, false),
899 cost_classes_(),
900 costs_are_homogeneous_across_vehicles_(
901 parameters.reduce_vehicle_cost_model()),
902 cache_callbacks_(false),
903 vehicle_class_index_of_vehicle_(vehicles_, VehicleClassIndex(-1)),
904 vehicle_pickup_delivery_policy_(vehicles_, PICKUP_AND_DELIVERY_NO_ORDER),
905 has_hard_type_incompatibilities_(false),
906 has_temporal_type_incompatibilities_(false),
907 has_same_vehicle_type_requirements_(false),
908 has_temporal_type_requirements_(false),
909 num_visit_types_(0),
910 starts_(vehicles_),
911 ends_(vehicles_),
912 manager_(index_manager) {
913 // Initialize vehicle costs to the zero evaluator.
914 vehicle_to_transit_cost_.assign(
915 vehicles_, RegisterTransitCallback(ReturnZero<int64_t, int64_t>));
916 // Active caching after initializing vehicle_to_transit_cost_ to avoid
917 // uselessly caching ReturnZero.
918 cache_callbacks_ = (nodes_ <= parameters.max_callback_cache_size());
919
920 VLOG(1) << "Model parameters:\n" << parameters.DebugString();
921 ConstraintSolverParameters solver_parameters =
922 parameters.has_solver_parameters() ? parameters.solver_parameters()
923 : Solver::DefaultSolverParameters();
924 solver_ = absl::make_unique<Solver>("Routing", solver_parameters);
925 // TODO(user): Remove when removal of NodeIndex is complete.
926 start_end_count_ = index_manager.num_unique_depots();
927 Initialize();
928
929 const int64_t size = Size();
930 index_to_pickup_index_pairs_.resize(size);
931 index_to_delivery_index_pairs_.resize(size);
932 index_to_visit_type_.resize(index_manager.num_indices(), kUnassigned);
933 index_to_type_policy_.resize(index_manager.num_indices());
934
935 index_to_vehicle_.resize(index_manager.num_indices(), kUnassigned);
936 for (int v = 0; v < index_manager.num_vehicles(); ++v) {
937 starts_[v] = index_manager.GetStartIndex(v);
938 index_to_vehicle_[starts_[v]] = v;
939 ends_[v] = index_manager.GetEndIndex(v);
940 index_to_vehicle_[ends_[v]] = v;
941 }
942
943 const std::vector<RoutingIndexManager::NodeIndex>& index_to_node =
944 index_manager.GetIndexToNodeMap();
945 index_to_equivalence_class_.resize(index_manager.num_indices());
946 for (int i = 0; i < index_to_node.size(); ++i) {
947 index_to_equivalence_class_[i] = index_to_node[i].value();
948 }
949 allowed_vehicles_.resize(Size() + vehicles_);
950 }
951
Initialize()952 void RoutingModel::Initialize() {
953 const int size = Size();
954 // Next variables
955 solver_->MakeIntVarArray(size, 0, size + vehicles_ - 1, "Nexts", &nexts_);
956 solver_->AddConstraint(solver_->MakeAllDifferent(nexts_, false));
957 index_to_disjunctions_.resize(size + vehicles_);
958 // Vehicle variables. In case that node i is not active, vehicle_vars_[i] is
959 // bound to -1.
960 solver_->MakeIntVarArray(size + vehicles_, -1, vehicles_ - 1, "Vehicles",
961 &vehicle_vars_);
962 // Active variables
963 solver_->MakeBoolVarArray(size, "Active", &active_);
964 // Active vehicle variables
965 solver_->MakeBoolVarArray(vehicles_, "ActiveVehicle", &vehicle_active_);
966 // Variables representing vehicles contributing to cost.
967 solver_->MakeBoolVarArray(vehicles_, "VehicleCostsConsidered",
968 &vehicle_route_considered_);
969 // Is-bound-to-end variables.
970 solver_->MakeBoolVarArray(size + vehicles_, "IsBoundToEnd",
971 &is_bound_to_end_);
972 // Cost cache
973 cost_cache_.clear();
974 cost_cache_.resize(size + vehicles_, {kUnassigned, CostClassIndex(-1), 0});
975 preassignment_ = solver_->MakeAssignment();
976 }
977
~RoutingModel()978 RoutingModel::~RoutingModel() {
979 gtl::STLDeleteElements(&dimensions_);
980
981 // State dependent transit callbacks.
982 absl::flat_hash_set<RangeIntToIntFunction*> value_functions_delete;
983 absl::flat_hash_set<RangeMinMaxIndexFunction*> index_functions_delete;
984 for (const auto& cache_line : state_dependent_transit_evaluators_cache_) {
985 for (const auto& key_transit : *cache_line) {
986 value_functions_delete.insert(key_transit.second.transit);
987 index_functions_delete.insert(key_transit.second.transit_plus_identity);
988 }
989 }
990 gtl::STLDeleteElements(&value_functions_delete);
991 gtl::STLDeleteElements(&index_functions_delete);
992 }
993
994 namespace {
RegisterCallback(RoutingTransitCallback2 callback,bool is_positive,RoutingModel * model)995 int RegisterCallback(RoutingTransitCallback2 callback, bool is_positive,
996 RoutingModel* model) {
997 if (is_positive) {
998 return model->RegisterPositiveTransitCallback(std::move(callback));
999 }
1000 return model->RegisterTransitCallback(std::move(callback));
1001 }
1002
RegisterUnaryCallback(RoutingTransitCallback1 callback,bool is_positive,RoutingModel * model)1003 int RegisterUnaryCallback(RoutingTransitCallback1 callback, bool is_positive,
1004 RoutingModel* model) {
1005 if (is_positive) {
1006 return model->RegisterPositiveUnaryTransitCallback(std::move(callback));
1007 }
1008 return model->RegisterUnaryTransitCallback(std::move(callback));
1009 }
1010 } // namespace
1011
RegisterUnaryTransitVector(std::vector<int64_t> values)1012 int RoutingModel::RegisterUnaryTransitVector(std::vector<int64_t> values) {
1013 return RegisterUnaryCallback(
1014 [this, values = std::move(values)](int64_t i) {
1015 return values[manager_.IndexToNode(i).value()];
1016 },
1017 /*is_positive=*/
1018 std::all_of(std::cbegin(values), std::cend(values),
1019 [](int64_t transit) { return transit >= 0; }),
1020 this);
1021 }
1022
RegisterUnaryTransitCallback(TransitCallback1 callback)1023 int RoutingModel::RegisterUnaryTransitCallback(TransitCallback1 callback) {
1024 const int index = unary_transit_evaluators_.size();
1025 unary_transit_evaluators_.push_back(std::move(callback));
1026 return RegisterTransitCallback([this, index](int i, int j) {
1027 return unary_transit_evaluators_[index](i);
1028 });
1029 }
1030
RegisterTransitMatrix(std::vector<std::vector<int64_t>> values)1031 int RoutingModel::RegisterTransitMatrix(
1032 std::vector<std::vector<int64_t> /*needed_for_swig*/> values) {
1033 bool all_transits_positive = true;
1034 for (const std::vector<int64_t>& transit_values : values) {
1035 all_transits_positive =
1036 std::all_of(std::cbegin(transit_values), std::cend(transit_values),
1037 [](int64_t transit) { return transit >= 0; });
1038 if (!all_transits_positive) {
1039 break;
1040 }
1041 }
1042 return RegisterCallback(
1043 [this, values = std::move(values)](int64_t i, int64_t j) {
1044 return values[manager_.IndexToNode(i).value()]
1045 [manager_.IndexToNode(j).value()];
1046 },
1047 all_transits_positive, this);
1048 }
1049
RegisterPositiveUnaryTransitCallback(TransitCallback1 callback)1050 int RoutingModel::RegisterPositiveUnaryTransitCallback(
1051 TransitCallback1 callback) {
1052 is_transit_evaluator_positive_.push_back(true);
1053 DCHECK(TransitCallbackPositive(
1054 [&callback](int i, int) { return callback(i); }, Size() + vehicles(), 1));
1055 return RegisterUnaryTransitCallback(std::move(callback));
1056 }
1057
RegisterTransitCallback(TransitCallback2 callback)1058 int RoutingModel::RegisterTransitCallback(TransitCallback2 callback) {
1059 if (cache_callbacks_) {
1060 const int size = Size() + vehicles();
1061 std::vector<int64_t> cache(size * size, 0);
1062 for (int i = 0; i < size; ++i) {
1063 for (int j = 0; j < size; ++j) {
1064 cache[i * size + j] = callback(i, j);
1065 }
1066 }
1067 transit_evaluators_.push_back(
1068 [cache, size](int64_t i, int64_t j) { return cache[i * size + j]; });
1069 } else {
1070 transit_evaluators_.push_back(std::move(callback));
1071 }
1072 if (transit_evaluators_.size() != unary_transit_evaluators_.size()) {
1073 DCHECK_EQ(transit_evaluators_.size(), unary_transit_evaluators_.size() + 1);
1074 unary_transit_evaluators_.push_back(nullptr);
1075 }
1076 if (transit_evaluators_.size() != is_transit_evaluator_positive_.size()) {
1077 DCHECK_EQ(transit_evaluators_.size(),
1078 is_transit_evaluator_positive_.size() + 1);
1079 is_transit_evaluator_positive_.push_back(false);
1080 }
1081 return transit_evaluators_.size() - 1;
1082 }
1083
RegisterPositiveTransitCallback(TransitCallback2 callback)1084 int RoutingModel::RegisterPositiveTransitCallback(TransitCallback2 callback) {
1085 is_transit_evaluator_positive_.push_back(true);
1086 DCHECK(TransitCallbackPositive(callback, Size() + vehicles(),
1087 Size() + vehicles()));
1088 return RegisterTransitCallback(std::move(callback));
1089 }
1090
RegisterStateDependentTransitCallback(VariableIndexEvaluator2 callback)1091 int RoutingModel::RegisterStateDependentTransitCallback(
1092 VariableIndexEvaluator2 callback) {
1093 state_dependent_transit_evaluators_cache_.push_back(
1094 absl::make_unique<StateDependentTransitCallbackCache>());
1095 StateDependentTransitCallbackCache* const cache =
1096 state_dependent_transit_evaluators_cache_.back().get();
1097 state_dependent_transit_evaluators_.push_back(
1098 [cache, callback](int64_t i, int64_t j) {
1099 StateDependentTransit value;
1100 if (gtl::FindCopy(*cache, CacheKey(i, j), &value)) return value;
1101 value = callback(i, j);
1102 cache->insert({CacheKey(i, j), value});
1103 return value;
1104 });
1105 return state_dependent_transit_evaluators_.size() - 1;
1106 }
1107
AddNoCycleConstraintInternal()1108 void RoutingModel::AddNoCycleConstraintInternal() {
1109 if (no_cycle_constraint_ == nullptr) {
1110 no_cycle_constraint_ = solver_->MakeNoCycle(nexts_, active_);
1111 solver_->AddConstraint(no_cycle_constraint_);
1112 }
1113 }
1114
AddDimension(int evaluator_index,int64_t slack_max,int64_t capacity,bool fix_start_cumul_to_zero,const std::string & name)1115 bool RoutingModel::AddDimension(int evaluator_index, int64_t slack_max,
1116 int64_t capacity, bool fix_start_cumul_to_zero,
1117 const std::string& name) {
1118 const std::vector<int> evaluator_indices(vehicles_, evaluator_index);
1119 std::vector<int64_t> capacities(vehicles_, capacity);
1120 return AddDimensionWithCapacityInternal(evaluator_indices, slack_max,
1121 std::move(capacities),
1122 fix_start_cumul_to_zero, name);
1123 }
1124
AddDimensionWithVehicleTransits(const std::vector<int> & evaluator_indices,int64_t slack_max,int64_t capacity,bool fix_start_cumul_to_zero,const std::string & name)1125 bool RoutingModel::AddDimensionWithVehicleTransits(
1126 const std::vector<int>& evaluator_indices, int64_t slack_max,
1127 int64_t capacity, bool fix_start_cumul_to_zero, const std::string& name) {
1128 std::vector<int64_t> capacities(vehicles_, capacity);
1129 return AddDimensionWithCapacityInternal(evaluator_indices, slack_max,
1130 std::move(capacities),
1131 fix_start_cumul_to_zero, name);
1132 }
1133
AddDimensionWithVehicleCapacity(int evaluator_index,int64_t slack_max,std::vector<int64_t> vehicle_capacities,bool fix_start_cumul_to_zero,const std::string & name)1134 bool RoutingModel::AddDimensionWithVehicleCapacity(
1135 int evaluator_index, int64_t slack_max,
1136 std::vector<int64_t> vehicle_capacities, bool fix_start_cumul_to_zero,
1137 const std::string& name) {
1138 const std::vector<int> evaluator_indices(vehicles_, evaluator_index);
1139 return AddDimensionWithCapacityInternal(evaluator_indices, slack_max,
1140 std::move(vehicle_capacities),
1141 fix_start_cumul_to_zero, name);
1142 }
1143
AddDimensionWithVehicleTransitAndCapacity(const std::vector<int> & evaluator_indices,int64_t slack_max,std::vector<int64_t> vehicle_capacities,bool fix_start_cumul_to_zero,const std::string & name)1144 bool RoutingModel::AddDimensionWithVehicleTransitAndCapacity(
1145 const std::vector<int>& evaluator_indices, int64_t slack_max,
1146 std::vector<int64_t> vehicle_capacities, bool fix_start_cumul_to_zero,
1147 const std::string& name) {
1148 return AddDimensionWithCapacityInternal(evaluator_indices, slack_max,
1149 std::move(vehicle_capacities),
1150 fix_start_cumul_to_zero, name);
1151 }
1152
AddDimensionWithCapacityInternal(const std::vector<int> & evaluator_indices,int64_t slack_max,std::vector<int64_t> vehicle_capacities,bool fix_start_cumul_to_zero,const std::string & name)1153 bool RoutingModel::AddDimensionWithCapacityInternal(
1154 const std::vector<int>& evaluator_indices, int64_t slack_max,
1155 std::vector<int64_t> vehicle_capacities, bool fix_start_cumul_to_zero,
1156 const std::string& name) {
1157 CHECK_EQ(vehicles_, vehicle_capacities.size());
1158 return InitializeDimensionInternal(
1159 evaluator_indices, std::vector<int>(), slack_max, fix_start_cumul_to_zero,
1160 new RoutingDimension(this, std::move(vehicle_capacities), name, nullptr));
1161 }
1162
InitializeDimensionInternal(const std::vector<int> & evaluator_indices,const std::vector<int> & state_dependent_evaluator_indices,int64_t slack_max,bool fix_start_cumul_to_zero,RoutingDimension * dimension)1163 bool RoutingModel::InitializeDimensionInternal(
1164 const std::vector<int>& evaluator_indices,
1165 const std::vector<int>& state_dependent_evaluator_indices,
1166 int64_t slack_max, bool fix_start_cumul_to_zero,
1167 RoutingDimension* dimension) {
1168 CHECK(dimension != nullptr);
1169 CHECK_EQ(vehicles_, evaluator_indices.size());
1170 CHECK((dimension->base_dimension_ == nullptr &&
1171 state_dependent_evaluator_indices.empty()) ||
1172 vehicles_ == state_dependent_evaluator_indices.size());
1173 if (!HasDimension(dimension->name())) {
1174 const DimensionIndex dimension_index(dimensions_.size());
1175 dimension_name_to_index_[dimension->name()] = dimension_index;
1176 dimensions_.push_back(dimension);
1177 dimension->Initialize(evaluator_indices, state_dependent_evaluator_indices,
1178 slack_max);
1179 solver_->AddConstraint(solver_->MakeDelayedPathCumul(
1180 nexts_, active_, dimension->cumuls(), dimension->transits()));
1181 if (fix_start_cumul_to_zero) {
1182 for (int i = 0; i < vehicles_; ++i) {
1183 IntVar* const start_cumul = dimension->CumulVar(Start(i));
1184 CHECK_EQ(0, start_cumul->Min());
1185 start_cumul->SetValue(0);
1186 }
1187 }
1188 return true;
1189 }
1190 delete dimension;
1191 return false;
1192 }
1193
AddConstantDimensionWithSlack(int64_t value,int64_t capacity,int64_t slack_max,bool fix_start_cumul_to_zero,const std::string & dimension_name)1194 std::pair<int, bool> RoutingModel::AddConstantDimensionWithSlack(
1195 int64_t value, int64_t capacity, int64_t slack_max,
1196 bool fix_start_cumul_to_zero, const std::string& dimension_name) {
1197 const int evaluator_index =
1198 RegisterUnaryCallback([value](int64_t) { return value; },
1199 /*is_positive=*/value >= 0, this);
1200 return std::make_pair(evaluator_index,
1201 AddDimension(evaluator_index, slack_max, capacity,
1202 fix_start_cumul_to_zero, dimension_name));
1203 }
1204
AddVectorDimension(std::vector<int64_t> values,int64_t capacity,bool fix_start_cumul_to_zero,const std::string & dimension_name)1205 std::pair<int, bool> RoutingModel::AddVectorDimension(
1206 std::vector<int64_t> values, int64_t capacity, bool fix_start_cumul_to_zero,
1207 const std::string& dimension_name) {
1208 const int evaluator_index = RegisterUnaryTransitVector(std::move(values));
1209 return std::make_pair(evaluator_index,
1210 AddDimension(evaluator_index, 0, capacity,
1211 fix_start_cumul_to_zero, dimension_name));
1212 }
1213
AddMatrixDimension(std::vector<std::vector<int64_t>> values,int64_t capacity,bool fix_start_cumul_to_zero,const std::string & dimension_name)1214 std::pair<int, bool> RoutingModel::AddMatrixDimension(
1215 std::vector<std::vector<int64_t>> values, int64_t capacity,
1216 bool fix_start_cumul_to_zero, const std::string& dimension_name) {
1217 const int evaluator_index = RegisterTransitMatrix(std::move(values));
1218 return std::make_pair(evaluator_index,
1219 AddDimension(evaluator_index, 0, capacity,
1220 fix_start_cumul_to_zero, dimension_name));
1221 }
1222
1223 namespace {
1224 // RangeMakeElementExpr is an IntExpr that corresponds to a
1225 // RangeIntToIntFunction indexed by an IntVar.
1226 // Do not create this class dicretly, but rather use MakeRangeMakeElementExpr.
1227 class RangeMakeElementExpr : public BaseIntExpr {
1228 public:
RangeMakeElementExpr(const RangeIntToIntFunction * callback,IntVar * index,Solver * s)1229 RangeMakeElementExpr(const RangeIntToIntFunction* callback, IntVar* index,
1230 Solver* s)
1231 : BaseIntExpr(s), callback_(ABSL_DIE_IF_NULL(callback)), index_(index) {
1232 CHECK(callback_ != nullptr);
1233 CHECK(index != nullptr);
1234 }
1235
Min() const1236 int64_t Min() const override {
1237 // Converting [index_->Min(), index_->Max()] to [idx_min, idx_max).
1238 const int idx_min = index_->Min();
1239 const int idx_max = index_->Max() + 1;
1240 return (idx_min < idx_max) ? callback_->RangeMin(idx_min, idx_max)
1241 : std::numeric_limits<int64_t>::max();
1242 }
SetMin(int64_t new_min)1243 void SetMin(int64_t new_min) override {
1244 const int64_t old_min = Min();
1245 const int64_t old_max = Max();
1246 if (old_min < new_min && new_min <= old_max) {
1247 const int64_t old_idx_min = index_->Min();
1248 const int64_t old_idx_max = index_->Max() + 1;
1249 if (old_idx_min < old_idx_max) {
1250 const int64_t new_idx_min = callback_->RangeFirstInsideInterval(
1251 old_idx_min, old_idx_max, new_min, old_max + 1);
1252 index_->SetMin(new_idx_min);
1253 if (new_idx_min < old_idx_max) {
1254 const int64_t new_idx_max = callback_->RangeLastInsideInterval(
1255 new_idx_min, old_idx_max, new_min, old_max + 1);
1256 index_->SetMax(new_idx_max);
1257 }
1258 }
1259 }
1260 }
Max() const1261 int64_t Max() const override {
1262 // Converting [index_->Min(), index_->Max()] to [idx_min, idx_max).
1263 const int idx_min = index_->Min();
1264 const int idx_max = index_->Max() + 1;
1265 return (idx_min < idx_max) ? callback_->RangeMax(idx_min, idx_max)
1266 : std::numeric_limits<int64_t>::min();
1267 }
SetMax(int64_t new_max)1268 void SetMax(int64_t new_max) override {
1269 const int64_t old_min = Min();
1270 const int64_t old_max = Max();
1271 if (old_min <= new_max && new_max < old_max) {
1272 const int64_t old_idx_min = index_->Min();
1273 const int64_t old_idx_max = index_->Max() + 1;
1274 if (old_idx_min < old_idx_max) {
1275 const int64_t new_idx_min = callback_->RangeFirstInsideInterval(
1276 old_idx_min, old_idx_max, old_min, new_max + 1);
1277 index_->SetMin(new_idx_min);
1278 if (new_idx_min < old_idx_max) {
1279 const int64_t new_idx_max = callback_->RangeLastInsideInterval(
1280 new_idx_min, old_idx_max, old_min, new_max + 1);
1281 index_->SetMax(new_idx_max);
1282 }
1283 }
1284 }
1285 }
WhenRange(Demon * d)1286 void WhenRange(Demon* d) override { index_->WhenRange(d); }
1287
1288 private:
1289 const RangeIntToIntFunction* const callback_;
1290 IntVar* const index_;
1291 };
1292
MakeRangeMakeElementExpr(const RangeIntToIntFunction * callback,IntVar * index,Solver * s)1293 IntExpr* MakeRangeMakeElementExpr(const RangeIntToIntFunction* callback,
1294 IntVar* index, Solver* s) {
1295 return s->RegisterIntExpr(
1296 s->RevAlloc(new RangeMakeElementExpr(callback, index, s)));
1297 }
1298 } // namespace
1299
AddDimensionDependentDimensionWithVehicleCapacity(const std::vector<int> & dependent_transits,const RoutingDimension * base_dimension,int64_t slack_max,std::vector<int64_t> vehicle_capacities,bool fix_start_cumul_to_zero,const std::string & name)1300 bool RoutingModel::AddDimensionDependentDimensionWithVehicleCapacity(
1301 const std::vector<int>& dependent_transits,
1302 const RoutingDimension* base_dimension, int64_t slack_max,
1303 std::vector<int64_t> vehicle_capacities, bool fix_start_cumul_to_zero,
1304 const std::string& name) {
1305 const std::vector<int> pure_transits(vehicles_, /*zero_evaluator*/ 0);
1306 return AddDimensionDependentDimensionWithVehicleCapacity(
1307 pure_transits, dependent_transits, base_dimension, slack_max,
1308 std::move(vehicle_capacities), fix_start_cumul_to_zero, name);
1309 }
1310
AddDimensionDependentDimensionWithVehicleCapacity(int transit,const RoutingDimension * dimension,int64_t slack_max,int64_t vehicle_capacity,bool fix_start_cumul_to_zero,const std::string & name)1311 bool RoutingModel::AddDimensionDependentDimensionWithVehicleCapacity(
1312 int transit, const RoutingDimension* dimension, int64_t slack_max,
1313 int64_t vehicle_capacity, bool fix_start_cumul_to_zero,
1314 const std::string& name) {
1315 return AddDimensionDependentDimensionWithVehicleCapacity(
1316 /*zero_evaluator*/ 0, transit, dimension, slack_max, vehicle_capacity,
1317 fix_start_cumul_to_zero, name);
1318 }
1319
AddDimensionDependentDimensionWithVehicleCapacityInternal(const std::vector<int> & pure_transits,const std::vector<int> & dependent_transits,const RoutingDimension * base_dimension,int64_t slack_max,std::vector<int64_t> vehicle_capacities,bool fix_start_cumul_to_zero,const std::string & name)1320 bool RoutingModel::AddDimensionDependentDimensionWithVehicleCapacityInternal(
1321 const std::vector<int>& pure_transits,
1322 const std::vector<int>& dependent_transits,
1323 const RoutingDimension* base_dimension, int64_t slack_max,
1324 std::vector<int64_t> vehicle_capacities, bool fix_start_cumul_to_zero,
1325 const std::string& name) {
1326 CHECK_EQ(vehicles_, vehicle_capacities.size());
1327 RoutingDimension* new_dimension = nullptr;
1328 if (base_dimension == nullptr) {
1329 new_dimension = new RoutingDimension(this, std::move(vehicle_capacities),
1330 name, RoutingDimension::SelfBased());
1331 } else {
1332 new_dimension = new RoutingDimension(this, std::move(vehicle_capacities),
1333 name, base_dimension);
1334 }
1335 return InitializeDimensionInternal(pure_transits, dependent_transits,
1336 slack_max, fix_start_cumul_to_zero,
1337 new_dimension);
1338 }
1339
AddDimensionDependentDimensionWithVehicleCapacity(int pure_transit,int dependent_transit,const RoutingDimension * base_dimension,int64_t slack_max,int64_t vehicle_capacity,bool fix_start_cumul_to_zero,const std::string & name)1340 bool RoutingModel::AddDimensionDependentDimensionWithVehicleCapacity(
1341 int pure_transit, int dependent_transit,
1342 const RoutingDimension* base_dimension, int64_t slack_max,
1343 int64_t vehicle_capacity, bool fix_start_cumul_to_zero,
1344 const std::string& name) {
1345 std::vector<int> pure_transits(vehicles_, pure_transit);
1346 std::vector<int> dependent_transits(vehicles_, dependent_transit);
1347 std::vector<int64_t> vehicle_capacities(vehicles_, vehicle_capacity);
1348 return AddDimensionDependentDimensionWithVehicleCapacityInternal(
1349 pure_transits, dependent_transits, base_dimension, slack_max,
1350 std::move(vehicle_capacities), fix_start_cumul_to_zero, name);
1351 }
1352
MakeStateDependentTransit(const std::function<int64_t (int64_t)> & f,int64_t domain_start,int64_t domain_end)1353 RoutingModel::StateDependentTransit RoutingModel::MakeStateDependentTransit(
1354 const std::function<int64_t(int64_t)>& f, int64_t domain_start,
1355 int64_t domain_end) {
1356 const std::function<int64_t(int64_t)> g = [&f](int64_t x) {
1357 return f(x) + x;
1358 };
1359 // The next line is safe, because MakeCachedIntToIntFunction does not count
1360 // on keeping the closure of its first argument alive.
1361 return {MakeCachedIntToIntFunction(f, domain_start, domain_end),
1362 MakeCachedRangeMinMaxIndexFunction(g, domain_start, domain_end)};
1363 }
1364
GetAllDimensionNames() const1365 std::vector<std::string> RoutingModel::GetAllDimensionNames() const {
1366 std::vector<std::string> dimension_names;
1367 for (const auto& dimension_name_index : dimension_name_to_index_) {
1368 dimension_names.push_back(dimension_name_index.first);
1369 }
1370 std::sort(dimension_names.begin(), dimension_names.end());
1371 return dimension_names;
1372 }
1373
GetMutableGlobalCumulOptimizer(const RoutingDimension & dimension) const1374 GlobalDimensionCumulOptimizer* RoutingModel::GetMutableGlobalCumulOptimizer(
1375 const RoutingDimension& dimension) const {
1376 const DimensionIndex dim_index = GetDimensionIndex(dimension.name());
1377 if (dim_index < 0 || dim_index >= global_optimizer_index_.size() ||
1378 global_optimizer_index_[dim_index] < 0) {
1379 return nullptr;
1380 }
1381 const int optimizer_index = global_optimizer_index_[dim_index];
1382 DCHECK_LT(optimizer_index, global_dimension_optimizers_.size());
1383 return global_dimension_optimizers_[optimizer_index].get();
1384 }
1385
GetMutableGlobalCumulMPOptimizer(const RoutingDimension & dimension) const1386 GlobalDimensionCumulOptimizer* RoutingModel::GetMutableGlobalCumulMPOptimizer(
1387 const RoutingDimension& dimension) const {
1388 const DimensionIndex dim_index = GetDimensionIndex(dimension.name());
1389 if (dim_index < 0 || dim_index >= global_optimizer_index_.size() ||
1390 global_optimizer_index_[dim_index] < 0) {
1391 return nullptr;
1392 }
1393 const int optimizer_index = global_optimizer_index_[dim_index];
1394 DCHECK_LT(optimizer_index, global_dimension_mp_optimizers_.size());
1395 return global_dimension_mp_optimizers_[optimizer_index].get();
1396 }
1397
GetMutableLocalCumulOptimizer(const RoutingDimension & dimension) const1398 LocalDimensionCumulOptimizer* RoutingModel::GetMutableLocalCumulOptimizer(
1399 const RoutingDimension& dimension) const {
1400 const DimensionIndex dim_index = GetDimensionIndex(dimension.name());
1401 if (dim_index < 0 || dim_index >= local_optimizer_index_.size() ||
1402 local_optimizer_index_[dim_index] < 0) {
1403 return nullptr;
1404 }
1405 const int optimizer_index = local_optimizer_index_[dim_index];
1406 DCHECK_LT(optimizer_index, local_dimension_optimizers_.size());
1407 return local_dimension_optimizers_[optimizer_index].get();
1408 }
1409
GetMutableLocalCumulMPOptimizer(const RoutingDimension & dimension) const1410 LocalDimensionCumulOptimizer* RoutingModel::GetMutableLocalCumulMPOptimizer(
1411 const RoutingDimension& dimension) const {
1412 const DimensionIndex dim_index = GetDimensionIndex(dimension.name());
1413 if (dim_index < 0 || dim_index >= local_optimizer_index_.size() ||
1414 local_optimizer_index_[dim_index] < 0) {
1415 return nullptr;
1416 }
1417 const int optimizer_index = local_optimizer_index_[dim_index];
1418 DCHECK_LT(optimizer_index, local_dimension_mp_optimizers_.size());
1419 return local_dimension_mp_optimizers_[optimizer_index].get();
1420 }
1421
HasDimension(const std::string & dimension_name) const1422 bool RoutingModel::HasDimension(const std::string& dimension_name) const {
1423 return gtl::ContainsKey(dimension_name_to_index_, dimension_name);
1424 }
1425
GetDimensionIndex(const std::string & dimension_name) const1426 RoutingModel::DimensionIndex RoutingModel::GetDimensionIndex(
1427 const std::string& dimension_name) const {
1428 return gtl::FindWithDefault(dimension_name_to_index_, dimension_name,
1429 kNoDimension);
1430 }
1431
GetDimensionOrDie(const std::string & dimension_name) const1432 const RoutingDimension& RoutingModel::GetDimensionOrDie(
1433 const std::string& dimension_name) const {
1434 return *dimensions_[gtl::FindOrDie(dimension_name_to_index_, dimension_name)];
1435 }
1436
GetMutableDimension(const std::string & dimension_name) const1437 RoutingDimension* RoutingModel::GetMutableDimension(
1438 const std::string& dimension_name) const {
1439 const DimensionIndex index = GetDimensionIndex(dimension_name);
1440 if (index != kNoDimension) {
1441 return dimensions_[index];
1442 }
1443 return nullptr;
1444 }
1445
1446 // ResourceGroup
Attributes()1447 ResourceGroup::Attributes::Attributes()
1448 : start_domain_(Domain::AllValues()), end_domain_(Domain::AllValues()) {
1449 /// The default attributes have unconstrained start/end domains.
1450 }
1451
Attributes(Domain start_domain,Domain end_domain)1452 RoutingModel::ResourceGroup::Attributes::Attributes(Domain start_domain,
1453 Domain end_domain)
1454 : start_domain_(std::move(start_domain)),
1455 end_domain_(std::move(end_domain)) {}
1456
1457 const ResourceGroup::Attributes&
GetDimensionAttributes(const RoutingDimension * dimension) const1458 ResourceGroup::Resource::GetDimensionAttributes(
1459 const RoutingDimension* dimension) const {
1460 DimensionIndex dimension_index = model_->GetDimensionIndex(dimension->name());
1461 DCHECK_NE(dimension_index, kNoDimension);
1462 return gtl::FindWithDefault(dimension_attributes_, dimension_index,
1463 GetDefaultAttributes());
1464 }
1465
SetDimensionAttributes(Attributes attributes,const RoutingDimension * dimension)1466 void ResourceGroup::Resource::SetDimensionAttributes(
1467 Attributes attributes, const RoutingDimension* dimension) {
1468 DCHECK(dimension_attributes_.empty())
1469 << "As of 2021/07, each resource can only constrain a single dimension.";
1470
1471 const DimensionIndex dimension_index =
1472 model_->GetDimensionIndex(dimension->name());
1473 DCHECK_NE(dimension_index, kNoDimension);
1474 DCHECK(!dimension_attributes_.contains(dimension_index));
1475 dimension_attributes_[dimension_index] = std::move(attributes);
1476 }
1477
GetDefaultAttributes() const1478 const ResourceGroup::Attributes& ResourceGroup::Resource::GetDefaultAttributes()
1479 const {
1480 static const Attributes* const kAttributes = new Attributes();
1481 return *kAttributes;
1482 }
1483
AddResourceGroup()1484 int RoutingModel::AddResourceGroup() {
1485 resource_groups_.push_back(absl::make_unique<ResourceGroup>(this));
1486 return resource_groups_.size() - 1;
1487 }
1488
AddResource(Attributes attributes,const RoutingDimension * dimension)1489 int RoutingModel::ResourceGroup::AddResource(
1490 Attributes attributes, const RoutingDimension* dimension) {
1491 resources_.push_back(Resource(model_));
1492 resources_.back().SetDimensionAttributes(std::move(attributes), dimension);
1493
1494 const DimensionIndex dimension_index =
1495 model_->GetDimensionIndex(dimension->name());
1496 DCHECK_NE(dimension_index, kNoDimension);
1497 affected_dimension_indices_.insert(dimension_index);
1498
1499 DCHECK_EQ(affected_dimension_indices_.size(), 1)
1500 << "As of 2021/07, each ResourceGroup can only affect a single "
1501 "RoutingDimension at a time.";
1502
1503 return resources_.size() - 1;
1504 }
1505
NotifyVehicleRequiresAResource(int vehicle)1506 void RoutingModel::ResourceGroup::NotifyVehicleRequiresAResource(int vehicle) {
1507 DCHECK_LT(vehicle, vehicle_requires_resource_.size());
1508 if (vehicle_requires_resource_[vehicle]) return;
1509 vehicle_requires_resource_[vehicle] = true;
1510 vehicles_requiring_resource_.push_back(vehicle);
1511 }
1512
GetDimensionResourceGroupIndices(const RoutingDimension * dimension) const1513 const std::vector<int>& RoutingModel::GetDimensionResourceGroupIndices(
1514 const RoutingDimension* dimension) const {
1515 DCHECK(closed_);
1516 const DimensionIndex dim = GetDimensionIndex(dimension->name());
1517 DCHECK_NE(dim, kNoDimension);
1518 return dimension_resource_group_indices_[dim];
1519 }
1520
SetArcCostEvaluatorOfAllVehicles(int evaluator_index)1521 void RoutingModel::SetArcCostEvaluatorOfAllVehicles(int evaluator_index) {
1522 CHECK_LT(0, vehicles_);
1523 for (int i = 0; i < vehicles_; ++i) {
1524 SetArcCostEvaluatorOfVehicle(evaluator_index, i);
1525 }
1526 }
1527
SetArcCostEvaluatorOfVehicle(int evaluator_index,int vehicle)1528 void RoutingModel::SetArcCostEvaluatorOfVehicle(int evaluator_index,
1529 int vehicle) {
1530 CHECK_LT(vehicle, vehicles_);
1531 CHECK_LT(evaluator_index, transit_evaluators_.size());
1532 vehicle_to_transit_cost_[vehicle] = evaluator_index;
1533 }
1534
SetFixedCostOfAllVehicles(int64_t cost)1535 void RoutingModel::SetFixedCostOfAllVehicles(int64_t cost) {
1536 for (int i = 0; i < vehicles_; ++i) {
1537 SetFixedCostOfVehicle(cost, i);
1538 }
1539 }
1540
GetFixedCostOfVehicle(int vehicle) const1541 int64_t RoutingModel::GetFixedCostOfVehicle(int vehicle) const {
1542 CHECK_LT(vehicle, vehicles_);
1543 return fixed_cost_of_vehicle_[vehicle];
1544 }
1545
SetFixedCostOfVehicle(int64_t cost,int vehicle)1546 void RoutingModel::SetFixedCostOfVehicle(int64_t cost, int vehicle) {
1547 CHECK_LT(vehicle, vehicles_);
1548 DCHECK_GE(cost, 0);
1549 fixed_cost_of_vehicle_[vehicle] = cost;
1550 }
1551
SetAmortizedCostFactorsOfAllVehicles(int64_t linear_cost_factor,int64_t quadratic_cost_factor)1552 void RoutingModel::SetAmortizedCostFactorsOfAllVehicles(
1553 int64_t linear_cost_factor, int64_t quadratic_cost_factor) {
1554 for (int v = 0; v < vehicles_; v++) {
1555 SetAmortizedCostFactorsOfVehicle(linear_cost_factor, quadratic_cost_factor,
1556 v);
1557 }
1558 }
1559
SetAmortizedCostFactorsOfVehicle(int64_t linear_cost_factor,int64_t quadratic_cost_factor,int vehicle)1560 void RoutingModel::SetAmortizedCostFactorsOfVehicle(
1561 int64_t linear_cost_factor, int64_t quadratic_cost_factor, int vehicle) {
1562 CHECK_LT(vehicle, vehicles_);
1563 DCHECK_GE(linear_cost_factor, 0);
1564 DCHECK_GE(quadratic_cost_factor, 0);
1565 if (linear_cost_factor + quadratic_cost_factor > 0) {
1566 vehicle_amortized_cost_factors_set_ = true;
1567 }
1568 linear_cost_factor_of_vehicle_[vehicle] = linear_cost_factor;
1569 quadratic_cost_factor_of_vehicle_[vehicle] = quadratic_cost_factor;
1570 }
1571
1572 namespace {
1573 // Some C++ versions used in the open-source export don't support comparison
1574 // functors for STL containers; so we need a comparator class instead.
1575 struct CostClassComparator {
operator ()operations_research::__anon1b0943261711::CostClassComparator1576 bool operator()(const RoutingModel::CostClass& a,
1577 const RoutingModel::CostClass& b) const {
1578 return RoutingModel::CostClass::LessThan(a, b);
1579 }
1580 };
1581
1582 struct VehicleClassComparator {
operator ()operations_research::__anon1b0943261711::VehicleClassComparator1583 bool operator()(const RoutingModel::VehicleClass& a,
1584 const RoutingModel::VehicleClass& b) const {
1585 return RoutingModel::VehicleClass::LessThan(a, b);
1586 }
1587 };
1588 } // namespace
1589
1590 // static
1591 const RoutingModel::CostClassIndex RoutingModel::kCostClassIndexOfZeroCost =
1592 CostClassIndex(0);
1593
ComputeCostClasses(const RoutingSearchParameters & parameters)1594 void RoutingModel::ComputeCostClasses(
1595 const RoutingSearchParameters& parameters) {
1596 // Create and reduce the cost classes.
1597 cost_classes_.reserve(vehicles_);
1598 cost_classes_.clear();
1599 cost_class_index_of_vehicle_.assign(vehicles_, CostClassIndex(-1));
1600 std::map<CostClass, CostClassIndex, CostClassComparator> cost_class_map;
1601
1602 // Pre-insert the built-in cost class 'zero cost' with index 0.
1603 const CostClass zero_cost_class(0);
1604 cost_classes_.push_back(zero_cost_class);
1605 DCHECK_EQ(cost_classes_[kCostClassIndexOfZeroCost].evaluator_index, 0);
1606 cost_class_map[zero_cost_class] = kCostClassIndexOfZeroCost;
1607
1608 // Determine the canonicalized cost class for each vehicle, and insert it as
1609 // a new cost class if it doesn't exist already. Building cached evaluators
1610 // on the way.
1611 has_vehicle_with_zero_cost_class_ = false;
1612 for (int vehicle = 0; vehicle < vehicles(); ++vehicle) {
1613 CostClass cost_class(vehicle_to_transit_cost_[vehicle]);
1614
1615 // Insert the dimension data in a canonical way.
1616 for (const RoutingDimension* const dimension : dimensions_) {
1617 const int64_t coeff =
1618 dimension->vehicle_span_cost_coefficients()[vehicle];
1619 if (coeff == 0) continue;
1620 cost_class.dimension_transit_evaluator_class_and_cost_coefficient
1621 .push_back({dimension->vehicle_to_class(vehicle), coeff, dimension});
1622 }
1623 std::sort(cost_class.dimension_transit_evaluator_class_and_cost_coefficient
1624 .begin(),
1625 cost_class.dimension_transit_evaluator_class_and_cost_coefficient
1626 .end());
1627 // Try inserting the CostClass, if it's not already present.
1628 const CostClassIndex num_cost_classes(cost_classes_.size());
1629 const CostClassIndex cost_class_index =
1630 gtl::LookupOrInsert(&cost_class_map, cost_class, num_cost_classes);
1631 if (cost_class_index == kCostClassIndexOfZeroCost) {
1632 has_vehicle_with_zero_cost_class_ = true;
1633 } else if (cost_class_index == num_cost_classes) { // New cost class.
1634 cost_classes_.push_back(cost_class);
1635 }
1636 cost_class_index_of_vehicle_[vehicle] = cost_class_index;
1637 }
1638
1639 // TRICKY:
1640 // If some vehicle had the "zero" cost class, then we'll have homogeneous
1641 // vehicles iff they all have that cost class (i.e. cost class count = 1).
1642 // If none of them have it, then we have homogeneous costs iff there are two
1643 // cost classes: the unused "zero" cost class and the one used by all
1644 // vehicles.
1645 // Note that we always need the zero cost class, even if no vehicle uses it,
1646 // because we use it in the vehicle_var = -1 scenario (i.e. unperformed).
1647 //
1648 // Fixed costs are simply ignored for computing these cost classes. They are
1649 // attached to start nodes directly.
1650 costs_are_homogeneous_across_vehicles_ &= has_vehicle_with_zero_cost_class_
1651 ? GetCostClassesCount() == 1
1652 : GetCostClassesCount() <= 2;
1653 }
1654
LessThan(const VehicleClass & a,const VehicleClass & b)1655 bool RoutingModel::VehicleClass::LessThan(const VehicleClass& a,
1656 const VehicleClass& b) {
1657 return std::tie(a.cost_class_index, a.fixed_cost, a.used_when_empty,
1658 a.start_equivalence_class, a.end_equivalence_class,
1659 a.unvisitable_nodes_fprint, a.dimension_start_cumuls_min,
1660 a.dimension_start_cumuls_max, a.dimension_end_cumuls_min,
1661 a.dimension_end_cumuls_max, a.dimension_capacities,
1662 a.dimension_evaluator_classes,
1663 a.required_resource_group_indices) <
1664 std::tie(b.cost_class_index, b.fixed_cost, b.used_when_empty,
1665 b.start_equivalence_class, b.end_equivalence_class,
1666 b.unvisitable_nodes_fprint, b.dimension_start_cumuls_min,
1667 b.dimension_start_cumuls_max, b.dimension_end_cumuls_min,
1668 b.dimension_end_cumuls_max, b.dimension_capacities,
1669 b.dimension_evaluator_classes,
1670 b.required_resource_group_indices);
1671 }
1672
ComputeVehicleClasses()1673 void RoutingModel::ComputeVehicleClasses() {
1674 vehicle_classes_.reserve(vehicles_);
1675 vehicle_classes_.clear();
1676 vehicle_class_index_of_vehicle_.assign(vehicles_, VehicleClassIndex(-1));
1677 std::map<VehicleClass, VehicleClassIndex, VehicleClassComparator>
1678 vehicle_class_map;
1679 const int nodes_unvisitability_num_bytes = (vehicle_vars_.size() + 7) / 8;
1680 std::unique_ptr<char[]> nodes_unvisitability_bitmask(
1681 new char[nodes_unvisitability_num_bytes]);
1682 for (int vehicle = 0; vehicle < vehicles(); ++vehicle) {
1683 VehicleClass vehicle_class;
1684 vehicle_class.cost_class_index = cost_class_index_of_vehicle_[vehicle];
1685 vehicle_class.fixed_cost = fixed_cost_of_vehicle_[vehicle];
1686 vehicle_class.used_when_empty = vehicle_used_when_empty_[vehicle];
1687 vehicle_class.start_equivalence_class =
1688 index_to_equivalence_class_[Start(vehicle)];
1689 vehicle_class.end_equivalence_class =
1690 index_to_equivalence_class_[End(vehicle)];
1691 for (const RoutingDimension* const dimension : dimensions_) {
1692 IntVar* const start_cumul_var = dimension->cumuls()[Start(vehicle)];
1693 vehicle_class.dimension_start_cumuls_min.push_back(
1694 start_cumul_var->Min());
1695 vehicle_class.dimension_start_cumuls_max.push_back(
1696 start_cumul_var->Max());
1697 IntVar* const end_cumul_var = dimension->cumuls()[End(vehicle)];
1698 vehicle_class.dimension_end_cumuls_min.push_back(end_cumul_var->Min());
1699 vehicle_class.dimension_end_cumuls_max.push_back(end_cumul_var->Max());
1700 vehicle_class.dimension_capacities.push_back(
1701 dimension->vehicle_capacities()[vehicle]);
1702 vehicle_class.dimension_evaluator_classes.push_back(
1703 dimension->vehicle_to_class(vehicle));
1704 }
1705 memset(nodes_unvisitability_bitmask.get(), 0,
1706 nodes_unvisitability_num_bytes);
1707 for (int index = 0; index < vehicle_vars_.size(); ++index) {
1708 IntVar* const vehicle_var = vehicle_vars_[index];
1709 if (!IsStart(index) && !IsEnd(index) &&
1710 (!vehicle_var->Contains(vehicle) ||
1711 !IsVehicleAllowedForIndex(vehicle, index))) {
1712 nodes_unvisitability_bitmask[index / CHAR_BIT] |= 1U
1713 << (index % CHAR_BIT);
1714 }
1715 }
1716 vehicle_class.unvisitable_nodes_fprint = ThoroughHash(
1717 nodes_unvisitability_bitmask.get(), nodes_unvisitability_num_bytes);
1718 for (int rg_index = 0; rg_index < resource_groups_.size(); rg_index++) {
1719 if (resource_groups_[rg_index]->VehicleRequiresAResource(vehicle)) {
1720 vehicle_class.required_resource_group_indices.push_back(rg_index);
1721 }
1722 }
1723
1724 const VehicleClassIndex num_vehicle_classes(vehicle_classes_.size());
1725 const VehicleClassIndex vehicle_class_index = gtl::LookupOrInsert(
1726 &vehicle_class_map, vehicle_class, num_vehicle_classes);
1727 if (vehicle_class_index == num_vehicle_classes) { // New vehicle class
1728 vehicle_classes_.push_back(vehicle_class);
1729 }
1730 vehicle_class_index_of_vehicle_[vehicle] = vehicle_class_index;
1731 }
1732 }
1733
ComputeVehicleTypes()1734 void RoutingModel::ComputeVehicleTypes() {
1735 const int nodes_squared = nodes_ * nodes_;
1736 std::vector<int>& type_index_of_vehicle =
1737 vehicle_type_container_.type_index_of_vehicle;
1738 std::vector<std::set<VehicleTypeContainer::VehicleClassEntry>>&
1739 sorted_vehicle_classes_per_type =
1740 vehicle_type_container_.sorted_vehicle_classes_per_type;
1741 std::vector<std::deque<int>>& vehicles_per_vehicle_class =
1742 vehicle_type_container_.vehicles_per_vehicle_class;
1743
1744 type_index_of_vehicle.resize(vehicles_);
1745 sorted_vehicle_classes_per_type.clear();
1746 sorted_vehicle_classes_per_type.reserve(vehicles_);
1747 vehicles_per_vehicle_class.clear();
1748 vehicles_per_vehicle_class.resize(GetVehicleClassesCount());
1749
1750 absl::flat_hash_map<int64_t, int> type_to_type_index;
1751
1752 for (int v = 0; v < vehicles_; v++) {
1753 const int start = manager_.IndexToNode(Start(v)).value();
1754 const int end = manager_.IndexToNode(End(v)).value();
1755 const int cost_class = GetCostClassIndexOfVehicle(v).value();
1756 const int64_t type = cost_class * nodes_squared + start * nodes_ + end;
1757
1758 const auto& vehicle_type_added = type_to_type_index.insert(
1759 std::make_pair(type, type_to_type_index.size()));
1760
1761 const int index = vehicle_type_added.first->second;
1762
1763 const int vehicle_class = GetVehicleClassIndexOfVehicle(v).value();
1764 const VehicleTypeContainer::VehicleClassEntry class_entry = {
1765 vehicle_class, GetFixedCostOfVehicle(v)};
1766
1767 if (vehicle_type_added.second) {
1768 // Type was not indexed yet.
1769 DCHECK_EQ(sorted_vehicle_classes_per_type.size(), index);
1770 sorted_vehicle_classes_per_type.push_back({class_entry});
1771 } else {
1772 // Type already indexed.
1773 DCHECK_LT(index, sorted_vehicle_classes_per_type.size());
1774 sorted_vehicle_classes_per_type[index].insert(class_entry);
1775 }
1776 vehicles_per_vehicle_class[vehicle_class].push_back(v);
1777 type_index_of_vehicle[v] = index;
1778 }
1779 }
1780
FinalizeVisitTypes()1781 void RoutingModel::FinalizeVisitTypes() {
1782 // NOTE(user): This is necessary if CloseVisitTypes() was not called
1783 // explicitly before. This will be removed when the TODO regarding this logic
1784 // is addressed.
1785 CloseVisitTypes();
1786
1787 single_nodes_of_type_.clear();
1788 single_nodes_of_type_.resize(num_visit_types_);
1789 pair_indices_of_type_.clear();
1790 pair_indices_of_type_.resize(num_visit_types_);
1791 std::vector<absl::flat_hash_set<int>> pair_indices_added_for_type(
1792 num_visit_types_);
1793
1794 for (int index = 0; index < index_to_visit_type_.size(); index++) {
1795 const int visit_type = GetVisitType(index);
1796 if (visit_type < 0) {
1797 continue;
1798 }
1799 const std::vector<std::pair<int, int>>& pickup_index_pairs =
1800 index_to_pickup_index_pairs_[index];
1801 const std::vector<std::pair<int, int>>& delivery_index_pairs =
1802 index_to_delivery_index_pairs_[index];
1803 if (pickup_index_pairs.empty() && delivery_index_pairs.empty()) {
1804 single_nodes_of_type_[visit_type].push_back(index);
1805 }
1806 for (const std::vector<std::pair<int, int>>* index_pairs :
1807 {&pickup_index_pairs, &delivery_index_pairs}) {
1808 for (const std::pair<int, int>& index_pair : *index_pairs) {
1809 const int pair_index = index_pair.first;
1810 if (pair_indices_added_for_type[visit_type].insert(pair_index).second) {
1811 pair_indices_of_type_[visit_type].push_back(pair_index);
1812 }
1813 }
1814 }
1815 }
1816
1817 TopologicallySortVisitTypes();
1818 }
1819
TopologicallySortVisitTypes()1820 void RoutingModel::TopologicallySortVisitTypes() {
1821 if (!has_same_vehicle_type_requirements_ &&
1822 !has_temporal_type_requirements_) {
1823 return;
1824 }
1825 std::vector<std::pair<double, double>> type_requirement_tightness(
1826 num_visit_types_, {0, 0});
1827 std::vector<absl::flat_hash_set<int>> type_to_dependent_types(
1828 num_visit_types_);
1829 SparseBitset<> types_in_requirement_graph(num_visit_types_);
1830 std::vector<int> in_degree(num_visit_types_, 0);
1831 for (int type = 0; type < num_visit_types_; type++) {
1832 int num_alternative_required_types = 0;
1833 int num_required_sets = 0;
1834 for (const std::vector<absl::flat_hash_set<int>>*
1835 required_type_alternatives :
1836 {&required_type_alternatives_when_adding_type_index_[type],
1837 &required_type_alternatives_when_removing_type_index_[type],
1838 &same_vehicle_required_type_alternatives_per_type_index_[type]}) {
1839 for (const absl::flat_hash_set<int>& alternatives :
1840 *required_type_alternatives) {
1841 types_in_requirement_graph.Set(type);
1842 num_required_sets++;
1843 for (int required_type : alternatives) {
1844 type_requirement_tightness[required_type].second +=
1845 1.0 / alternatives.size();
1846 types_in_requirement_graph.Set(required_type);
1847 num_alternative_required_types++;
1848 if (type_to_dependent_types[required_type].insert(type).second) {
1849 in_degree[type]++;
1850 }
1851 }
1852 }
1853 }
1854 if (num_alternative_required_types > 0) {
1855 type_requirement_tightness[type].first += 1.0 * num_required_sets *
1856 num_required_sets /
1857 num_alternative_required_types;
1858 }
1859 }
1860
1861 // Compute topological order of visit types.
1862 topologically_sorted_visit_types_.clear();
1863 std::vector<int> current_types_with_zero_indegree;
1864 for (int type : types_in_requirement_graph.PositionsSetAtLeastOnce()) {
1865 DCHECK(type_requirement_tightness[type].first > 0 ||
1866 type_requirement_tightness[type].second > 0);
1867 if (in_degree[type] == 0) {
1868 current_types_with_zero_indegree.push_back(type);
1869 }
1870 }
1871
1872 int num_types_added = 0;
1873 while (!current_types_with_zero_indegree.empty()) {
1874 // Add all zero-degree nodes to the same topological order group, while
1875 // also marking their dependent types that become part of the next group.
1876 topologically_sorted_visit_types_.push_back({});
1877 std::vector<int>& topological_group =
1878 topologically_sorted_visit_types_.back();
1879 std::vector<int> next_types_with_zero_indegree;
1880 for (int type : current_types_with_zero_indegree) {
1881 topological_group.push_back(type);
1882 num_types_added++;
1883 for (int dependent_type : type_to_dependent_types[type]) {
1884 DCHECK_GT(in_degree[dependent_type], 0);
1885 if (--in_degree[dependent_type] == 0) {
1886 next_types_with_zero_indegree.push_back(dependent_type);
1887 }
1888 }
1889 }
1890 // Sort the types in the current topological group based on their
1891 // requirement tightness.
1892 // NOTE: For a deterministic order, types with equal tightness are sorted by
1893 // increasing type.
1894 // TODO(user): Put types of the same topological order and same
1895 // requirement tightness in a single group (so that they all get inserted
1896 // simultaneously by the GlobalCheapestInsertion heuristic, for instance).
1897 std::sort(topological_group.begin(), topological_group.end(),
1898 [&type_requirement_tightness](int type1, int type2) {
1899 const auto& tightness1 = type_requirement_tightness[type1];
1900 const auto& tightness2 = type_requirement_tightness[type2];
1901 return tightness1 > tightness2 ||
1902 (tightness1 == tightness2 && type1 < type2);
1903 });
1904 // Swap the current types with zero in-degree with the next ones.
1905 current_types_with_zero_indegree.swap(next_types_with_zero_indegree);
1906 }
1907
1908 const int num_types_in_requirement_graph =
1909 types_in_requirement_graph.NumberOfSetCallsWithDifferentArguments();
1910 DCHECK_LE(num_types_added, num_types_in_requirement_graph);
1911 if (num_types_added < num_types_in_requirement_graph) {
1912 // Requirement graph is cyclic, no topological order.
1913 topologically_sorted_visit_types_.clear();
1914 }
1915 }
1916
AddDisjunction(const std::vector<int64_t> & indices,int64_t penalty,int64_t max_cardinality)1917 RoutingModel::DisjunctionIndex RoutingModel::AddDisjunction(
1918 const std::vector<int64_t>& indices, int64_t penalty,
1919 int64_t max_cardinality) {
1920 CHECK_GE(max_cardinality, 1);
1921 for (int i = 0; i < indices.size(); ++i) {
1922 CHECK_NE(kUnassigned, indices[i]);
1923 }
1924
1925 const DisjunctionIndex disjunction_index(disjunctions_.size());
1926 disjunctions_.push_back({indices, {penalty, max_cardinality}});
1927 for (const int64_t index : indices) {
1928 index_to_disjunctions_[index].push_back(disjunction_index);
1929 }
1930 return disjunction_index;
1931 }
1932
HasMandatoryDisjunctions() const1933 bool RoutingModel::HasMandatoryDisjunctions() const {
1934 for (const auto& [indices, value] : disjunctions_) {
1935 if (value.penalty == kNoPenalty) return true;
1936 }
1937 return false;
1938 }
1939
HasMaxCardinalityConstrainedDisjunctions() const1940 bool RoutingModel::HasMaxCardinalityConstrainedDisjunctions() const {
1941 for (const auto& [indices, value] : disjunctions_) {
1942 if (indices.size() > value.max_cardinality) return true;
1943 }
1944 return false;
1945 }
1946
1947 std::vector<std::pair<int64_t, int64_t>>
GetPerfectBinaryDisjunctions() const1948 RoutingModel::GetPerfectBinaryDisjunctions() const {
1949 std::vector<std::pair<int64_t, int64_t>> var_index_pairs;
1950 for (const Disjunction& disjunction : disjunctions_) {
1951 const std::vector<int64_t>& var_indices = disjunction.indices;
1952 if (var_indices.size() != 2) continue;
1953 const int64_t v0 = var_indices[0];
1954 const int64_t v1 = var_indices[1];
1955 if (index_to_disjunctions_[v0].size() == 1 &&
1956 index_to_disjunctions_[v1].size() == 1) {
1957 // We output sorted pairs.
1958 var_index_pairs.push_back({std::min(v0, v1), std::max(v0, v1)});
1959 }
1960 }
1961 std::sort(var_index_pairs.begin(), var_index_pairs.end());
1962 return var_index_pairs;
1963 }
1964
IgnoreDisjunctionsAlreadyForcedToZero()1965 void RoutingModel::IgnoreDisjunctionsAlreadyForcedToZero() {
1966 CHECK(!closed_);
1967 for (Disjunction& disjunction : disjunctions_) {
1968 bool has_one_potentially_active_var = false;
1969 for (const int64_t var_index : disjunction.indices) {
1970 if (ActiveVar(var_index)->Max() > 0) {
1971 has_one_potentially_active_var = true;
1972 break;
1973 }
1974 }
1975 if (!has_one_potentially_active_var) {
1976 disjunction.value.max_cardinality = 0;
1977 }
1978 }
1979 }
1980
CreateDisjunction(DisjunctionIndex disjunction)1981 IntVar* RoutingModel::CreateDisjunction(DisjunctionIndex disjunction) {
1982 const std::vector<int64_t>& indices = disjunctions_[disjunction].indices;
1983 const int indices_size = indices.size();
1984 std::vector<IntVar*> disjunction_vars(indices_size);
1985 for (int i = 0; i < indices_size; ++i) {
1986 const int64_t index = indices[i];
1987 CHECK_LT(index, Size());
1988 disjunction_vars[i] = ActiveVar(index);
1989 }
1990 const int64_t max_cardinality =
1991 disjunctions_[disjunction].value.max_cardinality;
1992 IntVar* no_active_var = solver_->MakeBoolVar();
1993 IntVar* number_active_vars = solver_->MakeIntVar(0, max_cardinality);
1994 solver_->AddConstraint(
1995 solver_->MakeSumEquality(disjunction_vars, number_active_vars));
1996 solver_->AddConstraint(solver_->MakeIsDifferentCstCt(
1997 number_active_vars, max_cardinality, no_active_var));
1998 const int64_t penalty = disjunctions_[disjunction].value.penalty;
1999 if (penalty < 0) {
2000 no_active_var->SetMax(0);
2001 return nullptr;
2002 } else {
2003 return solver_->MakeProd(no_active_var, penalty)->Var();
2004 }
2005 }
2006
AddSoftSameVehicleConstraint(const std::vector<int64_t> & indices,int64_t cost)2007 void RoutingModel::AddSoftSameVehicleConstraint(
2008 const std::vector<int64_t>& indices, int64_t cost) {
2009 if (!indices.empty()) {
2010 ValuedNodes<int64_t> same_vehicle_cost;
2011 for (const int64_t index : indices) {
2012 same_vehicle_cost.indices.push_back(index);
2013 }
2014 same_vehicle_cost.value = cost;
2015 same_vehicle_costs_.push_back(same_vehicle_cost);
2016 }
2017 }
2018
SetAllowedVehiclesForIndex(const std::vector<int> & vehicles,int64_t index)2019 void RoutingModel::SetAllowedVehiclesForIndex(const std::vector<int>& vehicles,
2020 int64_t index) {
2021 auto& allowed_vehicles = allowed_vehicles_[index];
2022 allowed_vehicles.clear();
2023 for (int vehicle : vehicles) {
2024 allowed_vehicles.insert(vehicle);
2025 }
2026 }
2027
AddPickupAndDelivery(int64_t pickup,int64_t delivery)2028 void RoutingModel::AddPickupAndDelivery(int64_t pickup, int64_t delivery) {
2029 AddPickupAndDeliverySetsInternal({pickup}, {delivery});
2030 pickup_delivery_disjunctions_.push_back({kNoDisjunction, kNoDisjunction});
2031 }
2032
AddPickupAndDeliverySets(DisjunctionIndex pickup_disjunction,DisjunctionIndex delivery_disjunction)2033 void RoutingModel::AddPickupAndDeliverySets(
2034 DisjunctionIndex pickup_disjunction,
2035 DisjunctionIndex delivery_disjunction) {
2036 AddPickupAndDeliverySetsInternal(
2037 GetDisjunctionNodeIndices(pickup_disjunction),
2038 GetDisjunctionNodeIndices(delivery_disjunction));
2039 pickup_delivery_disjunctions_.push_back(
2040 {pickup_disjunction, delivery_disjunction});
2041 }
2042
AddPickupAndDeliverySetsInternal(const std::vector<int64_t> & pickups,const std::vector<int64_t> & deliveries)2043 void RoutingModel::AddPickupAndDeliverySetsInternal(
2044 const std::vector<int64_t>& pickups,
2045 const std::vector<int64_t>& deliveries) {
2046 if (pickups.empty() || deliveries.empty()) {
2047 return;
2048 }
2049 const int64_t size = Size();
2050 const int pair_index = pickup_delivery_pairs_.size();
2051 for (int pickup_index = 0; pickup_index < pickups.size(); pickup_index++) {
2052 const int64_t pickup = pickups[pickup_index];
2053 CHECK_LT(pickup, size);
2054 index_to_pickup_index_pairs_[pickup].emplace_back(pair_index, pickup_index);
2055 }
2056 for (int delivery_index = 0; delivery_index < deliveries.size();
2057 delivery_index++) {
2058 const int64_t delivery = deliveries[delivery_index];
2059 CHECK_LT(delivery, size);
2060 index_to_delivery_index_pairs_[delivery].emplace_back(pair_index,
2061 delivery_index);
2062 }
2063 pickup_delivery_pairs_.push_back({pickups, deliveries});
2064 }
2065
GetPickupIndexPairs(int64_t node_index) const2066 const std::vector<std::pair<int, int>>& RoutingModel::GetPickupIndexPairs(
2067 int64_t node_index) const {
2068 CHECK_LT(node_index, index_to_pickup_index_pairs_.size());
2069 return index_to_pickup_index_pairs_[node_index];
2070 }
2071
GetDeliveryIndexPairs(int64_t node_index) const2072 const std::vector<std::pair<int, int>>& RoutingModel::GetDeliveryIndexPairs(
2073 int64_t node_index) const {
2074 CHECK_LT(node_index, index_to_delivery_index_pairs_.size());
2075 return index_to_delivery_index_pairs_[node_index];
2076 }
2077
SetPickupAndDeliveryPolicyOfVehicle(PickupAndDeliveryPolicy policy,int vehicle)2078 void RoutingModel::SetPickupAndDeliveryPolicyOfVehicle(
2079 PickupAndDeliveryPolicy policy, int vehicle) {
2080 CHECK_LT(vehicle, vehicles_);
2081 vehicle_pickup_delivery_policy_[vehicle] = policy;
2082 }
2083
SetPickupAndDeliveryPolicyOfAllVehicles(PickupAndDeliveryPolicy policy)2084 void RoutingModel::SetPickupAndDeliveryPolicyOfAllVehicles(
2085 PickupAndDeliveryPolicy policy) {
2086 CHECK_LT(0, vehicles_);
2087 for (int i = 0; i < vehicles_; ++i) {
2088 SetPickupAndDeliveryPolicyOfVehicle(policy, i);
2089 }
2090 }
2091
2092 RoutingModel::PickupAndDeliveryPolicy
GetPickupAndDeliveryPolicyOfVehicle(int vehicle) const2093 RoutingModel::GetPickupAndDeliveryPolicyOfVehicle(int vehicle) const {
2094 CHECK_LT(vehicle, vehicles_);
2095 return vehicle_pickup_delivery_policy_[vehicle];
2096 }
2097
GetNumOfSingletonNodes() const2098 int RoutingModel::GetNumOfSingletonNodes() const {
2099 int count = 0;
2100 for (int i = 0; i < Nexts().size(); ++i) {
2101 // End nodes have no next variables.
2102 if (!IsStart(i) && GetPickupIndexPairs(i).empty() &&
2103 GetDeliveryIndexPairs(i).empty()) {
2104 ++count;
2105 }
2106 }
2107 return count;
2108 }
2109
CreateSameVehicleCost(int vehicle_index)2110 IntVar* RoutingModel::CreateSameVehicleCost(int vehicle_index) {
2111 const std::vector<int64_t>& indices =
2112 same_vehicle_costs_[vehicle_index].indices;
2113 CHECK(!indices.empty());
2114 std::vector<IntVar*> vehicle_counts;
2115 solver_->MakeIntVarArray(vehicle_vars_.size() + 1, 0, indices.size() + 1,
2116 &vehicle_counts);
2117 std::vector<int64_t> vehicle_values(vehicle_vars_.size() + 1);
2118 for (int i = 0; i < vehicle_vars_.size(); ++i) {
2119 vehicle_values[i] = i;
2120 }
2121 vehicle_values[vehicle_vars_.size()] = -1;
2122 std::vector<IntVar*> vehicle_vars;
2123 vehicle_vars.reserve(indices.size());
2124 for (const int64_t index : indices) {
2125 vehicle_vars.push_back(vehicle_vars_[index]);
2126 }
2127 solver_->AddConstraint(solver_->MakeDistribute(vehicle_vars, vehicle_counts));
2128 std::vector<IntVar*> vehicle_used;
2129 for (int i = 0; i < vehicle_vars_.size() + 1; ++i) {
2130 vehicle_used.push_back(
2131 solver_->MakeIsGreaterOrEqualCstVar(vehicle_counts[i], 1));
2132 }
2133 vehicle_used.push_back(solver_->MakeIntConst(-1));
2134 return solver_
2135 ->MakeProd(solver_->MakeMax(solver_->MakeSum(vehicle_used), 0),
2136 same_vehicle_costs_[vehicle_index].value)
2137 ->Var();
2138 }
2139
AddLocalSearchOperator(LocalSearchOperator * ls_operator)2140 void RoutingModel::AddLocalSearchOperator(LocalSearchOperator* ls_operator) {
2141 extra_operators_.push_back(ls_operator);
2142 }
2143
GetDepot() const2144 int64_t RoutingModel::GetDepot() const {
2145 return vehicles() > 0 ? Start(0) : -1;
2146 }
2147
2148 // TODO(user): Remove the need for the homogeneous version once the
2149 // vehicle var to cost class element constraint is fast enough.
AppendHomogeneousArcCosts(const RoutingSearchParameters & parameters,int node_index,std::vector<IntVar * > * cost_elements)2150 void RoutingModel::AppendHomogeneousArcCosts(
2151 const RoutingSearchParameters& parameters, int node_index,
2152 std::vector<IntVar*>* cost_elements) {
2153 CHECK(cost_elements != nullptr);
2154 const auto arc_cost_evaluator = [this, node_index](int64_t next_index) {
2155 return GetHomogeneousCost(node_index, next_index);
2156 };
2157 if (UsesLightPropagation(parameters)) {
2158 // Only supporting positive costs.
2159 // TODO(user): Detect why changing lower bound to kint64min stalls
2160 // the search in GLS in some cases (Solomon instances for instance).
2161 IntVar* const base_cost_var =
2162 solver_->MakeIntVar(0, std::numeric_limits<int64_t>::max());
2163 solver_->AddConstraint(MakeLightElement(
2164 solver_.get(), base_cost_var, nexts_[node_index], arc_cost_evaluator,
2165 [this]() { return enable_deep_serialization_; }));
2166 IntVar* const var =
2167 solver_->MakeProd(base_cost_var, active_[node_index])->Var();
2168 cost_elements->push_back(var);
2169 } else {
2170 IntExpr* const expr =
2171 solver_->MakeElement(arc_cost_evaluator, nexts_[node_index]);
2172 IntVar* const var = solver_->MakeProd(expr, active_[node_index])->Var();
2173 cost_elements->push_back(var);
2174 }
2175 }
2176
AppendArcCosts(const RoutingSearchParameters & parameters,int node_index,std::vector<IntVar * > * cost_elements)2177 void RoutingModel::AppendArcCosts(const RoutingSearchParameters& parameters,
2178 int node_index,
2179 std::vector<IntVar*>* cost_elements) {
2180 CHECK(cost_elements != nullptr);
2181 DCHECK_GT(vehicles_, 0);
2182 if (UsesLightPropagation(parameters)) {
2183 // Only supporting positive costs.
2184 // TODO(user): Detect why changing lower bound to kint64min stalls
2185 // the search in GLS in some cases (Solomon instances for instance).
2186 IntVar* const base_cost_var =
2187 solver_->MakeIntVar(0, std::numeric_limits<int64_t>::max());
2188 solver_->AddConstraint(MakeLightElement2(
2189 solver_.get(), base_cost_var, nexts_[node_index],
2190 vehicle_vars_[node_index],
2191 [this, node_index](int64_t to, int64_t vehicle) {
2192 return GetArcCostForVehicle(node_index, to, vehicle);
2193 },
2194 [this]() { return enable_deep_serialization_; }));
2195 IntVar* const var =
2196 solver_->MakeProd(base_cost_var, active_[node_index])->Var();
2197 cost_elements->push_back(var);
2198 } else {
2199 IntVar* const vehicle_class_var =
2200 solver_
2201 ->MakeElement(
2202 [this](int64_t index) {
2203 return SafeGetCostClassInt64OfVehicle(index);
2204 },
2205 vehicle_vars_[node_index])
2206 ->Var();
2207 IntExpr* const expr = solver_->MakeElement(
2208 [this, node_index](int64_t next, int64_t vehicle_class) {
2209 return GetArcCostForClass(node_index, next, vehicle_class);
2210 },
2211 nexts_[node_index], vehicle_class_var);
2212 IntVar* const var = solver_->MakeProd(expr, active_[node_index])->Var();
2213 cost_elements->push_back(var);
2214 }
2215 }
2216
GetVehicleStartClass(int64_t start_index) const2217 int RoutingModel::GetVehicleStartClass(int64_t start_index) const {
2218 const int vehicle = index_to_vehicle_[start_index];
2219 if (vehicle != kUnassigned) {
2220 return GetVehicleClassIndexOfVehicle(vehicle).value();
2221 }
2222 return kUnassigned;
2223 }
2224
FindErrorInSearchParametersForModel(const RoutingSearchParameters & search_parameters) const2225 std::string RoutingModel::FindErrorInSearchParametersForModel(
2226 const RoutingSearchParameters& search_parameters) const {
2227 const FirstSolutionStrategy::Value first_solution_strategy =
2228 search_parameters.first_solution_strategy();
2229 if (GetFirstSolutionDecisionBuilder(search_parameters) == nullptr) {
2230 return absl::StrCat(
2231 "Undefined first solution strategy: ",
2232 FirstSolutionStrategy::Value_Name(first_solution_strategy),
2233 " (int value: ", first_solution_strategy, ")");
2234 }
2235 if (search_parameters.first_solution_strategy() ==
2236 FirstSolutionStrategy::SWEEP &&
2237 sweep_arranger() == nullptr) {
2238 return "Undefined sweep arranger for ROUTING_SWEEP strategy.";
2239 }
2240 return "";
2241 }
2242
QuietCloseModel()2243 void RoutingModel::QuietCloseModel() {
2244 QuietCloseModelWithParameters(DefaultRoutingSearchParameters());
2245 }
2246
CloseModel()2247 void RoutingModel::CloseModel() {
2248 CloseModelWithParameters(DefaultRoutingSearchParameters());
2249 }
2250
2251 class RoutingModelInspector : public ModelVisitor {
2252 public:
RoutingModelInspector(RoutingModel * model)2253 explicit RoutingModelInspector(RoutingModel* model) : model_(model) {
2254 same_vehicle_components_.SetNumberOfNodes(model->Size());
2255 for (const std::string& name : model->GetAllDimensionNames()) {
2256 RoutingDimension* const dimension = model->GetMutableDimension(name);
2257 const std::vector<IntVar*>& cumuls = dimension->cumuls();
2258 for (int i = 0; i < cumuls.size(); ++i) {
2259 cumul_to_dim_indices_[cumuls[i]] = {dimension, i};
2260 }
2261 }
2262 const std::vector<IntVar*>& vehicle_vars = model->VehicleVars();
2263 for (int i = 0; i < vehicle_vars.size(); ++i) {
2264 vehicle_var_to_indices_[vehicle_vars[i]] = i;
2265 }
2266 RegisterInspectors();
2267 }
~RoutingModelInspector()2268 ~RoutingModelInspector() override {}
EndVisitModel(const std::string & solver_name)2269 void EndVisitModel(const std::string& solver_name) override {
2270 const std::vector<int> node_to_same_vehicle_component_id =
2271 same_vehicle_components_.GetComponentIds();
2272 model_->InitSameVehicleGroups(
2273 same_vehicle_components_.GetNumberOfComponents());
2274 for (int node = 0; node < model_->Size(); ++node) {
2275 model_->SetSameVehicleGroup(node,
2276 node_to_same_vehicle_component_id[node]);
2277 }
2278 // TODO(user): Perform transitive closure of dimension precedence graphs.
2279 // TODO(user): Have a single annotated precedence graph.
2280 }
EndVisitConstraint(const std::string & type_name,const Constraint * const constraint)2281 void EndVisitConstraint(const std::string& type_name,
2282 const Constraint* const constraint) override {
2283 gtl::FindWithDefault(constraint_inspectors_, type_name, []() {})();
2284 }
VisitIntegerExpressionArgument(const std::string & type_name,IntExpr * const expr)2285 void VisitIntegerExpressionArgument(const std::string& type_name,
2286 IntExpr* const expr) override {
2287 gtl::FindWithDefault(expr_inspectors_, type_name,
2288 [](const IntExpr* expr) {})(expr);
2289 }
VisitIntegerArrayArgument(const std::string & arg_name,const std::vector<int64_t> & values)2290 void VisitIntegerArrayArgument(const std::string& arg_name,
2291 const std::vector<int64_t>& values) override {
2292 gtl::FindWithDefault(array_inspectors_, arg_name,
2293 [](const std::vector<int64_t>& int_array) {})(values);
2294 }
2295
2296 private:
2297 using ExprInspector = std::function<void(const IntExpr*)>;
2298 using ArrayInspector = std::function<void(const std::vector<int64_t>&)>;
2299 using ConstraintInspector = std::function<void()>;
2300
RegisterInspectors()2301 void RegisterInspectors() {
2302 expr_inspectors_[kExpressionArgument] = [this](const IntExpr* expr) {
2303 expr_ = expr;
2304 };
2305 expr_inspectors_[kLeftArgument] = [this](const IntExpr* expr) {
2306 left_ = expr;
2307 };
2308 expr_inspectors_[kRightArgument] = [this](const IntExpr* expr) {
2309 right_ = expr;
2310 };
2311 array_inspectors_[kStartsArgument] =
2312 [this](const std::vector<int64_t>& int_array) {
2313 starts_argument_ = int_array;
2314 };
2315 array_inspectors_[kEndsArgument] =
2316 [this](const std::vector<int64_t>& int_array) {
2317 ends_argument_ = int_array;
2318 };
2319 constraint_inspectors_[kNotMember] = [this]() {
2320 std::pair<RoutingDimension*, int> dim_index;
2321 if (gtl::FindCopy(cumul_to_dim_indices_, expr_, &dim_index)) {
2322 RoutingDimension* const dimension = dim_index.first;
2323 const int index = dim_index.second;
2324 dimension->forbidden_intervals_[index].InsertIntervals(starts_argument_,
2325 ends_argument_);
2326 VLOG(2) << dimension->name() << " " << index << ": "
2327 << dimension->forbidden_intervals_[index].DebugString();
2328 }
2329 expr_ = nullptr;
2330 starts_argument_.clear();
2331 ends_argument_.clear();
2332 };
2333 constraint_inspectors_[kEquality] = [this]() {
2334 int left_index = 0;
2335 int right_index = 0;
2336 if (gtl::FindCopy(vehicle_var_to_indices_, left_, &left_index) &&
2337 gtl::FindCopy(vehicle_var_to_indices_, right_, &right_index)) {
2338 VLOG(2) << "Vehicle variables for " << left_index << " and "
2339 << right_index << " are equal.";
2340 same_vehicle_components_.AddEdge(left_index, right_index);
2341 }
2342 left_ = nullptr;
2343 right_ = nullptr;
2344 };
2345 constraint_inspectors_[kLessOrEqual] = [this]() {
2346 std::pair<RoutingDimension*, int> left_index;
2347 std::pair<RoutingDimension*, int> right_index;
2348 if (gtl::FindCopy(cumul_to_dim_indices_, left_, &left_index) &&
2349 gtl::FindCopy(cumul_to_dim_indices_, right_, &right_index)) {
2350 RoutingDimension* const dimension = left_index.first;
2351 if (dimension == right_index.first) {
2352 VLOG(2) << "For dimension " << dimension->name() << ", cumul for "
2353 << left_index.second << " is less than " << right_index.second
2354 << ".";
2355 dimension->path_precedence_graph_.AddArc(left_index.second,
2356 right_index.second);
2357 }
2358 }
2359 left_ = nullptr;
2360 right_ = nullptr;
2361 };
2362 }
2363
2364 RoutingModel* const model_;
2365 DenseConnectedComponentsFinder same_vehicle_components_;
2366 absl::flat_hash_map<const IntExpr*, std::pair<RoutingDimension*, int>>
2367 cumul_to_dim_indices_;
2368 absl::flat_hash_map<const IntExpr*, int> vehicle_var_to_indices_;
2369 absl::flat_hash_map<std::string, ExprInspector> expr_inspectors_;
2370 absl::flat_hash_map<std::string, ArrayInspector> array_inspectors_;
2371 absl::flat_hash_map<std::string, ConstraintInspector> constraint_inspectors_;
2372 const IntExpr* expr_ = nullptr;
2373 const IntExpr* left_ = nullptr;
2374 const IntExpr* right_ = nullptr;
2375 std::vector<int64_t> starts_argument_;
2376 std::vector<int64_t> ends_argument_;
2377 };
2378
DetectImplicitPickupAndDeliveries()2379 void RoutingModel::DetectImplicitPickupAndDeliveries() {
2380 std::vector<int> non_pickup_delivery_nodes;
2381 for (int node = 0; node < Size(); ++node) {
2382 if (!IsStart(node) && GetPickupIndexPairs(node).empty() &&
2383 GetDeliveryIndexPairs(node).empty()) {
2384 non_pickup_delivery_nodes.push_back(node);
2385 }
2386 }
2387 // Needs to be sorted for stability.
2388 std::set<std::pair<int64_t, int64_t>> implicit_pickup_deliveries;
2389 for (const RoutingDimension* const dimension : dimensions_) {
2390 if (dimension->class_evaluators_.size() != 1) {
2391 continue;
2392 }
2393 const TransitCallback1& transit =
2394 UnaryTransitCallbackOrNull(dimension->class_evaluators_[0]);
2395 if (transit == nullptr) continue;
2396 absl::flat_hash_map<int64_t, std::vector<int64_t>> nodes_by_positive_demand;
2397 absl::flat_hash_map<int64_t, std::vector<int64_t>> nodes_by_negative_demand;
2398 for (int node : non_pickup_delivery_nodes) {
2399 const int64_t demand = transit(node);
2400 if (demand > 0) {
2401 nodes_by_positive_demand[demand].push_back(node);
2402 } else if (demand < 0) {
2403 nodes_by_negative_demand[-demand].push_back(node);
2404 }
2405 }
2406 for (const auto& [demand, positive_nodes] : nodes_by_positive_demand) {
2407 const std::vector<int64_t>* const negative_nodes =
2408 gtl::FindOrNull(nodes_by_negative_demand, demand);
2409 if (negative_nodes != nullptr) {
2410 for (int64_t positive_node : positive_nodes) {
2411 for (int64_t negative_node : *negative_nodes) {
2412 implicit_pickup_deliveries.insert({positive_node, negative_node});
2413 }
2414 }
2415 }
2416 }
2417 }
2418 implicit_pickup_delivery_pairs_without_alternatives_.clear();
2419 for (auto [pickup, delivery] : implicit_pickup_deliveries) {
2420 implicit_pickup_delivery_pairs_without_alternatives_.emplace_back(
2421 std::vector<int64_t>({pickup}), std::vector<int64_t>({delivery}));
2422 }
2423 }
2424
CloseModelWithParameters(const RoutingSearchParameters & parameters)2425 void RoutingModel::CloseModelWithParameters(
2426 const RoutingSearchParameters& parameters) {
2427 std::string error = FindErrorInRoutingSearchParameters(parameters);
2428 if (!error.empty()) {
2429 status_ = ROUTING_INVALID;
2430 LOG(ERROR) << "Invalid RoutingSearchParameters: " << error;
2431 return;
2432 }
2433 if (closed_) {
2434 LOG(WARNING) << "Model already closed";
2435 return;
2436 }
2437 closed_ = true;
2438
2439 for (RoutingDimension* const dimension : dimensions_) {
2440 dimension->CloseModel(UsesLightPropagation(parameters));
2441 }
2442
2443 dimension_resource_group_indices_.resize(dimensions_.size());
2444 for (int rg_index = 0; rg_index < resource_groups_.size(); rg_index++) {
2445 const ResourceGroup& resource_group = *resource_groups_[rg_index];
2446 if (resource_group.GetVehiclesRequiringAResource().empty()) continue;
2447 for (DimensionIndex dim_index :
2448 resource_group.GetAffectedDimensionIndices()) {
2449 dimension_resource_group_indices_[dim_index].push_back(rg_index);
2450 }
2451 }
2452
2453 ComputeCostClasses(parameters);
2454 ComputeVehicleClasses();
2455 ComputeVehicleTypes();
2456 FinalizeVisitTypes();
2457 vehicle_start_class_callback_ = [this](int64_t start) {
2458 return GetVehicleStartClass(start);
2459 };
2460
2461 AddNoCycleConstraintInternal();
2462
2463 const int size = Size();
2464
2465 // Vehicle variable constraints
2466 for (int i = 0; i < vehicles_; ++i) {
2467 const int64_t start = starts_[i];
2468 const int64_t end = ends_[i];
2469 solver_->AddConstraint(
2470 solver_->MakeEquality(vehicle_vars_[start], solver_->MakeIntConst(i)));
2471 solver_->AddConstraint(
2472 solver_->MakeEquality(vehicle_vars_[end], solver_->MakeIntConst(i)));
2473 solver_->AddConstraint(
2474 solver_->MakeIsDifferentCstCt(nexts_[start], end, vehicle_active_[i]));
2475 if (vehicle_used_when_empty_[i]) {
2476 vehicle_route_considered_[i]->SetMin(1);
2477 } else {
2478 solver_->AddConstraint(solver_->MakeEquality(
2479 vehicle_active_[i], vehicle_route_considered_[i]));
2480 }
2481 }
2482
2483 // Limit the number of vehicles with non-empty routes.
2484 if (vehicles_ > max_active_vehicles_) {
2485 solver_->AddConstraint(
2486 solver_->MakeSumLessOrEqual(vehicle_active_, max_active_vehicles_));
2487 }
2488
2489 // If there is only one vehicle in the model the vehicle variables will have
2490 // a maximum domain of [-1, 0]. If a node is performed/active then its vehicle
2491 // variable will be reduced to [0] making the path-cumul constraint below
2492 // useless. If the node is unperformed/unactive then its vehicle variable will
2493 // be reduced to [-1] in any case.
2494 if (vehicles_ > 1) {
2495 std::vector<IntVar*> zero_transit(size, solver_->MakeIntConst(0));
2496 solver_->AddConstraint(solver_->MakeDelayedPathCumul(
2497 nexts_, active_, vehicle_vars_, zero_transit));
2498 }
2499
2500 // Nodes which are not in a disjunction are mandatory, and those with a
2501 // trivially infeasible type are necessarily unperformed
2502 for (int i = 0; i < size; ++i) {
2503 if (GetDisjunctionIndices(i).empty() && active_[i]->Max() != 0) {
2504 active_[i]->SetValue(1);
2505 }
2506 const int type = GetVisitType(i);
2507 if (type == kUnassigned) {
2508 continue;
2509 }
2510 const absl::flat_hash_set<VisitTypePolicy>* const infeasible_policies =
2511 gtl::FindOrNull(trivially_infeasible_visit_types_to_policies_, type);
2512 if (infeasible_policies != nullptr &&
2513 gtl::ContainsKey(*infeasible_policies, index_to_type_policy_[i])) {
2514 active_[i]->SetValue(0);
2515 }
2516 }
2517
2518 // Reduce domains of vehicle variables
2519 for (int i = 0; i < allowed_vehicles_.size(); ++i) {
2520 const auto& allowed_vehicles = allowed_vehicles_[i];
2521 if (!allowed_vehicles.empty()) {
2522 std::vector<int64_t> vehicles;
2523 vehicles.reserve(allowed_vehicles.size() + 1);
2524 vehicles.push_back(-1);
2525 for (int vehicle : allowed_vehicles) {
2526 vehicles.push_back(vehicle);
2527 }
2528 solver_->AddConstraint(solver_->MakeMemberCt(VehicleVar(i), vehicles));
2529 }
2530 }
2531
2532 // Reduce domain of next variables.
2533 for (int i = 0; i < size; ++i) {
2534 // No variable can point back to a start.
2535 solver_->AddConstraint(solver_->RevAlloc(
2536 new DifferentFromValues(solver_.get(), nexts_[i], starts_)));
2537 // Extra constraint to state an active node can't point to itself.
2538 solver_->AddConstraint(
2539 solver_->MakeIsDifferentCstCt(nexts_[i], i, active_[i]));
2540 }
2541
2542 // Add constraints to bind vehicle_vars_[i] to -1 in case that node i is not
2543 // active.
2544 for (int i = 0; i < size; ++i) {
2545 solver_->AddConstraint(
2546 solver_->MakeIsDifferentCstCt(vehicle_vars_[i], -1, active_[i]));
2547 }
2548
2549 if (HasTypeRegulations()) {
2550 solver_->AddConstraint(
2551 solver_->RevAlloc(new TypeRegulationsConstraint(*this)));
2552 }
2553
2554 // Associate first and "logical" last nodes
2555 for (int i = 0; i < vehicles_; ++i) {
2556 std::vector<int64_t> forbidden_ends;
2557 forbidden_ends.reserve(vehicles_ - 1);
2558 for (int j = 0; j < vehicles_; ++j) {
2559 if (i != j) {
2560 forbidden_ends.push_back(ends_[j]);
2561 }
2562 }
2563 solver_->AddConstraint(solver_->RevAlloc(new DifferentFromValues(
2564 solver_.get(), nexts_[starts_[i]], std::move(forbidden_ends))));
2565 }
2566
2567 // Constraining is_bound_to_end_ variables.
2568 for (const int64_t end : ends_) {
2569 is_bound_to_end_[end]->SetValue(1);
2570 }
2571
2572 std::vector<IntVar*> cost_elements;
2573 // Arc and dimension costs.
2574 if (vehicles_ > 0) {
2575 for (int node_index = 0; node_index < size; ++node_index) {
2576 if (CostsAreHomogeneousAcrossVehicles()) {
2577 AppendHomogeneousArcCosts(parameters, node_index, &cost_elements);
2578 } else {
2579 AppendArcCosts(parameters, node_index, &cost_elements);
2580 }
2581 }
2582 if (vehicle_amortized_cost_factors_set_) {
2583 std::vector<IntVar*> route_lengths;
2584 solver_->MakeIntVarArray(vehicles_, 0, size, &route_lengths);
2585 solver_->AddConstraint(
2586 solver_->MakeDistribute(vehicle_vars_, route_lengths));
2587 std::vector<IntVar*> vehicle_used;
2588 for (int i = 0; i < vehicles_; i++) {
2589 // The start/end of the vehicle are always on the route.
2590 vehicle_used.push_back(
2591 solver_->MakeIsGreaterCstVar(route_lengths[i], 2));
2592 IntVar* const var =
2593 solver_
2594 ->MakeProd(solver_->MakeOpposite(solver_->MakeSquare(
2595 solver_->MakeSum(route_lengths[i], -2))),
2596 quadratic_cost_factor_of_vehicle_[i])
2597 ->Var();
2598 cost_elements.push_back(var);
2599 }
2600 IntVar* const vehicle_usage_cost =
2601 solver_->MakeScalProd(vehicle_used, linear_cost_factor_of_vehicle_)
2602 ->Var();
2603 cost_elements.push_back(vehicle_usage_cost);
2604 }
2605 }
2606 // Dimension span constraints: cost and limits.
2607 for (const RoutingDimension* dimension : dimensions_) {
2608 dimension->SetupGlobalSpanCost(&cost_elements);
2609 dimension->SetupSlackAndDependentTransitCosts();
2610 const std::vector<int64_t>& span_costs =
2611 dimension->vehicle_span_cost_coefficients();
2612 const std::vector<int64_t>& span_ubs =
2613 dimension->vehicle_span_upper_bounds();
2614 const bool has_span_constraint =
2615 std::any_of(span_costs.begin(), span_costs.end(),
2616 [](int64_t coeff) { return coeff != 0; }) ||
2617 std::any_of(span_ubs.begin(), span_ubs.end(),
2618 [](int64_t value) {
2619 return value < std::numeric_limits<int64_t>::max();
2620 }) ||
2621 dimension->HasSoftSpanUpperBounds() ||
2622 dimension->HasQuadraticCostSoftSpanUpperBounds();
2623 if (has_span_constraint) {
2624 std::vector<IntVar*> spans(vehicles(), nullptr);
2625 std::vector<IntVar*> total_slacks(vehicles(), nullptr);
2626 // Generate variables only where needed.
2627 for (int vehicle = 0; vehicle < vehicles(); ++vehicle) {
2628 if (span_ubs[vehicle] < std::numeric_limits<int64_t>::max()) {
2629 spans[vehicle] = solver_->MakeIntVar(0, span_ubs[vehicle], "");
2630 }
2631 if (span_costs[vehicle] != 0) {
2632 total_slacks[vehicle] = solver_->MakeIntVar(0, span_ubs[vehicle], "");
2633 }
2634 }
2635 if (dimension->HasSoftSpanUpperBounds()) {
2636 for (int vehicle = 0; vehicle < vehicles(); ++vehicle) {
2637 if (spans[vehicle]) continue;
2638 const SimpleBoundCosts::BoundCost bound_cost =
2639 dimension->GetSoftSpanUpperBoundForVehicle(vehicle);
2640 if (bound_cost.cost == 0) continue;
2641 spans[vehicle] = solver_->MakeIntVar(0, span_ubs[vehicle]);
2642 }
2643 }
2644 if (dimension->HasQuadraticCostSoftSpanUpperBounds()) {
2645 for (int vehicle = 0; vehicle < vehicles(); ++vehicle) {
2646 if (spans[vehicle]) continue;
2647 const SimpleBoundCosts::BoundCost bound_cost =
2648 dimension->GetQuadraticCostSoftSpanUpperBoundForVehicle(vehicle);
2649 if (bound_cost.cost == 0) continue;
2650 spans[vehicle] = solver_->MakeIntVar(0, span_ubs[vehicle]);
2651 }
2652 }
2653 solver_->AddConstraint(
2654 MakePathSpansAndTotalSlacks(dimension, spans, total_slacks));
2655 // If a vehicle's span is constrained, its start/end cumuls must be
2656 // instantiated.
2657 for (int vehicle = 0; vehicle < vehicles(); ++vehicle) {
2658 if (!spans[vehicle] && !total_slacks[vehicle]) continue;
2659 if (spans[vehicle]) {
2660 AddVariableTargetToFinalizer(spans[vehicle],
2661 std::numeric_limits<int64_t>::min());
2662 }
2663 AddVariableTargetToFinalizer(dimension->CumulVar(End(vehicle)),
2664 std::numeric_limits<int64_t>::min());
2665 AddVariableTargetToFinalizer(dimension->CumulVar(Start(vehicle)),
2666 std::numeric_limits<int64_t>::max());
2667 }
2668 // Add costs of variables.
2669 for (int vehicle = 0; vehicle < vehicles(); ++vehicle) {
2670 if (span_costs[vehicle] == 0) continue;
2671 DCHECK(total_slacks[vehicle] != nullptr);
2672 IntVar* const slack_amount =
2673 solver_
2674 ->MakeProd(vehicle_route_considered_[vehicle],
2675 total_slacks[vehicle])
2676 ->Var();
2677 IntVar* const slack_cost =
2678 solver_->MakeProd(slack_amount, span_costs[vehicle])->Var();
2679 cost_elements.push_back(slack_cost);
2680 AddWeightedVariableMinimizedByFinalizer(slack_amount,
2681 span_costs[vehicle]);
2682 }
2683 if (dimension->HasSoftSpanUpperBounds()) {
2684 for (int vehicle = 0; vehicle < vehicles(); ++vehicle) {
2685 const auto bound_cost =
2686 dimension->GetSoftSpanUpperBoundForVehicle(vehicle);
2687 if (bound_cost.cost == 0 ||
2688 bound_cost.bound == std::numeric_limits<int64_t>::max())
2689 continue;
2690 DCHECK(spans[vehicle] != nullptr);
2691 // Additional cost is vehicle_cost_considered_[vehicle] *
2692 // max(0, spans[vehicle] - bound_cost.bound) * bound_cost.cost.
2693 IntVar* const span_violation_amount =
2694 solver_
2695 ->MakeProd(
2696 vehicle_route_considered_[vehicle],
2697 solver_->MakeMax(
2698 solver_->MakeSum(spans[vehicle], -bound_cost.bound),
2699 0))
2700 ->Var();
2701 IntVar* const span_violation_cost =
2702 solver_->MakeProd(span_violation_amount, bound_cost.cost)->Var();
2703 cost_elements.push_back(span_violation_cost);
2704 AddWeightedVariableMinimizedByFinalizer(span_violation_amount,
2705 bound_cost.cost);
2706 }
2707 }
2708 if (dimension->HasQuadraticCostSoftSpanUpperBounds()) {
2709 for (int vehicle = 0; vehicle < vehicles(); ++vehicle) {
2710 const auto bound_cost =
2711 dimension->GetQuadraticCostSoftSpanUpperBoundForVehicle(vehicle);
2712 if (bound_cost.cost == 0 ||
2713 bound_cost.bound == std::numeric_limits<int64_t>::max())
2714 continue;
2715 DCHECK(spans[vehicle] != nullptr);
2716 // Additional cost is vehicle_cost_considered_[vehicle] *
2717 // max(0, spans[vehicle] - bound_cost.bound)^2 * bound_cost.cost.
2718 IntExpr* max0 = solver_->MakeMax(
2719 solver_->MakeSum(spans[vehicle], -bound_cost.bound), 0);
2720 IntVar* const squared_span_violation_amount =
2721 solver_
2722 ->MakeProd(vehicle_route_considered_[vehicle],
2723 solver_->MakeSquare(max0))
2724 ->Var();
2725 IntVar* const span_violation_cost =
2726 solver_->MakeProd(squared_span_violation_amount, bound_cost.cost)
2727 ->Var();
2728 cost_elements.push_back(span_violation_cost);
2729 AddWeightedVariableMinimizedByFinalizer(squared_span_violation_amount,
2730 bound_cost.cost);
2731 }
2732 }
2733 }
2734 }
2735 // Penalty costs
2736 for (DisjunctionIndex i(0); i < disjunctions_.size(); ++i) {
2737 IntVar* penalty_var = CreateDisjunction(i);
2738 if (penalty_var != nullptr) {
2739 cost_elements.push_back(penalty_var);
2740 }
2741 }
2742 // Soft cumul lower/upper bound costs
2743 for (const RoutingDimension* dimension : dimensions_) {
2744 dimension->SetupCumulVarSoftLowerBoundCosts(&cost_elements);
2745 dimension->SetupCumulVarSoftUpperBoundCosts(&cost_elements);
2746 dimension->SetupCumulVarPiecewiseLinearCosts(&cost_elements);
2747 }
2748 // Same vehicle costs
2749 for (int i = 0; i < same_vehicle_costs_.size(); ++i) {
2750 cost_elements.push_back(CreateSameVehicleCost(i));
2751 }
2752 cost_ = solver_->MakeSum(cost_elements)->Var();
2753 cost_->set_name("Cost");
2754
2755 // Pickup-delivery precedences
2756 std::vector<std::pair<int, int>> pickup_delivery_precedences;
2757 for (const auto& pair : pickup_delivery_pairs_) {
2758 DCHECK(!pair.first.empty() && !pair.second.empty());
2759 for (int pickup : pair.first) {
2760 for (int delivery : pair.second) {
2761 pickup_delivery_precedences.emplace_back(pickup, delivery);
2762 }
2763 }
2764 }
2765 std::vector<int> lifo_vehicles;
2766 std::vector<int> fifo_vehicles;
2767 for (int i = 0; i < vehicles_; ++i) {
2768 switch (vehicle_pickup_delivery_policy_[i]) {
2769 case PICKUP_AND_DELIVERY_NO_ORDER:
2770 break;
2771 case PICKUP_AND_DELIVERY_LIFO:
2772 lifo_vehicles.push_back(Start(i));
2773 break;
2774 case PICKUP_AND_DELIVERY_FIFO:
2775 fifo_vehicles.push_back(Start(i));
2776 break;
2777 }
2778 }
2779 solver_->AddConstraint(solver_->MakePathPrecedenceConstraint(
2780 nexts_, pickup_delivery_precedences, lifo_vehicles, fifo_vehicles));
2781
2782 // Detect constraints
2783 enable_deep_serialization_ = false;
2784 std::unique_ptr<RoutingModelInspector> inspector(
2785 new RoutingModelInspector(this));
2786 solver_->Accept(inspector.get());
2787 enable_deep_serialization_ = true;
2788
2789 for (const RoutingDimension* const dimension : dimensions_) {
2790 // Dimension path precedences, discovered by model inspection (which must be
2791 // performed before adding path transit precedences).
2792 const ReverseArcListGraph<int, int>& graph =
2793 dimension->GetPathPrecedenceGraph();
2794 std::vector<std::pair<int, int>> path_precedences;
2795 for (const auto tail : graph.AllNodes()) {
2796 for (const auto head : graph[tail]) {
2797 path_precedences.emplace_back(tail, head);
2798 }
2799 }
2800 if (!path_precedences.empty()) {
2801 solver_->AddConstraint(solver_->MakePathTransitPrecedenceConstraint(
2802 nexts_, dimension->transits(), path_precedences));
2803 }
2804
2805 // Dimension node precedences.
2806 for (const RoutingDimension::NodePrecedence& node_precedence :
2807 dimension->GetNodePrecedences()) {
2808 const int64_t first_node = node_precedence.first_node;
2809 const int64_t second_node = node_precedence.second_node;
2810 IntExpr* const nodes_are_selected =
2811 solver_->MakeMin(active_[first_node], active_[second_node]);
2812 IntExpr* const cumul_difference = solver_->MakeDifference(
2813 dimension->CumulVar(second_node), dimension->CumulVar(first_node));
2814 IntVar* const cumul_difference_is_ge_offset =
2815 solver_->MakeIsGreaterOrEqualCstVar(cumul_difference,
2816 node_precedence.offset);
2817 // Forces the implication: both nodes are active => cumul difference
2818 // constraint is active.
2819 solver_->AddConstraint(solver_->MakeLessOrEqual(
2820 nodes_are_selected->Var(), cumul_difference_is_ge_offset));
2821 }
2822 }
2823
2824 // Setup resource vars and constraints.
2825 resource_vars_.resize(resource_groups_.size());
2826 for (int rg = 0; rg < resource_groups_.size(); rg++) {
2827 const ResourceGroup* const resource_group = resource_groups_[rg].get();
2828
2829 const int num_resources = resource_group->Size();
2830 std::vector<IntVar*>& resource_vars = resource_vars_[rg];
2831 solver_->MakeIntVarArray(vehicles_, -1, num_resources - 1,
2832 absl::StrCat("Resources[", rg, "]"),
2833 &resource_vars);
2834 // Resources cannot be shared, so assigned resources must all be different
2835 // (note that resource_var == -1 means no resource assigned).
2836 solver_->AddConstraint(solver_->MakeAllDifferentExcept(resource_vars, -1));
2837 for (int v = 0; v < vehicles_; v++) {
2838 IntVar* const resource_var = resource_vars[v];
2839 AddToAssignment(resource_var);
2840 if (!resource_group->VehicleRequiresAResource(v)) {
2841 resource_var->SetValue(-1);
2842 continue;
2843 }
2844 // vehicle_route_considered_[v] <--> resource_vars[v] != -1.
2845 solver_->AddConstraint(solver_->MakeEquality(
2846 vehicle_route_considered_[v],
2847 solver_->MakeIsDifferentCstVar(resource_var, -1)));
2848
2849 // Add dimension cumul constraints.
2850 for (const DimensionIndex d :
2851 resource_group->GetAffectedDimensionIndices()) {
2852 const RoutingDimension* const dim = dimensions_[d];
2853 // resource_start_lb_var <= cumul[start(v)] <= resource_start_ub_var
2854 IntVar* const start_cumul_var = dim->CumulVar(Start(v));
2855 IntVar* const resource_start_lb_var =
2856 solver_->MakeIntVar(std::numeric_limits<int64_t>::min(),
2857 std::numeric_limits<int64_t>::max());
2858 solver_->AddConstraint(MakeLightElement(
2859 solver_.get(), resource_start_lb_var, resource_var,
2860 [dim, resource_group](int r) {
2861 if (r < 0) return std::numeric_limits<int64_t>::min();
2862 return resource_group->GetResources()[r]
2863 .GetDimensionAttributes(dim)
2864 .start_domain()
2865 .Min();
2866 },
2867 [this]() { return enable_deep_serialization_; }));
2868 solver_->AddConstraint(solver_->MakeGreaterOrEqual(
2869 start_cumul_var, resource_start_lb_var));
2870
2871 IntVar* const resource_start_ub_var =
2872 solver_->MakeIntVar(std::numeric_limits<int64_t>::min(),
2873 std::numeric_limits<int64_t>::max());
2874 solver_->AddConstraint(MakeLightElement(
2875 solver_.get(), resource_start_ub_var, resource_var,
2876 [dim, resource_group](int r) {
2877 if (r < 0) return std::numeric_limits<int64_t>::max();
2878 return resource_group->GetResources()[r]
2879 .GetDimensionAttributes(dim)
2880 .start_domain()
2881 .Max();
2882 },
2883 [this]() { return enable_deep_serialization_; }));
2884 solver_->AddConstraint(
2885 solver_->MakeLessOrEqual(start_cumul_var, resource_start_ub_var));
2886
2887 // resource_end_lb_var <= cumul[end(v)] <= resource_end_ub_var
2888 IntVar* const end_cumul_var = dim->CumulVar(End(v));
2889 IntVar* const resource_end_lb_var =
2890 solver_->MakeIntVar(std::numeric_limits<int64_t>::min(),
2891 std::numeric_limits<int64_t>::max());
2892 solver_->AddConstraint(MakeLightElement(
2893 solver_.get(), resource_end_lb_var, resource_var,
2894 [dim, resource_group](int r) {
2895 if (r < 0) return std::numeric_limits<int64_t>::min();
2896 return resource_group->GetResources()[r]
2897 .GetDimensionAttributes(dim)
2898 .end_domain()
2899 .Min();
2900 },
2901 [this]() { return enable_deep_serialization_; }));
2902 solver_->AddConstraint(
2903 solver_->MakeGreaterOrEqual(end_cumul_var, resource_end_lb_var));
2904
2905 IntVar* const resource_end_ub_var =
2906 solver_->MakeIntVar(std::numeric_limits<int64_t>::min(),
2907 std::numeric_limits<int64_t>::max());
2908 solver_->AddConstraint(MakeLightElement(
2909 solver_.get(), resource_end_ub_var, resource_var,
2910 [dim, resource_group](int r) {
2911 if (r < 0) return std::numeric_limits<int64_t>::max();
2912 return resource_group->GetResources()[r]
2913 .GetDimensionAttributes(dim)
2914 .end_domain()
2915 .Max();
2916 },
2917 [this]() { return enable_deep_serialization_; }));
2918 solver_->AddConstraint(
2919 solver_->MakeLessOrEqual(end_cumul_var, resource_end_ub_var));
2920 }
2921 }
2922 }
2923
2924 DetectImplicitPickupAndDeliveries();
2925
2926 // Store the local/global cumul optimizers, along with their offsets.
2927 StoreDimensionCumulOptimizers(parameters);
2928
2929 // Keep this out of SetupSearch as this contains static search objects.
2930 // This will allow calling SetupSearch multiple times with different search
2931 // parameters.
2932 CreateNeighborhoodOperators(parameters);
2933 CreateFirstSolutionDecisionBuilders(parameters);
2934 error = FindErrorInSearchParametersForModel(parameters);
2935 if (!error.empty()) {
2936 status_ = ROUTING_INVALID;
2937 LOG(ERROR) << "Invalid RoutingSearchParameters for this model: " << error;
2938 return;
2939 }
2940 SetupSearch(parameters);
2941 }
2942
2943 namespace {
2944 // A decision builder that tries to set variables to their value in the last
2945 // solution, if their corresponding vehicle path has not changed.
2946 // This tries to constrain all such variables in one shot in order to speed up
2947 // instantiation.
2948 // TODO(user): try to use Assignment instead of MakeAssignment(),
2949 // try to record and restore the min/max instead of a single value.
2950 class RestoreDimensionValuesForUnchangedRoutes : public DecisionBuilder {
2951 public:
RestoreDimensionValuesForUnchangedRoutes(RoutingModel * model)2952 explicit RestoreDimensionValuesForUnchangedRoutes(RoutingModel* model)
2953 : model_(model) {
2954 model_->AddAtSolutionCallback([this]() { AtSolution(); });
2955 next_last_value_.resize(model_->Nexts().size(), -1);
2956 }
2957
2958 // In a given branch of a search tree, this decision builder only returns
2959 // a Decision once, the first time it is called in that branch.
Next(Solver * const s)2960 Decision* Next(Solver* const s) override {
2961 if (!must_return_decision_) return nullptr;
2962 s->SaveAndSetValue(&must_return_decision_, false);
2963 return MakeDecision(s);
2964 }
2965
2966 private:
2967 // Initialize() is lazy to make sure all dimensions have been instantiated
2968 // when initialization is done.
Initialize()2969 void Initialize() {
2970 is_initialized_ = true;
2971 const int num_nodes = model_->VehicleVars().size();
2972 node_to_integer_variable_indices_.resize(num_nodes);
2973 node_to_interval_variable_indices_.resize(num_nodes);
2974 // Search for dimension variables that correspond to input variables.
2975 for (const std::string& dimension_name : model_->GetAllDimensionNames()) {
2976 const RoutingDimension& dimension =
2977 model_->GetDimensionOrDie(dimension_name);
2978 // Search among cumuls and slacks, and attach them to corresponding nodes.
2979 for (const std::vector<IntVar*>& dimension_variables :
2980 {dimension.cumuls(), dimension.slacks()}) {
2981 const int num_dimension_variables = dimension_variables.size();
2982 DCHECK_LE(num_dimension_variables, num_nodes);
2983 for (int node = 0; node < num_dimension_variables; ++node) {
2984 node_to_integer_variable_indices_[node].push_back(
2985 integer_variables_.size());
2986 integer_variables_.push_back(dimension_variables[node]);
2987 }
2988 }
2989 // Search for break start/end variables, attach them to vehicle starts.
2990 for (int vehicle = 0; vehicle < model_->vehicles(); ++vehicle) {
2991 if (!dimension.HasBreakConstraints()) continue;
2992 const int vehicle_start = model_->Start(vehicle);
2993 for (IntervalVar* interval :
2994 dimension.GetBreakIntervalsOfVehicle(vehicle)) {
2995 node_to_interval_variable_indices_[vehicle_start].push_back(
2996 interval_variables_.size());
2997 interval_variables_.push_back(interval);
2998 }
2999 }
3000 }
3001 integer_variables_last_min_.resize(integer_variables_.size());
3002 interval_variables_last_start_min_.resize(interval_variables_.size());
3003 interval_variables_last_end_max_.resize(interval_variables_.size());
3004 }
3005
MakeDecision(Solver * const s)3006 Decision* MakeDecision(Solver* const s) {
3007 if (!is_initialized_) return nullptr;
3008 // Collect vehicles that have not changed.
3009 std::vector<int> unchanged_vehicles;
3010 const int num_vehicles = model_->vehicles();
3011 for (int v = 0; v < num_vehicles; ++v) {
3012 bool unchanged = true;
3013 for (int current = model_->Start(v); !model_->IsEnd(current);
3014 current = next_last_value_[current]) {
3015 if (!model_->NextVar(current)->Bound() ||
3016 next_last_value_[current] != model_->NextVar(current)->Value()) {
3017 unchanged = false;
3018 break;
3019 }
3020 }
3021 if (unchanged) unchanged_vehicles.push_back(v);
3022 }
3023 // If all routes are unchanged, the solver might be trying to do a full
3024 // reschedule. Do nothing.
3025 if (unchanged_vehicles.size() == num_vehicles) return nullptr;
3026
3027 // Collect cumuls and slacks of unchanged routes to be assigned a value.
3028 std::vector<IntVar*> vars;
3029 std::vector<int64_t> values;
3030 for (const int vehicle : unchanged_vehicles) {
3031 for (int current = model_->Start(vehicle); true;
3032 current = next_last_value_[current]) {
3033 for (const int index : node_to_integer_variable_indices_[current]) {
3034 vars.push_back(integer_variables_[index]);
3035 values.push_back(integer_variables_last_min_[index]);
3036 }
3037 for (const int index : node_to_interval_variable_indices_[current]) {
3038 const int64_t start_min = interval_variables_last_start_min_[index];
3039 const int64_t end_max = interval_variables_last_end_max_[index];
3040 if (start_min < end_max) {
3041 vars.push_back(interval_variables_[index]->SafeStartExpr(0)->Var());
3042 values.push_back(interval_variables_last_start_min_[index]);
3043 vars.push_back(interval_variables_[index]->SafeEndExpr(0)->Var());
3044 values.push_back(interval_variables_last_end_max_[index]);
3045 } else {
3046 vars.push_back(interval_variables_[index]->PerformedExpr()->Var());
3047 values.push_back(0);
3048 }
3049 }
3050 if (model_->IsEnd(current)) break;
3051 }
3052 }
3053 return s->MakeAssignVariablesValuesOrDoNothing(vars, values);
3054 }
3055
AtSolution()3056 void AtSolution() {
3057 if (!is_initialized_) Initialize();
3058 const int num_integers = integer_variables_.size();
3059 // Variables may not be fixed at solution time,
3060 // the decision builder is fine with the Min() of the unfixed variables.
3061 for (int i = 0; i < num_integers; ++i) {
3062 integer_variables_last_min_[i] = integer_variables_[i]->Min();
3063 }
3064 const int num_intervals = interval_variables_.size();
3065 for (int i = 0; i < num_intervals; ++i) {
3066 const bool is_performed = interval_variables_[i]->MustBePerformed();
3067 interval_variables_last_start_min_[i] =
3068 is_performed ? interval_variables_[i]->StartMin() : 0;
3069 interval_variables_last_end_max_[i] =
3070 is_performed ? interval_variables_[i]->EndMax() : -1;
3071 }
3072 const int num_nodes = next_last_value_.size();
3073 for (int node = 0; node < num_nodes; ++node) {
3074 if (model_->IsEnd(node)) continue;
3075 next_last_value_[node] = model_->NextVar(node)->Value();
3076 }
3077 }
3078
3079 // Input data.
3080 RoutingModel* const model_;
3081
3082 // The valuation of the last solution.
3083 std::vector<int> next_last_value_;
3084 // For every node, the indices of integer_variables_ and interval_variables_
3085 // that correspond to that node.
3086 std::vector<std::vector<int>> node_to_integer_variable_indices_;
3087 std::vector<std::vector<int>> node_to_interval_variable_indices_;
3088 // Variables and the value they had in the previous solution.
3089 std::vector<IntVar*> integer_variables_;
3090 std::vector<int64_t> integer_variables_last_min_;
3091 std::vector<IntervalVar*> interval_variables_;
3092 std::vector<int64_t> interval_variables_last_start_min_;
3093 std::vector<int64_t> interval_variables_last_end_max_;
3094
3095 bool is_initialized_ = false;
3096 bool must_return_decision_ = true;
3097 };
3098 } // namespace
3099
MakeRestoreDimensionValuesForUnchangedRoutes(RoutingModel * model)3100 DecisionBuilder* MakeRestoreDimensionValuesForUnchangedRoutes(
3101 RoutingModel* model) {
3102 return model->solver()->RevAlloc(
3103 new RestoreDimensionValuesForUnchangedRoutes(model));
3104 }
3105
AddSearchMonitor(SearchMonitor * const monitor)3106 void RoutingModel::AddSearchMonitor(SearchMonitor* const monitor) {
3107 monitors_.push_back(monitor);
3108 }
3109
3110 namespace {
3111 class AtSolutionCallbackMonitor : public SearchMonitor {
3112 public:
AtSolutionCallbackMonitor(Solver * solver,std::function<void ()> callback)3113 AtSolutionCallbackMonitor(Solver* solver, std::function<void()> callback)
3114 : SearchMonitor(solver), callback_(std::move(callback)) {}
AtSolution()3115 bool AtSolution() override {
3116 callback_();
3117 return false;
3118 }
3119
3120 private:
3121 std::function<void()> callback_;
3122 };
3123 } // namespace
3124
AddAtSolutionCallback(std::function<void ()> callback)3125 void RoutingModel::AddAtSolutionCallback(std::function<void()> callback) {
3126 AddSearchMonitor(solver_->RevAlloc(
3127 new AtSolutionCallbackMonitor(solver_.get(), std::move(callback))));
3128 }
3129
Solve(const Assignment * assignment)3130 const Assignment* RoutingModel::Solve(const Assignment* assignment) {
3131 return SolveFromAssignmentWithParameters(assignment,
3132 DefaultRoutingSearchParameters());
3133 }
3134
SolveWithParameters(const RoutingSearchParameters & parameters,std::vector<const Assignment * > * solutions)3135 const Assignment* RoutingModel::SolveWithParameters(
3136 const RoutingSearchParameters& parameters,
3137 std::vector<const Assignment*>* solutions) {
3138 return SolveFromAssignmentWithParameters(nullptr, parameters, solutions);
3139 }
3140
3141 namespace {
GetTimeLimit(const RoutingSearchParameters & parameters)3142 absl::Duration GetTimeLimit(const RoutingSearchParameters& parameters) {
3143 if (!parameters.has_time_limit()) return absl::InfiniteDuration();
3144 return util_time::DecodeGoogleApiProto(parameters.time_limit()).value();
3145 }
3146
GetLnsTimeLimit(const RoutingSearchParameters & parameters)3147 absl::Duration GetLnsTimeLimit(const RoutingSearchParameters& parameters) {
3148 if (!parameters.has_lns_time_limit()) return absl::InfiniteDuration();
3149 return util_time::DecodeGoogleApiProto(parameters.lns_time_limit()).value();
3150 }
3151
3152 } // namespace
3153
3154 namespace {
MakeAllUnperformedInAssignment(const RoutingModel * model,Assignment * assignment)3155 void MakeAllUnperformedInAssignment(const RoutingModel* model,
3156 Assignment* assignment) {
3157 assignment->Clear();
3158 for (int i = 0; i < model->Nexts().size(); ++i) {
3159 if (!model->IsStart(i)) {
3160 assignment->Add(model->NextVar(i))->SetValue(i);
3161 }
3162 }
3163 for (int vehicle = 0; vehicle < model->vehicles(); ++vehicle) {
3164 assignment->Add(model->NextVar(model->Start(vehicle)))
3165 ->SetValue(model->End(vehicle));
3166 }
3167 }
3168 } // namespace
3169
AppendAssignmentIfFeasible(const Assignment & assignment,std::vector<std::unique_ptr<Assignment>> * assignments)3170 bool RoutingModel::AppendAssignmentIfFeasible(
3171 const Assignment& assignment,
3172 std::vector<std::unique_ptr<Assignment>>* assignments) {
3173 tmp_assignment_->CopyIntersection(&assignment);
3174 solver_->Solve(restore_tmp_assignment_, collect_one_assignment_,
3175 GetOrCreateLimit());
3176 if (collect_one_assignment_->solution_count() == 1) {
3177 assignments->push_back(absl::make_unique<Assignment>(solver_.get()));
3178 assignments->back()->Copy(collect_one_assignment_->solution(0));
3179 return true;
3180 }
3181 return false;
3182 }
3183
LogSolution(const RoutingSearchParameters & parameters,const std::string & description,int64_t solution_cost,int64_t start_time_ms)3184 void RoutingModel::LogSolution(const RoutingSearchParameters& parameters,
3185 const std::string& description,
3186 int64_t solution_cost, int64_t start_time_ms) {
3187 const std::string memory_str = MemoryUsage();
3188 const double cost_scaling_factor = parameters.log_cost_scaling_factor();
3189 const double cost_offset = parameters.log_cost_offset();
3190 const std::string cost_string =
3191 cost_scaling_factor == 1.0 && cost_offset == 0.0
3192 ? absl::StrCat(solution_cost)
3193 : absl::StrFormat(
3194 "%d (%.8lf)", solution_cost,
3195 cost_scaling_factor * (solution_cost + cost_offset));
3196 LOG(INFO) << absl::StrFormat(
3197 "%s (%s, time = %d ms, memory used = %s)", description, cost_string,
3198 solver_->wall_time() - start_time_ms, memory_str);
3199 }
3200
SolveFromAssignmentWithParameters(const Assignment * assignment,const RoutingSearchParameters & parameters,std::vector<const Assignment * > * solutions)3201 const Assignment* RoutingModel::SolveFromAssignmentWithParameters(
3202 const Assignment* assignment, const RoutingSearchParameters& parameters,
3203 std::vector<const Assignment*>* solutions) {
3204 return SolveFromAssignmentsWithParameters({assignment}, parameters,
3205 solutions);
3206 }
3207
SolveFromAssignmentsWithParameters(const std::vector<const Assignment * > & assignments,const RoutingSearchParameters & parameters,std::vector<const Assignment * > * solutions)3208 const Assignment* RoutingModel::SolveFromAssignmentsWithParameters(
3209 const std::vector<const Assignment*>& assignments,
3210 const RoutingSearchParameters& parameters,
3211 std::vector<const Assignment*>* solutions) {
3212 const int64_t start_time_ms = solver_->wall_time();
3213 QuietCloseModelWithParameters(parameters);
3214 VLOG(1) << "Search parameters:\n" << parameters.DebugString();
3215 if (solutions != nullptr) solutions->clear();
3216 if (status_ == ROUTING_INVALID) {
3217 return nullptr;
3218 }
3219 const auto update_time_limits = [this, start_time_ms, ¶meters]() {
3220 const absl::Duration elapsed_time =
3221 absl::Milliseconds(solver_->wall_time() - start_time_ms);
3222 const absl::Duration time_left = GetTimeLimit(parameters) - elapsed_time;
3223 if (time_left >= absl::ZeroDuration()) {
3224 limit_->UpdateLimits(time_left, std::numeric_limits<int64_t>::max(),
3225 std::numeric_limits<int64_t>::max(),
3226 parameters.solution_limit());
3227 ls_limit_->UpdateLimits(time_left, std::numeric_limits<int64_t>::max(),
3228 std::numeric_limits<int64_t>::max(), 1);
3229 return true;
3230 }
3231 return false;
3232 };
3233 if (!update_time_limits()) {
3234 status_ = ROUTING_FAIL_TIMEOUT;
3235 return nullptr;
3236 }
3237 lns_limit_->UpdateLimits(
3238 GetLnsTimeLimit(parameters), std::numeric_limits<int64_t>::max(),
3239 std::numeric_limits<int64_t>::max(), std::numeric_limits<int64_t>::max());
3240 // NOTE: Allow more time for the first solution's scheduling, since if it
3241 // fails, we won't have anything to build upon.
3242 // We set this time limit based on whether local/global dimension optimizers
3243 // are used in the finalizer to avoid going over the general time limit.
3244 // TODO(user): Adapt this when absolute timeouts are given to the model.
3245 const int time_limit_shares = 1 + !global_dimension_optimizers_.empty() +
3246 !local_dimension_optimizers_.empty();
3247 const absl::Duration first_solution_lns_time_limit =
3248 std::max(GetTimeLimit(parameters) / time_limit_shares,
3249 GetLnsTimeLimit(parameters));
3250 first_solution_lns_limit_->UpdateLimits(
3251 first_solution_lns_time_limit, std::numeric_limits<int64_t>::max(),
3252 std::numeric_limits<int64_t>::max(), std::numeric_limits<int64_t>::max());
3253
3254 std::vector<std::unique_ptr<Assignment>> solution_pool;
3255 std::vector<const Assignment*> first_solution_assignments;
3256 for (const Assignment* assignment : assignments) {
3257 if (assignment != nullptr) first_solution_assignments.push_back(assignment);
3258 }
3259 if (parameters.use_cp() == BOOL_TRUE) {
3260 if (first_solution_assignments.empty()) {
3261 bool solution_found = false;
3262 Assignment matching(solver_.get());
3263 if (IsMatchingModel() && SolveMatchingModel(&matching, parameters) &&
3264 AppendAssignmentIfFeasible(matching, &solution_pool)) {
3265 if (parameters.log_search()) {
3266 LogSolution(parameters, "Min-Cost Flow Solution",
3267 solution_pool.back()->ObjectiveValue(), start_time_ms);
3268 }
3269 solution_found = true;
3270 }
3271 if (!solution_found) {
3272 // Build trivial solutions to which we can come back too in case the
3273 // solver does not manage to build something better.
3274 Assignment unperformed(solver_.get());
3275 MakeAllUnperformedInAssignment(this, &unperformed);
3276 if (AppendAssignmentIfFeasible(unperformed, &solution_pool) &&
3277 parameters.log_search()) {
3278 LogSolution(parameters, "All Unperformed Solution",
3279 solution_pool.back()->ObjectiveValue(), start_time_ms);
3280 }
3281 if (update_time_limits()) {
3282 solver_->Solve(solve_db_, monitors_);
3283 }
3284 }
3285 } else {
3286 for (const Assignment* assignment : first_solution_assignments) {
3287 assignment_->CopyIntersection(assignment);
3288 solver_->Solve(improve_db_, monitors_);
3289 if (collect_assignments_->solution_count() >= 1 ||
3290 !update_time_limits()) {
3291 break;
3292 }
3293 }
3294 }
3295 }
3296
3297 if (parameters.use_cp_sat() == BOOL_TRUE ||
3298 parameters.use_generalized_cp_sat() == BOOL_TRUE) {
3299 const int solution_count = collect_assignments_->solution_count();
3300 Assignment* const cp_solution =
3301 solution_count >= 1 ? collect_assignments_->solution(solution_count - 1)
3302 : nullptr;
3303 Assignment sat_solution(solver_.get());
3304 if (SolveModelWithSat(*this, parameters, cp_solution, &sat_solution) &&
3305 AppendAssignmentIfFeasible(sat_solution, &solution_pool) &&
3306 parameters.log_search()) {
3307 LogSolution(parameters, "SAT", solution_pool.back()->ObjectiveValue(),
3308 start_time_ms);
3309 }
3310 }
3311
3312 const absl::Duration elapsed_time =
3313 absl::Milliseconds(solver_->wall_time() - start_time_ms);
3314 const int solution_count = collect_assignments_->solution_count();
3315 if (solution_count >= 1 || !solution_pool.empty()) {
3316 status_ = ROUTING_SUCCESS;
3317 if (solutions != nullptr) {
3318 for (int i = 0; i < solution_count; ++i) {
3319 solutions->push_back(
3320 solver_->MakeAssignment(collect_assignments_->solution(i)));
3321 }
3322 for (const auto& solution : solution_pool) {
3323 if (solutions->empty() ||
3324 solution->ObjectiveValue() < solutions->back()->ObjectiveValue()) {
3325 solutions->push_back(solver_->MakeAssignment(solution.get()));
3326 }
3327 }
3328 return solutions->back();
3329 }
3330 Assignment* best_assignment =
3331 solution_count >= 1 ? collect_assignments_->solution(solution_count - 1)
3332 : nullptr;
3333 for (const auto& solution : solution_pool) {
3334 if (best_assignment == nullptr ||
3335 solution->ObjectiveValue() < best_assignment->ObjectiveValue()) {
3336 best_assignment = solution.get();
3337 }
3338 }
3339 return solver_->MakeAssignment(best_assignment);
3340 } else {
3341 if (elapsed_time >= GetTimeLimit(parameters)) {
3342 status_ = ROUTING_FAIL_TIMEOUT;
3343 } else {
3344 status_ = ROUTING_FAIL;
3345 }
3346 return nullptr;
3347 }
3348 }
3349
SetAssignmentFromOtherModelAssignment(Assignment * target_assignment,const RoutingModel * source_model,const Assignment * source_assignment)3350 void RoutingModel::SetAssignmentFromOtherModelAssignment(
3351 Assignment* target_assignment, const RoutingModel* source_model,
3352 const Assignment* source_assignment) {
3353 const int size = Size();
3354 DCHECK_EQ(size, source_model->Size());
3355 CHECK_EQ(target_assignment->solver(), solver_.get());
3356
3357 if (CostsAreHomogeneousAcrossVehicles()) {
3358 SetAssignmentFromAssignment(target_assignment, Nexts(), source_assignment,
3359 source_model->Nexts());
3360 } else {
3361 std::vector<IntVar*> source_vars(size + size + vehicles_);
3362 std::vector<IntVar*> target_vars(size + size + vehicles_);
3363 for (int index = 0; index < size; index++) {
3364 source_vars[index] = source_model->NextVar(index);
3365 target_vars[index] = NextVar(index);
3366 }
3367 for (int index = 0; index < size + vehicles_; index++) {
3368 source_vars[size + index] = source_model->VehicleVar(index);
3369 target_vars[size + index] = VehicleVar(index);
3370 }
3371 SetAssignmentFromAssignment(target_assignment, target_vars,
3372 source_assignment, source_vars);
3373 }
3374
3375 target_assignment->AddObjective(cost_);
3376 }
3377
3378 // Computing a lower bound to the cost of a vehicle routing problem solving a
3379 // a linear assignment problem (minimum-cost perfect bipartite matching).
3380 // A bipartite graph is created with left nodes representing the nodes of the
3381 // routing problem and right nodes representing possible node successors; an
3382 // arc between a left node l and a right node r is created if r can be the
3383 // node folowing l in a route (Next(l) = r); the cost of the arc is the transit
3384 // cost between l and r in the routing problem.
3385 // This is a lower bound given the solution to assignment problem does not
3386 // necessarily produce a (set of) closed route(s) from a starting node to an
3387 // ending node.
ComputeLowerBound()3388 int64_t RoutingModel::ComputeLowerBound() {
3389 if (!closed_) {
3390 LOG(WARNING) << "Non-closed model not supported.";
3391 return 0;
3392 }
3393 if (!CostsAreHomogeneousAcrossVehicles()) {
3394 LOG(WARNING) << "Non-homogeneous vehicle costs not supported";
3395 return 0;
3396 }
3397 if (!disjunctions_.empty()) {
3398 LOG(WARNING)
3399 << "Node disjunction constraints or optional nodes not supported.";
3400 return 0;
3401 }
3402 const int num_nodes = Size() + vehicles_;
3403 ForwardStarGraph graph(2 * num_nodes, num_nodes * num_nodes);
3404 LinearSumAssignment<ForwardStarGraph> linear_sum_assignment(graph, num_nodes);
3405 // Adding arcs for non-end nodes, based on possible values of next variables.
3406 // Left nodes in the bipartite are indexed from 0 to num_nodes - 1; right
3407 // nodes are indexed from num_nodes to 2 * num_nodes - 1.
3408 for (int tail = 0; tail < Size(); ++tail) {
3409 std::unique_ptr<IntVarIterator> iterator(
3410 nexts_[tail]->MakeDomainIterator(false));
3411 for (const int64_t head : InitAndGetValues(iterator.get())) {
3412 // Given there are no disjunction constraints, a node cannot point to
3413 // itself. Doing this explicitly given that outside the search,
3414 // propagation hasn't removed this value from next variables yet.
3415 if (head == tail) {
3416 continue;
3417 }
3418 // The index of a right node in the bipartite graph is the index
3419 // of the successor offset by the number of nodes.
3420 const ArcIndex arc = graph.AddArc(tail, num_nodes + head);
3421 const CostValue cost = GetHomogeneousCost(tail, head);
3422 linear_sum_assignment.SetArcCost(arc, cost);
3423 }
3424 }
3425 // The linear assignment library requires having as many left and right nodes.
3426 // Therefore we are creating fake assignments for end nodes, forced to point
3427 // to the equivalent start node with a cost of 0.
3428 for (int tail = Size(); tail < num_nodes; ++tail) {
3429 const ArcIndex arc = graph.AddArc(tail, num_nodes + starts_[tail - Size()]);
3430 linear_sum_assignment.SetArcCost(arc, 0);
3431 }
3432 if (linear_sum_assignment.ComputeAssignment()) {
3433 return linear_sum_assignment.GetCost();
3434 }
3435 return 0;
3436 }
3437
RouteCanBeUsedByVehicle(const Assignment & assignment,int start_index,int vehicle) const3438 bool RoutingModel::RouteCanBeUsedByVehicle(const Assignment& assignment,
3439 int start_index, int vehicle) const {
3440 int current_index =
3441 IsStart(start_index) ? Next(assignment, start_index) : start_index;
3442 while (!IsEnd(current_index)) {
3443 const IntVar* const vehicle_var = VehicleVar(current_index);
3444 if (!vehicle_var->Contains(vehicle)) {
3445 return false;
3446 }
3447 const int next_index = Next(assignment, current_index);
3448 CHECK_NE(next_index, current_index) << "Inactive node inside a route";
3449 current_index = next_index;
3450 }
3451 return true;
3452 }
3453
ReplaceUnusedVehicle(int unused_vehicle,int active_vehicle,Assignment * const compact_assignment) const3454 bool RoutingModel::ReplaceUnusedVehicle(
3455 int unused_vehicle, int active_vehicle,
3456 Assignment* const compact_assignment) const {
3457 CHECK(compact_assignment != nullptr);
3458 CHECK(!IsVehicleUsed(*compact_assignment, unused_vehicle));
3459 CHECK(IsVehicleUsed(*compact_assignment, active_vehicle));
3460 // Swap NextVars at start nodes.
3461 const int unused_vehicle_start = Start(unused_vehicle);
3462 IntVar* const unused_vehicle_start_var = NextVar(unused_vehicle_start);
3463 const int unused_vehicle_end = End(unused_vehicle);
3464 const int active_vehicle_start = Start(active_vehicle);
3465 const int active_vehicle_end = End(active_vehicle);
3466 IntVar* const active_vehicle_start_var = NextVar(active_vehicle_start);
3467 const int active_vehicle_next =
3468 compact_assignment->Value(active_vehicle_start_var);
3469 compact_assignment->SetValue(unused_vehicle_start_var, active_vehicle_next);
3470 compact_assignment->SetValue(active_vehicle_start_var, End(active_vehicle));
3471
3472 // Update VehicleVars along the route, update the last NextVar.
3473 int current_index = active_vehicle_next;
3474 while (!IsEnd(current_index)) {
3475 IntVar* const vehicle_var = VehicleVar(current_index);
3476 compact_assignment->SetValue(vehicle_var, unused_vehicle);
3477 const int next_index = Next(*compact_assignment, current_index);
3478 if (IsEnd(next_index)) {
3479 IntVar* const last_next_var = NextVar(current_index);
3480 compact_assignment->SetValue(last_next_var, End(unused_vehicle));
3481 }
3482 current_index = next_index;
3483 }
3484
3485 // Update dimensions: update transits at the start.
3486 for (const RoutingDimension* const dimension : dimensions_) {
3487 const std::vector<IntVar*>& transit_variables = dimension->transits();
3488 IntVar* const unused_vehicle_transit_var =
3489 transit_variables[unused_vehicle_start];
3490 IntVar* const active_vehicle_transit_var =
3491 transit_variables[active_vehicle_start];
3492 const bool contains_unused_vehicle_transit_var =
3493 compact_assignment->Contains(unused_vehicle_transit_var);
3494 const bool contains_active_vehicle_transit_var =
3495 compact_assignment->Contains(active_vehicle_transit_var);
3496 if (contains_unused_vehicle_transit_var !=
3497 contains_active_vehicle_transit_var) {
3498 // TODO(user): clarify the expected trigger rate of this LOG.
3499 LOG(INFO) << "The assignment contains transit variable for dimension '"
3500 << dimension->name() << "' for some vehicles, but not for all";
3501 return false;
3502 }
3503 if (contains_unused_vehicle_transit_var) {
3504 const int64_t old_unused_vehicle_transit =
3505 compact_assignment->Value(unused_vehicle_transit_var);
3506 const int64_t old_active_vehicle_transit =
3507 compact_assignment->Value(active_vehicle_transit_var);
3508 compact_assignment->SetValue(unused_vehicle_transit_var,
3509 old_active_vehicle_transit);
3510 compact_assignment->SetValue(active_vehicle_transit_var,
3511 old_unused_vehicle_transit);
3512 }
3513
3514 // Update dimensions: update cumuls at the end.
3515 const std::vector<IntVar*>& cumul_variables = dimension->cumuls();
3516 IntVar* const unused_vehicle_cumul_var =
3517 cumul_variables[unused_vehicle_end];
3518 IntVar* const active_vehicle_cumul_var =
3519 cumul_variables[active_vehicle_end];
3520 const int64_t old_unused_vehicle_cumul =
3521 compact_assignment->Value(unused_vehicle_cumul_var);
3522 const int64_t old_active_vehicle_cumul =
3523 compact_assignment->Value(active_vehicle_cumul_var);
3524 compact_assignment->SetValue(unused_vehicle_cumul_var,
3525 old_active_vehicle_cumul);
3526 compact_assignment->SetValue(active_vehicle_cumul_var,
3527 old_unused_vehicle_cumul);
3528 }
3529 return true;
3530 }
3531
CompactAssignment(const Assignment & assignment) const3532 Assignment* RoutingModel::CompactAssignment(
3533 const Assignment& assignment) const {
3534 return CompactAssignmentInternal(assignment, false);
3535 }
3536
CompactAndCheckAssignment(const Assignment & assignment) const3537 Assignment* RoutingModel::CompactAndCheckAssignment(
3538 const Assignment& assignment) const {
3539 return CompactAssignmentInternal(assignment, true);
3540 }
3541
CompactAssignmentInternal(const Assignment & assignment,bool check_compact_assignment) const3542 Assignment* RoutingModel::CompactAssignmentInternal(
3543 const Assignment& assignment, bool check_compact_assignment) const {
3544 CHECK_EQ(assignment.solver(), solver_.get());
3545 if (!CostsAreHomogeneousAcrossVehicles()) {
3546 LOG(WARNING)
3547 << "The costs are not homogeneous, routes cannot be rearranged";
3548 return nullptr;
3549 }
3550
3551 std::unique_ptr<Assignment> compact_assignment(new Assignment(&assignment));
3552 for (int vehicle = 0; vehicle < vehicles_ - 1; ++vehicle) {
3553 if (IsVehicleUsed(*compact_assignment, vehicle)) {
3554 continue;
3555 }
3556 const int vehicle_start = Start(vehicle);
3557 const int vehicle_end = End(vehicle);
3558 // Find the last vehicle, that can swap routes with this one.
3559 int swap_vehicle = vehicles_ - 1;
3560 bool has_more_vehicles_with_route = false;
3561 for (; swap_vehicle > vehicle; --swap_vehicle) {
3562 // If a vehicle was already swapped, it will appear in compact_assignment
3563 // as unused.
3564 if (!IsVehicleUsed(*compact_assignment, swap_vehicle) ||
3565 !IsVehicleUsed(*compact_assignment, swap_vehicle)) {
3566 continue;
3567 }
3568 has_more_vehicles_with_route = true;
3569 const int swap_vehicle_start = Start(swap_vehicle);
3570 const int swap_vehicle_end = End(swap_vehicle);
3571 if (manager_.IndexToNode(vehicle_start) !=
3572 manager_.IndexToNode(swap_vehicle_start) ||
3573 manager_.IndexToNode(vehicle_end) !=
3574 manager_.IndexToNode(swap_vehicle_end)) {
3575 continue;
3576 }
3577
3578 // Check that updating VehicleVars is OK.
3579 if (RouteCanBeUsedByVehicle(*compact_assignment, swap_vehicle_start,
3580 vehicle)) {
3581 break;
3582 }
3583 }
3584
3585 if (swap_vehicle == vehicle) {
3586 if (has_more_vehicles_with_route) {
3587 // No route can be assigned to this vehicle, but there are more vehicles
3588 // with a route left. This would leave a gap in the indices.
3589 // TODO(user): clarify the expected trigger rate of this LOG.
3590 LOG(INFO) << "No vehicle that can be swapped with " << vehicle
3591 << " was found";
3592 return nullptr;
3593 } else {
3594 break;
3595 }
3596 } else {
3597 if (!ReplaceUnusedVehicle(vehicle, swap_vehicle,
3598 compact_assignment.get())) {
3599 return nullptr;
3600 }
3601 }
3602 }
3603 if (check_compact_assignment &&
3604 !solver_->CheckAssignment(compact_assignment.get())) {
3605 // TODO(user): clarify the expected trigger rate of this LOG.
3606 LOG(WARNING) << "The compacted assignment is not a valid solution";
3607 return nullptr;
3608 }
3609 return compact_assignment.release();
3610 }
3611
FindNextActive(int index,const std::vector<int64_t> & indices) const3612 int RoutingModel::FindNextActive(int index,
3613 const std::vector<int64_t>& indices) const {
3614 ++index;
3615 CHECK_LE(0, index);
3616 const int size = indices.size();
3617 while (index < size && ActiveVar(indices[index])->Max() == 0) {
3618 ++index;
3619 }
3620 return index;
3621 }
3622
ApplyLocks(const std::vector<int64_t> & locks)3623 IntVar* RoutingModel::ApplyLocks(const std::vector<int64_t>& locks) {
3624 // TODO(user): Replace calls to this method with calls to
3625 // ApplyLocksToAllVehicles and remove this method?
3626 CHECK_EQ(vehicles_, 1);
3627 preassignment_->Clear();
3628 IntVar* next_var = nullptr;
3629 int lock_index = FindNextActive(-1, locks);
3630 const int size = locks.size();
3631 if (lock_index < size) {
3632 next_var = NextVar(locks[lock_index]);
3633 preassignment_->Add(next_var);
3634 for (lock_index = FindNextActive(lock_index, locks); lock_index < size;
3635 lock_index = FindNextActive(lock_index, locks)) {
3636 preassignment_->SetValue(next_var, locks[lock_index]);
3637 next_var = NextVar(locks[lock_index]);
3638 preassignment_->Add(next_var);
3639 }
3640 }
3641 return next_var;
3642 }
3643
ApplyLocksToAllVehicles(const std::vector<std::vector<int64_t>> & locks,bool close_routes)3644 bool RoutingModel::ApplyLocksToAllVehicles(
3645 const std::vector<std::vector<int64_t>>& locks, bool close_routes) {
3646 preassignment_->Clear();
3647 return RoutesToAssignment(locks, true, close_routes, preassignment_);
3648 }
3649
GetNumberOfDecisionsInFirstSolution(const RoutingSearchParameters & parameters) const3650 int64_t RoutingModel::GetNumberOfDecisionsInFirstSolution(
3651 const RoutingSearchParameters& parameters) const {
3652 IntVarFilteredDecisionBuilder* const decision_builder =
3653 GetFilteredFirstSolutionDecisionBuilderOrNull(parameters);
3654 return decision_builder != nullptr ? decision_builder->number_of_decisions()
3655 : 0;
3656 }
3657
GetNumberOfRejectsInFirstSolution(const RoutingSearchParameters & parameters) const3658 int64_t RoutingModel::GetNumberOfRejectsInFirstSolution(
3659 const RoutingSearchParameters& parameters) const {
3660 IntVarFilteredDecisionBuilder* const decision_builder =
3661 GetFilteredFirstSolutionDecisionBuilderOrNull(parameters);
3662 return decision_builder != nullptr ? decision_builder->number_of_rejects()
3663 : 0;
3664 }
3665
WriteAssignment(const std::string & file_name) const3666 bool RoutingModel::WriteAssignment(const std::string& file_name) const {
3667 if (collect_assignments_->solution_count() == 1 && assignment_ != nullptr) {
3668 assignment_->CopyIntersection(collect_assignments_->solution(0));
3669 return assignment_->Save(file_name);
3670 } else {
3671 return false;
3672 }
3673 }
3674
ReadAssignment(const std::string & file_name)3675 Assignment* RoutingModel::ReadAssignment(const std::string& file_name) {
3676 QuietCloseModel();
3677 CHECK(assignment_ != nullptr);
3678 if (assignment_->Load(file_name)) {
3679 return DoRestoreAssignment();
3680 }
3681 return nullptr;
3682 }
3683
RestoreAssignment(const Assignment & solution)3684 Assignment* RoutingModel::RestoreAssignment(const Assignment& solution) {
3685 QuietCloseModel();
3686 CHECK(assignment_ != nullptr);
3687 assignment_->CopyIntersection(&solution);
3688 return DoRestoreAssignment();
3689 }
3690
DoRestoreAssignment()3691 Assignment* RoutingModel::DoRestoreAssignment() {
3692 if (status_ == ROUTING_INVALID) {
3693 return nullptr;
3694 }
3695 solver_->Solve(restore_assignment_, monitors_);
3696 if (collect_assignments_->solution_count() == 1) {
3697 status_ = ROUTING_SUCCESS;
3698 return collect_assignments_->solution(0);
3699 } else {
3700 status_ = ROUTING_FAIL;
3701 return nullptr;
3702 }
3703 return nullptr;
3704 }
3705
RoutesToAssignment(const std::vector<std::vector<int64_t>> & routes,bool ignore_inactive_indices,bool close_routes,Assignment * const assignment) const3706 bool RoutingModel::RoutesToAssignment(
3707 const std::vector<std::vector<int64_t>>& routes,
3708 bool ignore_inactive_indices, bool close_routes,
3709 Assignment* const assignment) const {
3710 CHECK(assignment != nullptr);
3711 if (!closed_) {
3712 LOG(ERROR) << "The model is not closed yet";
3713 return false;
3714 }
3715 const int num_routes = routes.size();
3716 if (num_routes > vehicles_) {
3717 LOG(ERROR) << "The number of vehicles in the assignment (" << routes.size()
3718 << ") is greater than the number of vehicles in the model ("
3719 << vehicles_ << ")";
3720 return false;
3721 }
3722
3723 absl::flat_hash_set<int> visited_indices;
3724 // Set value to NextVars based on the routes.
3725 for (int vehicle = 0; vehicle < num_routes; ++vehicle) {
3726 const std::vector<int64_t>& route = routes[vehicle];
3727 int from_index = Start(vehicle);
3728 std::pair<absl::flat_hash_set<int>::iterator, bool> insert_result =
3729 visited_indices.insert(from_index);
3730 if (!insert_result.second) {
3731 LOG(ERROR) << "Index " << from_index << " (start node for vehicle "
3732 << vehicle << ") was already used";
3733 return false;
3734 }
3735
3736 for (const int64_t to_index : route) {
3737 if (to_index < 0 || to_index >= Size()) {
3738 LOG(ERROR) << "Invalid index: " << to_index;
3739 return false;
3740 }
3741
3742 IntVar* const active_var = ActiveVar(to_index);
3743 if (active_var->Max() == 0) {
3744 if (ignore_inactive_indices) {
3745 continue;
3746 } else {
3747 LOG(ERROR) << "Index " << to_index << " is not active";
3748 return false;
3749 }
3750 }
3751
3752 insert_result = visited_indices.insert(to_index);
3753 if (!insert_result.second) {
3754 LOG(ERROR) << "Index " << to_index << " is used multiple times";
3755 return false;
3756 }
3757
3758 const IntVar* const vehicle_var = VehicleVar(to_index);
3759 if (!vehicle_var->Contains(vehicle)) {
3760 LOG(ERROR) << "Vehicle " << vehicle << " is not allowed at index "
3761 << to_index;
3762 return false;
3763 }
3764
3765 IntVar* const from_var = NextVar(from_index);
3766 if (!assignment->Contains(from_var)) {
3767 assignment->Add(from_var);
3768 }
3769 assignment->SetValue(from_var, to_index);
3770
3771 from_index = to_index;
3772 }
3773
3774 if (close_routes) {
3775 IntVar* const last_var = NextVar(from_index);
3776 if (!assignment->Contains(last_var)) {
3777 assignment->Add(last_var);
3778 }
3779 assignment->SetValue(last_var, End(vehicle));
3780 }
3781 }
3782
3783 // Do not use the remaining vehicles.
3784 for (int vehicle = num_routes; vehicle < vehicles_; ++vehicle) {
3785 const int start_index = Start(vehicle);
3786 // Even if close_routes is false, we still need to add the start index to
3787 // visited_indices so that deactivating other nodes works correctly.
3788 std::pair<absl::flat_hash_set<int>::iterator, bool> insert_result =
3789 visited_indices.insert(start_index);
3790 if (!insert_result.second) {
3791 LOG(ERROR) << "Index " << start_index << " is used multiple times";
3792 return false;
3793 }
3794 if (close_routes) {
3795 IntVar* const start_var = NextVar(start_index);
3796 if (!assignment->Contains(start_var)) {
3797 assignment->Add(start_var);
3798 }
3799 assignment->SetValue(start_var, End(vehicle));
3800 }
3801 }
3802
3803 // Deactivate other nodes (by pointing them to themselves).
3804 if (close_routes) {
3805 for (int index = 0; index < Size(); ++index) {
3806 if (!gtl::ContainsKey(visited_indices, index)) {
3807 IntVar* const next_var = NextVar(index);
3808 if (!assignment->Contains(next_var)) {
3809 assignment->Add(next_var);
3810 }
3811 assignment->SetValue(next_var, index);
3812 }
3813 }
3814 }
3815
3816 return true;
3817 }
3818
ReadAssignmentFromRoutes(const std::vector<std::vector<int64_t>> & routes,bool ignore_inactive_indices)3819 Assignment* RoutingModel::ReadAssignmentFromRoutes(
3820 const std::vector<std::vector<int64_t>>& routes,
3821 bool ignore_inactive_indices) {
3822 QuietCloseModel();
3823 if (!RoutesToAssignment(routes, ignore_inactive_indices, true, assignment_)) {
3824 return nullptr;
3825 }
3826 // DoRestoreAssignment() might still fail when checking constraints (most
3827 // constraints are not verified by RoutesToAssignment) or when filling in
3828 // dimension variables.
3829 return DoRestoreAssignment();
3830 }
3831
AssignmentToRoutes(const Assignment & assignment,std::vector<std::vector<int64_t>> * const routes) const3832 void RoutingModel::AssignmentToRoutes(
3833 const Assignment& assignment,
3834 std::vector<std::vector<int64_t>>* const routes) const {
3835 CHECK(closed_);
3836 CHECK(routes != nullptr);
3837
3838 const int model_size = Size();
3839 routes->resize(vehicles_);
3840 for (int vehicle = 0; vehicle < vehicles_; ++vehicle) {
3841 std::vector<int64_t>* const vehicle_route = &routes->at(vehicle);
3842 vehicle_route->clear();
3843
3844 int num_visited_indices = 0;
3845 const int first_index = Start(vehicle);
3846 const IntVar* const first_var = NextVar(first_index);
3847 CHECK(assignment.Contains(first_var));
3848 CHECK(assignment.Bound(first_var));
3849 int current_index = assignment.Value(first_var);
3850 while (!IsEnd(current_index)) {
3851 vehicle_route->push_back(current_index);
3852
3853 const IntVar* const next_var = NextVar(current_index);
3854 CHECK(assignment.Contains(next_var));
3855 CHECK(assignment.Bound(next_var));
3856 current_index = assignment.Value(next_var);
3857
3858 ++num_visited_indices;
3859 CHECK_LE(num_visited_indices, model_size)
3860 << "The assignment contains a cycle";
3861 }
3862 }
3863 }
3864
3865 #ifndef SWIG
GetRoutesFromAssignment(const Assignment & assignment)3866 std::vector<std::vector<int64_t>> RoutingModel::GetRoutesFromAssignment(
3867 const Assignment& assignment) {
3868 std::vector<std::vector<int64_t>> route_indices(vehicles());
3869 for (int vehicle = 0; vehicle < vehicles(); ++vehicle) {
3870 if (!assignment.Bound(NextVar(vehicle))) {
3871 LOG(DFATAL) << "GetRoutesFromAssignment() called on incomplete solution:"
3872 << " NextVar(" << vehicle << ") is unbound.";
3873 }
3874 }
3875 for (int vehicle = 0; vehicle < vehicles(); ++vehicle) {
3876 int64_t index = Start(vehicle);
3877 route_indices[vehicle].push_back(index);
3878 while (!IsEnd(index)) {
3879 index = assignment.Value(NextVar(index));
3880 route_indices[vehicle].push_back(index);
3881 }
3882 }
3883 return route_indices;
3884 }
3885 #endif
3886
GetArcCostForClassInternal(int64_t from_index,int64_t to_index,CostClassIndex cost_class_index) const3887 int64_t RoutingModel::GetArcCostForClassInternal(
3888 int64_t from_index, int64_t to_index,
3889 CostClassIndex cost_class_index) const {
3890 DCHECK(closed_);
3891 DCHECK_GE(cost_class_index, 0);
3892 DCHECK_LT(cost_class_index, cost_classes_.size());
3893 CostCacheElement* const cache = &cost_cache_[from_index];
3894 // See the comment in CostCacheElement in the .h for the int64_t->int cast.
3895 if (cache->index == static_cast<int>(to_index) &&
3896 cache->cost_class_index == cost_class_index) {
3897 return cache->cost;
3898 }
3899 int64_t cost = 0;
3900 const CostClass& cost_class = cost_classes_[cost_class_index];
3901 const auto& evaluator = transit_evaluators_[cost_class.evaluator_index];
3902 if (!IsStart(from_index)) {
3903 cost = CapAdd(evaluator(from_index, to_index),
3904 GetDimensionTransitCostSum(from_index, to_index, cost_class));
3905 } else if (!IsEnd(to_index)) {
3906 // Apply route fixed cost on first non-first/last node, in other words on
3907 // the arc from the first node to its next node if it's not the last node.
3908 cost = CapAdd(
3909 evaluator(from_index, to_index),
3910 CapAdd(GetDimensionTransitCostSum(from_index, to_index, cost_class),
3911 fixed_cost_of_vehicle_[index_to_vehicle_[from_index]]));
3912 } else {
3913 // If there's only the first and last nodes on the route, it is considered
3914 // as an empty route.
3915 if (vehicle_used_when_empty_[index_to_vehicle_[from_index]]) {
3916 cost =
3917 CapAdd(evaluator(from_index, to_index),
3918 GetDimensionTransitCostSum(from_index, to_index, cost_class));
3919 } else {
3920 cost = 0;
3921 }
3922 }
3923 *cache = {static_cast<int>(to_index), cost_class_index, cost};
3924 return cost;
3925 }
3926
IsStart(int64_t index) const3927 bool RoutingModel::IsStart(int64_t index) const {
3928 return !IsEnd(index) && index_to_vehicle_[index] != kUnassigned;
3929 }
3930
IsVehicleUsed(const Assignment & assignment,int vehicle) const3931 bool RoutingModel::IsVehicleUsed(const Assignment& assignment,
3932 int vehicle) const {
3933 CHECK_GE(vehicle, 0);
3934 CHECK_LT(vehicle, vehicles_);
3935 CHECK_EQ(solver_.get(), assignment.solver());
3936 IntVar* const start_var = NextVar(Start(vehicle));
3937 CHECK(assignment.Contains(start_var));
3938 return !IsEnd(assignment.Value(start_var));
3939 }
3940
Next(const Assignment & assignment,int64_t index) const3941 int64_t RoutingModel::Next(const Assignment& assignment, int64_t index) const {
3942 CHECK_EQ(solver_.get(), assignment.solver());
3943 IntVar* const next_var = NextVar(index);
3944 CHECK(assignment.Contains(next_var));
3945 CHECK(assignment.Bound(next_var));
3946 return assignment.Value(next_var);
3947 }
3948
GetArcCostForVehicle(int64_t from_index,int64_t to_index,int64_t vehicle) const3949 int64_t RoutingModel::GetArcCostForVehicle(int64_t from_index, int64_t to_index,
3950 int64_t vehicle) const {
3951 if (from_index != to_index && vehicle >= 0) {
3952 return GetArcCostForClassInternal(from_index, to_index,
3953 GetCostClassIndexOfVehicle(vehicle));
3954 } else {
3955 return 0;
3956 }
3957 }
3958
GetArcCostForClass(int64_t from_index,int64_t to_index,int64_t cost_class_index) const3959 int64_t RoutingModel::GetArcCostForClass(
3960 int64_t from_index, int64_t to_index,
3961 int64_t /*CostClassIndex*/ cost_class_index) const {
3962 if (from_index != to_index) {
3963 return GetArcCostForClassInternal(from_index, to_index,
3964 CostClassIndex(cost_class_index));
3965 } else {
3966 return 0;
3967 }
3968 }
3969
GetArcCostForFirstSolution(int64_t from_index,int64_t to_index) const3970 int64_t RoutingModel::GetArcCostForFirstSolution(int64_t from_index,
3971 int64_t to_index) const {
3972 // Return high cost if connecting to an end (or bound-to-end) node;
3973 // this is used in the cost-based first solution strategies to avoid closing
3974 // routes too soon.
3975 if (!is_bound_to_end_ct_added_.Switched()) {
3976 // Lazily adding path-cumul constraint propagating connection to route end,
3977 // as it can be pretty costly in the general case.
3978 std::vector<IntVar*> zero_transit(Size(), solver_->MakeIntConst(0));
3979 solver_->AddConstraint(solver_->MakeDelayedPathCumul(
3980 nexts_, active_, is_bound_to_end_, zero_transit));
3981 is_bound_to_end_ct_added_.Switch(solver_.get());
3982 }
3983 if (is_bound_to_end_[to_index]->Min() == 1)
3984 return std::numeric_limits<int64_t>::max();
3985 // TODO(user): Take vehicle into account.
3986 return GetHomogeneousCost(from_index, to_index);
3987 }
3988
GetDimensionTransitCostSum(int64_t i,int64_t j,const CostClass & cost_class) const3989 int64_t RoutingModel::GetDimensionTransitCostSum(
3990 int64_t i, int64_t j, const CostClass& cost_class) const {
3991 int64_t cost = 0;
3992 for (const auto& evaluator_and_coefficient :
3993 cost_class.dimension_transit_evaluator_class_and_cost_coefficient) {
3994 DCHECK_GT(evaluator_and_coefficient.cost_coefficient, 0);
3995 cost = CapAdd(
3996 cost,
3997 CapProd(evaluator_and_coefficient.cost_coefficient,
3998 evaluator_and_coefficient.dimension->GetTransitValueFromClass(
3999 i, j, evaluator_and_coefficient.transit_evaluator_class)));
4000 }
4001 return cost;
4002 }
4003
ArcIsMoreConstrainedThanArc(int64_t from,int64_t to1,int64_t to2)4004 bool RoutingModel::ArcIsMoreConstrainedThanArc(int64_t from, int64_t to1,
4005 int64_t to2) {
4006 // Deal with end nodes: never pick an end node over a non-end node.
4007 if (IsEnd(to1) || IsEnd(to2)) {
4008 if (IsEnd(to1) != IsEnd(to2)) return IsEnd(to2);
4009 // If both are end nodes, we don't care; the right end node will be picked
4010 // by constraint propagation. Break the tie by index.
4011 return to1 < to2;
4012 }
4013
4014 // Look whether they are mandatory (must be performed) or optional.
4015 const bool mandatory1 = active_[to1]->Min() == 1;
4016 const bool mandatory2 = active_[to2]->Min() == 1;
4017 // Always pick a mandatory node over a non-mandatory one.
4018 if (mandatory1 != mandatory2) return mandatory1;
4019
4020 // Look at the vehicle variables.
4021 IntVar* const src_vehicle_var = VehicleVar(from);
4022 // In case the source vehicle is bound, "src_vehicle" will be it.
4023 // Otherwise, it'll be set to some possible source vehicle that
4024 // isn't -1 (if possible).
4025 const int64_t src_vehicle = src_vehicle_var->Max();
4026 if (src_vehicle_var->Bound()) {
4027 IntVar* const to1_vehicle_var = VehicleVar(to1);
4028 IntVar* const to2_vehicle_var = VehicleVar(to2);
4029 // Subtle: non-mandatory node have kNoVehicle as possible value for
4030 // their vehicle variable. So they're effectively "bound" when their domain
4031 // size is 2.
4032 const bool bound1 =
4033 mandatory1 ? to1_vehicle_var->Bound() : (to1_vehicle_var->Size() <= 2);
4034 const bool bound2 =
4035 mandatory2 ? to2_vehicle_var->Bound() : (to2_vehicle_var->Size() <= 2);
4036 // Prefer a destination bound to a given vehicle, even if it's not
4037 // bound to the right one (the propagation will quickly rule it out).
4038 if (bound1 != bound2) return bound1;
4039 if (bound1) { // same as bound1 && bound2.
4040 // Min() will return kNoVehicle for optional nodes. Thus we use Max().
4041 const int64_t vehicle1 = to1_vehicle_var->Max();
4042 const int64_t vehicle2 = to2_vehicle_var->Max();
4043 // Prefer a destination bound to the right vehicle.
4044 // TODO(user): cover this clause in a unit test.
4045 if ((vehicle1 == src_vehicle) != (vehicle2 == src_vehicle)) {
4046 return vehicle1 == src_vehicle;
4047 }
4048 // If no destination is bound to the right vehicle, whatever we
4049 // return doesn't matter: both are infeasible. To be consistent, we
4050 // just break the tie.
4051 if (vehicle1 != src_vehicle) return to1 < to2;
4052 }
4053 }
4054 // At this point, either both destinations are bound to the source vehicle,
4055 // or none of them is bound, or the source vehicle isn't bound.
4056 // We don't bother inspecting the domains of the vehicle variables further.
4057
4058 // Inspect the primary constrained dimension, if any.
4059 // TODO(user): try looking at all the dimensions, not just the primary one,
4060 // and reconsider the need for a "primary" dimension.
4061 if (!GetPrimaryConstrainedDimension().empty()) {
4062 const std::vector<IntVar*>& cumul_vars =
4063 GetDimensionOrDie(GetPrimaryConstrainedDimension()).cumuls();
4064 IntVar* const dim1 = cumul_vars[to1];
4065 IntVar* const dim2 = cumul_vars[to2];
4066 // Prefer the destination that has a lower upper bound for the constrained
4067 // dimension.
4068 if (dim1->Max() != dim2->Max()) return dim1->Max() < dim2->Max();
4069 // TODO(user): evaluate the *actual* Min() of each cumul variable in the
4070 // scenario where the corresponding arc from->to is performed, and pick
4071 // the destination with the lowest value.
4072 }
4073
4074 // Break ties on equally constrained nodes with the (cost - unperformed
4075 // penalty).
4076 {
4077 const /*CostClassIndex*/ int64_t cost_class_index =
4078 SafeGetCostClassInt64OfVehicle(src_vehicle);
4079 const int64_t cost1 =
4080 CapSub(GetArcCostForClass(from, to1, cost_class_index),
4081 UnperformedPenalty(to1));
4082 const int64_t cost2 =
4083 CapSub(GetArcCostForClass(from, to2, cost_class_index),
4084 UnperformedPenalty(to2));
4085 if (cost1 != cost2) return cost1 < cost2;
4086 }
4087
4088 // Further break ties by looking at the size of the VehicleVar.
4089 {
4090 const int64_t num_vehicles1 = VehicleVar(to1)->Size();
4091 const int64_t num_vehicles2 = VehicleVar(to2)->Size();
4092 if (num_vehicles1 != num_vehicles2) return num_vehicles1 < num_vehicles2;
4093 }
4094
4095 // Break perfect ties by value.
4096 return to1 < to2;
4097 }
4098
SetVisitType(int64_t index,int type,VisitTypePolicy policy)4099 void RoutingModel::SetVisitType(int64_t index, int type,
4100 VisitTypePolicy policy) {
4101 CHECK_LT(index, index_to_visit_type_.size());
4102 DCHECK_EQ(index_to_visit_type_.size(), index_to_type_policy_.size());
4103 index_to_visit_type_[index] = type;
4104 index_to_type_policy_[index] = policy;
4105 num_visit_types_ = std::max(num_visit_types_, type + 1);
4106 }
4107
GetVisitType(int64_t index) const4108 int RoutingModel::GetVisitType(int64_t index) const {
4109 CHECK_LT(index, index_to_visit_type_.size());
4110 return index_to_visit_type_[index];
4111 }
4112
GetSingleNodesOfType(int type) const4113 const std::vector<int>& RoutingModel::GetSingleNodesOfType(int type) const {
4114 DCHECK_LT(type, single_nodes_of_type_.size());
4115 return single_nodes_of_type_[type];
4116 }
4117
GetPairIndicesOfType(int type) const4118 const std::vector<int>& RoutingModel::GetPairIndicesOfType(int type) const {
4119 DCHECK_LT(type, pair_indices_of_type_.size());
4120 return pair_indices_of_type_[type];
4121 }
4122
GetVisitTypePolicy(int64_t index) const4123 RoutingModel::VisitTypePolicy RoutingModel::GetVisitTypePolicy(
4124 int64_t index) const {
4125 CHECK_LT(index, index_to_type_policy_.size());
4126 return index_to_type_policy_[index];
4127 }
4128
CloseVisitTypes()4129 void RoutingModel::CloseVisitTypes() {
4130 hard_incompatible_types_per_type_index_.resize(num_visit_types_);
4131 temporal_incompatible_types_per_type_index_.resize(num_visit_types_);
4132 same_vehicle_required_type_alternatives_per_type_index_.resize(
4133 num_visit_types_);
4134 required_type_alternatives_when_adding_type_index_.resize(num_visit_types_);
4135 required_type_alternatives_when_removing_type_index_.resize(num_visit_types_);
4136 }
4137
AddHardTypeIncompatibility(int type1,int type2)4138 void RoutingModel::AddHardTypeIncompatibility(int type1, int type2) {
4139 DCHECK_LT(std::max(type1, type2),
4140 hard_incompatible_types_per_type_index_.size());
4141 has_hard_type_incompatibilities_ = true;
4142
4143 hard_incompatible_types_per_type_index_[type1].insert(type2);
4144 hard_incompatible_types_per_type_index_[type2].insert(type1);
4145 }
4146
AddTemporalTypeIncompatibility(int type1,int type2)4147 void RoutingModel::AddTemporalTypeIncompatibility(int type1, int type2) {
4148 DCHECK_LT(std::max(type1, type2),
4149 temporal_incompatible_types_per_type_index_.size());
4150 has_temporal_type_incompatibilities_ = true;
4151
4152 temporal_incompatible_types_per_type_index_[type1].insert(type2);
4153 temporal_incompatible_types_per_type_index_[type2].insert(type1);
4154 }
4155
4156 const absl::flat_hash_set<int>&
GetHardTypeIncompatibilitiesOfType(int type) const4157 RoutingModel::GetHardTypeIncompatibilitiesOfType(int type) const {
4158 DCHECK_GE(type, 0);
4159 DCHECK_LT(type, hard_incompatible_types_per_type_index_.size());
4160 return hard_incompatible_types_per_type_index_[type];
4161 }
4162
4163 const absl::flat_hash_set<int>&
GetTemporalTypeIncompatibilitiesOfType(int type) const4164 RoutingModel::GetTemporalTypeIncompatibilitiesOfType(int type) const {
4165 DCHECK_GE(type, 0);
4166 DCHECK_LT(type, temporal_incompatible_types_per_type_index_.size());
4167 return temporal_incompatible_types_per_type_index_[type];
4168 }
4169
4170 // TODO(user): Consider if an empty "required_type_alternatives" should mean
4171 // trivially feasible requirement, as there are no required type alternatives?
AddSameVehicleRequiredTypeAlternatives(int dependent_type,absl::flat_hash_set<int> required_type_alternatives)4172 void RoutingModel::AddSameVehicleRequiredTypeAlternatives(
4173 int dependent_type, absl::flat_hash_set<int> required_type_alternatives) {
4174 DCHECK_LT(dependent_type,
4175 same_vehicle_required_type_alternatives_per_type_index_.size());
4176
4177 if (required_type_alternatives.empty()) {
4178 // The dependent_type requires an infeasible (empty) set of types.
4179 // Nodes of this type and all policies except
4180 // ADDED_TYPE_REMOVED_FROM_VEHICLE are trivially infeasible.
4181 absl::flat_hash_set<VisitTypePolicy>& infeasible_policies =
4182 trivially_infeasible_visit_types_to_policies_[dependent_type];
4183 infeasible_policies.insert(TYPE_ADDED_TO_VEHICLE);
4184 infeasible_policies.insert(TYPE_ON_VEHICLE_UP_TO_VISIT);
4185 infeasible_policies.insert(TYPE_SIMULTANEOUSLY_ADDED_AND_REMOVED);
4186 return;
4187 }
4188
4189 has_same_vehicle_type_requirements_ = true;
4190 same_vehicle_required_type_alternatives_per_type_index_[dependent_type]
4191 .push_back(std::move(required_type_alternatives));
4192 }
4193
AddRequiredTypeAlternativesWhenAddingType(int dependent_type,absl::flat_hash_set<int> required_type_alternatives)4194 void RoutingModel::AddRequiredTypeAlternativesWhenAddingType(
4195 int dependent_type, absl::flat_hash_set<int> required_type_alternatives) {
4196 DCHECK_LT(dependent_type,
4197 required_type_alternatives_when_adding_type_index_.size());
4198
4199 if (required_type_alternatives.empty()) {
4200 // The dependent_type requires an infeasible (empty) set of types.
4201 // Nodes of this type and policy TYPE_ADDED_TO_VEHICLE or
4202 // TYPE_SIMULTANEOUSLY_ADDED_AND_REMOVED are trivially infeasible.
4203 absl::flat_hash_set<VisitTypePolicy>& infeasible_policies =
4204 trivially_infeasible_visit_types_to_policies_[dependent_type];
4205 infeasible_policies.insert(TYPE_ADDED_TO_VEHICLE);
4206 infeasible_policies.insert(TYPE_SIMULTANEOUSLY_ADDED_AND_REMOVED);
4207 return;
4208 }
4209
4210 has_temporal_type_requirements_ = true;
4211 required_type_alternatives_when_adding_type_index_[dependent_type].push_back(
4212 std::move(required_type_alternatives));
4213 }
4214
AddRequiredTypeAlternativesWhenRemovingType(int dependent_type,absl::flat_hash_set<int> required_type_alternatives)4215 void RoutingModel::AddRequiredTypeAlternativesWhenRemovingType(
4216 int dependent_type, absl::flat_hash_set<int> required_type_alternatives) {
4217 DCHECK_LT(dependent_type,
4218 required_type_alternatives_when_removing_type_index_.size());
4219
4220 if (required_type_alternatives.empty()) {
4221 // The dependent_type requires an infeasible (empty) set of types.
4222 // Nodes of this type and all policies except TYPE_ADDED_TO_VEHICLE are
4223 // trivially infeasible.
4224 absl::flat_hash_set<VisitTypePolicy>& infeasible_policies =
4225 trivially_infeasible_visit_types_to_policies_[dependent_type];
4226 infeasible_policies.insert(ADDED_TYPE_REMOVED_FROM_VEHICLE);
4227 infeasible_policies.insert(TYPE_ON_VEHICLE_UP_TO_VISIT);
4228 infeasible_policies.insert(TYPE_SIMULTANEOUSLY_ADDED_AND_REMOVED);
4229 return;
4230 }
4231
4232 has_temporal_type_requirements_ = true;
4233 required_type_alternatives_when_removing_type_index_[dependent_type]
4234 .push_back(std::move(required_type_alternatives));
4235 }
4236
4237 const std::vector<absl::flat_hash_set<int>>&
GetSameVehicleRequiredTypeAlternativesOfType(int type) const4238 RoutingModel::GetSameVehicleRequiredTypeAlternativesOfType(int type) const {
4239 DCHECK_GE(type, 0);
4240 DCHECK_LT(type,
4241 same_vehicle_required_type_alternatives_per_type_index_.size());
4242 return same_vehicle_required_type_alternatives_per_type_index_[type];
4243 }
4244
4245 const std::vector<absl::flat_hash_set<int>>&
GetRequiredTypeAlternativesWhenAddingType(int type) const4246 RoutingModel::GetRequiredTypeAlternativesWhenAddingType(int type) const {
4247 DCHECK_GE(type, 0);
4248 DCHECK_LT(type, required_type_alternatives_when_adding_type_index_.size());
4249 return required_type_alternatives_when_adding_type_index_[type];
4250 }
4251
4252 const std::vector<absl::flat_hash_set<int>>&
GetRequiredTypeAlternativesWhenRemovingType(int type) const4253 RoutingModel::GetRequiredTypeAlternativesWhenRemovingType(int type) const {
4254 DCHECK_GE(type, 0);
4255 DCHECK_LT(type, required_type_alternatives_when_removing_type_index_.size());
4256 return required_type_alternatives_when_removing_type_index_[type];
4257 }
4258
UnperformedPenalty(int64_t var_index) const4259 int64_t RoutingModel::UnperformedPenalty(int64_t var_index) const {
4260 return UnperformedPenaltyOrValue(0, var_index);
4261 }
4262
UnperformedPenaltyOrValue(int64_t default_value,int64_t var_index) const4263 int64_t RoutingModel::UnperformedPenaltyOrValue(int64_t default_value,
4264 int64_t var_index) const {
4265 if (active_[var_index]->Min() == 1)
4266 return std::numeric_limits<int64_t>::max(); // Forced active.
4267 const std::vector<DisjunctionIndex>& disjunction_indices =
4268 GetDisjunctionIndices(var_index);
4269 if (disjunction_indices.size() != 1) return default_value;
4270 const DisjunctionIndex disjunction_index = disjunction_indices[0];
4271 // The disjunction penalty can be kNoPenalty iff there is more than one node
4272 // in the disjunction; otherwise we would have caught it earlier (the node
4273 // would be forced active).
4274 return std::max(int64_t{0}, disjunctions_[disjunction_index].value.penalty);
4275 }
4276
DebugOutputAssignment(const Assignment & solution_assignment,const std::string & dimension_to_print) const4277 std::string RoutingModel::DebugOutputAssignment(
4278 const Assignment& solution_assignment,
4279 const std::string& dimension_to_print) const {
4280 for (int i = 0; i < Size(); ++i) {
4281 if (!solution_assignment.Bound(NextVar(i))) {
4282 LOG(DFATAL)
4283 << "DebugOutputVehicleSchedules() called on incomplete solution:"
4284 << " NextVar(" << i << ") is unbound.";
4285 return "";
4286 }
4287 }
4288 std::string output;
4289 absl::flat_hash_set<std::string> dimension_names;
4290 if (dimension_to_print.empty()) {
4291 const std::vector<std::string> all_dimension_names = GetAllDimensionNames();
4292 dimension_names.insert(all_dimension_names.begin(),
4293 all_dimension_names.end());
4294 } else {
4295 dimension_names.insert(dimension_to_print);
4296 }
4297 for (int vehicle = 0; vehicle < vehicles(); ++vehicle) {
4298 int empty_vehicle_range_start = vehicle;
4299 while (vehicle < vehicles() &&
4300 IsEnd(solution_assignment.Value(NextVar(Start(vehicle))))) {
4301 vehicle++;
4302 }
4303 if (empty_vehicle_range_start != vehicle) {
4304 if (empty_vehicle_range_start == vehicle - 1) {
4305 absl::StrAppendFormat(&output, "Vehicle %d: empty",
4306 empty_vehicle_range_start);
4307 } else {
4308 absl::StrAppendFormat(&output, "Vehicles %d-%d: empty",
4309 empty_vehicle_range_start, vehicle - 1);
4310 }
4311 output.append("\n");
4312 }
4313 if (vehicle < vehicles()) {
4314 absl::StrAppendFormat(&output, "Vehicle %d:", vehicle);
4315 int64_t index = Start(vehicle);
4316 for (;;) {
4317 const IntVar* vehicle_var = VehicleVar(index);
4318 absl::StrAppendFormat(&output, "%d Vehicle(%d) ", index,
4319 solution_assignment.Value(vehicle_var));
4320 for (const RoutingDimension* const dimension : dimensions_) {
4321 if (gtl::ContainsKey(dimension_names, dimension->name())) {
4322 const IntVar* const var = dimension->CumulVar(index);
4323 absl::StrAppendFormat(&output, "%s(%d..%d) ", dimension->name(),
4324 solution_assignment.Min(var),
4325 solution_assignment.Max(var));
4326 }
4327 }
4328 if (IsEnd(index)) break;
4329 index = solution_assignment.Value(NextVar(index));
4330 if (IsEnd(index)) output.append("Route end ");
4331 }
4332 output.append("\n");
4333 }
4334 }
4335 output.append("Unperformed nodes: ");
4336 bool has_unperformed = false;
4337 for (int i = 0; i < Size(); ++i) {
4338 if (!IsEnd(i) && !IsStart(i) &&
4339 solution_assignment.Value(NextVar(i)) == i) {
4340 absl::StrAppendFormat(&output, "%d ", i);
4341 has_unperformed = true;
4342 }
4343 }
4344 if (!has_unperformed) output.append("None");
4345 output.append("\n");
4346 return output;
4347 }
4348
4349 #ifndef SWIG
4350 std::vector<std::vector<std::pair<int64_t, int64_t>>>
GetCumulBounds(const Assignment & solution_assignment,const RoutingDimension & dimension)4351 RoutingModel::GetCumulBounds(const Assignment& solution_assignment,
4352 const RoutingDimension& dimension) {
4353 std::vector<std::vector<std::pair<int64_t, int64_t>>> cumul_bounds(
4354 vehicles());
4355 for (int vehicle = 0; vehicle < vehicles(); ++vehicle) {
4356 if (!solution_assignment.Bound(NextVar(vehicle))) {
4357 LOG(DFATAL) << "GetCumulBounds() called on incomplete solution:"
4358 << " NextVar(" << vehicle << ") is unbound.";
4359 }
4360 }
4361
4362 for (int vehicle_id = 0; vehicle_id < vehicles(); ++vehicle_id) {
4363 int64_t index = Start(vehicle_id);
4364 IntVar* dim_var = dimension.CumulVar(index);
4365 cumul_bounds[vehicle_id].emplace_back(solution_assignment.Min(dim_var),
4366 solution_assignment.Max(dim_var));
4367 while (!IsEnd(index)) {
4368 index = solution_assignment.Value(NextVar(index));
4369 IntVar* dim_var = dimension.CumulVar(index);
4370 cumul_bounds[vehicle_id].emplace_back(solution_assignment.Min(dim_var),
4371 solution_assignment.Max(dim_var));
4372 }
4373 }
4374 return cumul_bounds;
4375 }
4376 #endif
4377
GetOrCreateAssignment()4378 Assignment* RoutingModel::GetOrCreateAssignment() {
4379 if (assignment_ == nullptr) {
4380 assignment_ = solver_->MakeAssignment();
4381 assignment_->Add(nexts_);
4382 if (!CostsAreHomogeneousAcrossVehicles()) {
4383 assignment_->Add(vehicle_vars_);
4384 }
4385 assignment_->AddObjective(cost_);
4386 }
4387 return assignment_;
4388 }
4389
GetOrCreateTmpAssignment()4390 Assignment* RoutingModel::GetOrCreateTmpAssignment() {
4391 if (tmp_assignment_ == nullptr) {
4392 tmp_assignment_ = solver_->MakeAssignment();
4393 tmp_assignment_->Add(nexts_);
4394 }
4395 return tmp_assignment_;
4396 }
4397
GetOrCreateLimit()4398 RegularLimit* RoutingModel::GetOrCreateLimit() {
4399 if (limit_ == nullptr) {
4400 limit_ = solver_->MakeLimit(
4401 absl::InfiniteDuration(), std::numeric_limits<int64_t>::max(),
4402 std::numeric_limits<int64_t>::max(),
4403 std::numeric_limits<int64_t>::max(), /*smart_time_check=*/true);
4404 }
4405 return limit_;
4406 }
4407
GetOrCreateLocalSearchLimit()4408 RegularLimit* RoutingModel::GetOrCreateLocalSearchLimit() {
4409 if (ls_limit_ == nullptr) {
4410 ls_limit_ = solver_->MakeLimit(absl::InfiniteDuration(),
4411 std::numeric_limits<int64_t>::max(),
4412 std::numeric_limits<int64_t>::max(),
4413 /*solutions=*/1, /*smart_time_check=*/true);
4414 }
4415 return ls_limit_;
4416 }
4417
GetOrCreateLargeNeighborhoodSearchLimit()4418 RegularLimit* RoutingModel::GetOrCreateLargeNeighborhoodSearchLimit() {
4419 if (lns_limit_ == nullptr) {
4420 lns_limit_ = solver_->MakeLimit(
4421 absl::InfiniteDuration(), std::numeric_limits<int64_t>::max(),
4422 std::numeric_limits<int64_t>::max(),
4423 std::numeric_limits<int64_t>::max(), /*smart_time_check=*/false);
4424 }
4425 return lns_limit_;
4426 }
4427
4428 RegularLimit*
GetOrCreateFirstSolutionLargeNeighborhoodSearchLimit()4429 RoutingModel::GetOrCreateFirstSolutionLargeNeighborhoodSearchLimit() {
4430 if (first_solution_lns_limit_ == nullptr) {
4431 first_solution_lns_limit_ = solver_->MakeLimit(
4432 absl::InfiniteDuration(), std::numeric_limits<int64_t>::max(),
4433 std::numeric_limits<int64_t>::max(),
4434 std::numeric_limits<int64_t>::max(), /*smart_time_check=*/false);
4435 }
4436 return first_solution_lns_limit_;
4437 }
4438
CreateInsertionOperator()4439 LocalSearchOperator* RoutingModel::CreateInsertionOperator() {
4440 LocalSearchOperator* insertion_operator =
4441 CreateCPOperator<MakeActiveOperator>();
4442 if (!pickup_delivery_pairs_.empty()) {
4443 insertion_operator = solver_->ConcatenateOperators(
4444 {CreatePairOperator<MakePairActiveOperator>(), insertion_operator});
4445 }
4446 if (!implicit_pickup_delivery_pairs_without_alternatives_.empty()) {
4447 insertion_operator = solver_->ConcatenateOperators(
4448 {CreateOperator<MakePairActiveOperator>(
4449 implicit_pickup_delivery_pairs_without_alternatives_),
4450 insertion_operator});
4451 }
4452 return insertion_operator;
4453 }
4454
CreateMakeInactiveOperator()4455 LocalSearchOperator* RoutingModel::CreateMakeInactiveOperator() {
4456 LocalSearchOperator* make_inactive_operator =
4457 CreateCPOperator<MakeInactiveOperator>();
4458 if (!pickup_delivery_pairs_.empty()) {
4459 make_inactive_operator = solver_->ConcatenateOperators(
4460 {CreatePairOperator<MakePairInactiveOperator>(),
4461 make_inactive_operator});
4462 }
4463 return make_inactive_operator;
4464 }
4465
CreateNeighborhoodOperators(const RoutingSearchParameters & parameters)4466 void RoutingModel::CreateNeighborhoodOperators(
4467 const RoutingSearchParameters& parameters) {
4468 local_search_operators_.clear();
4469 local_search_operators_.resize(LOCAL_SEARCH_OPERATOR_COUNTER, nullptr);
4470 {
4471 // Operators defined by Solver::LocalSearchOperators.
4472 const std::vector<
4473 std::pair<RoutingLocalSearchOperator, Solver::LocalSearchOperators>>
4474 operator_by_type = {{OR_OPT, Solver::OROPT},
4475 {PATH_LNS, Solver::PATHLNS},
4476 {FULL_PATH_LNS, Solver::FULLPATHLNS},
4477 {INACTIVE_LNS, Solver::UNACTIVELNS}};
4478 for (const auto [type, op] : operator_by_type) {
4479 local_search_operators_[type] =
4480 CostsAreHomogeneousAcrossVehicles()
4481 ? solver_->MakeOperator(nexts_, op)
4482 : solver_->MakeOperator(nexts_, vehicle_vars_, op);
4483 }
4484 }
4485 {
4486 // Operators defined by Solver::EvaluatorLocalSearchOperators.
4487 const std::vector<std::pair<RoutingLocalSearchOperator,
4488 Solver::EvaluatorLocalSearchOperators>>
4489 operator_by_type = {{LIN_KERNIGHAN, Solver::LK},
4490 {TSP_OPT, Solver::TSPOPT},
4491 {TSP_LNS, Solver::TSPLNS}};
4492 for (const auto [type, op] : operator_by_type) {
4493 auto arc_cost =
4494 absl::bind_front(&RoutingModel::GetArcCostForVehicle, this);
4495 local_search_operators_[type] =
4496 CostsAreHomogeneousAcrossVehicles()
4497 ? solver_->MakeOperator(nexts_, std::move(arc_cost), op)
4498 : solver_->MakeOperator(nexts_, vehicle_vars_,
4499 std::move(arc_cost), op);
4500 }
4501 }
4502
4503 // Other operators defined in the CP solver.
4504 local_search_operators_[RELOCATE] = CreateCPOperator<Relocate>();
4505 local_search_operators_[EXCHANGE] = CreateCPOperator<Exchange>();
4506 local_search_operators_[CROSS] = CreateCPOperator<Cross>();
4507 local_search_operators_[TWO_OPT] = CreateCPOperator<TwoOpt>();
4508 local_search_operators_[RELOCATE_AND_MAKE_ACTIVE] =
4509 CreateCPOperator<RelocateAndMakeActiveOperator>();
4510 local_search_operators_[MAKE_ACTIVE_AND_RELOCATE] =
4511 CreateCPOperator<MakeActiveAndRelocate>();
4512 local_search_operators_[MAKE_CHAIN_INACTIVE] =
4513 CreateCPOperator<MakeChainInactiveOperator>();
4514 local_search_operators_[SWAP_ACTIVE] = CreateCPOperator<SwapActiveOperator>();
4515 local_search_operators_[EXTENDED_SWAP_ACTIVE] =
4516 CreateCPOperator<ExtendedSwapActiveOperator>();
4517
4518 // Routing-specific operators.
4519 local_search_operators_[MAKE_ACTIVE] = CreateInsertionOperator();
4520 local_search_operators_[MAKE_INACTIVE] = CreateMakeInactiveOperator();
4521 local_search_operators_[RELOCATE_PAIR] =
4522 CreatePairOperator<PairRelocateOperator>();
4523 std::vector<LocalSearchOperator*> light_relocate_pair_operators;
4524 light_relocate_pair_operators.push_back(
4525 CreatePairOperator<LightPairRelocateOperator>());
4526 local_search_operators_[LIGHT_RELOCATE_PAIR] =
4527 solver_->ConcatenateOperators(light_relocate_pair_operators);
4528 local_search_operators_[EXCHANGE_PAIR] =
4529 CreatePairOperator<PairExchangeOperator>();
4530 local_search_operators_[EXCHANGE_RELOCATE_PAIR] =
4531 CreatePairOperator<PairExchangeRelocateOperator>();
4532 local_search_operators_[RELOCATE_NEIGHBORS] =
4533 CreateOperator<MakeRelocateNeighborsOperator>(
4534 absl::bind_front(&RoutingModel::GetHomogeneousCost, this));
4535 local_search_operators_[NODE_PAIR_SWAP] = solver_->ConcatenateOperators(
4536 {CreatePairOperator<IndexPairSwapActiveOperator>(),
4537 CreatePairOperator<SwapIndexPairOperator>(),
4538 CreatePairOperator<PairNodeSwapActiveOperator<true>>(),
4539 CreatePairOperator<PairNodeSwapActiveOperator<false>>()});
4540 local_search_operators_[RELOCATE_SUBTRIP] =
4541 CreatePairOperator<RelocateSubtrip>();
4542 local_search_operators_[EXCHANGE_SUBTRIP] =
4543 CreatePairOperator<ExchangeSubtrip>();
4544
4545 const auto arc_cost_for_path_start =
4546 [this](int64_t before_node, int64_t after_node, int64_t start_index) {
4547 const int vehicle = index_to_vehicle_[start_index];
4548 const int64_t arc_cost =
4549 GetArcCostForVehicle(before_node, after_node, vehicle);
4550 return (before_node != start_index || IsEnd(after_node))
4551 ? arc_cost
4552 : CapSub(arc_cost, GetFixedCostOfVehicle(vehicle));
4553 };
4554 local_search_operators_[RELOCATE_EXPENSIVE_CHAIN] =
4555 solver_->RevAlloc(new RelocateExpensiveChain(
4556 nexts_,
4557 CostsAreHomogeneousAcrossVehicles() ? std::vector<IntVar*>()
4558 : vehicle_vars_,
4559 vehicle_start_class_callback_,
4560 parameters.relocate_expensive_chain_num_arcs_to_consider(),
4561 arc_cost_for_path_start));
4562
4563 // Insertion-based LNS neighborhoods.
4564 const auto make_global_cheapest_insertion_filtered_heuristic =
4565 [this, ¶meters]() {
4566 using Heuristic = GlobalCheapestInsertionFilteredHeuristic;
4567 Heuristic::GlobalCheapestInsertionParameters ls_gci_parameters;
4568 ls_gci_parameters.is_sequential = false;
4569 ls_gci_parameters.farthest_seeds_ratio = 0.0;
4570 ls_gci_parameters.neighbors_ratio =
4571 parameters.cheapest_insertion_ls_operator_neighbors_ratio();
4572 ls_gci_parameters.min_neighbors =
4573 parameters.cheapest_insertion_ls_operator_min_neighbors();
4574 ls_gci_parameters.use_neighbors_ratio_for_initialization = true;
4575 ls_gci_parameters.add_unperformed_entries =
4576 parameters.cheapest_insertion_add_unperformed_entries();
4577 return absl::make_unique<Heuristic>(
4578 this, absl::bind_front(&RoutingModel::GetArcCostForVehicle, this),
4579 absl::bind_front(&RoutingModel::UnperformedPenaltyOrValue, this, 0),
4580 GetOrCreateFeasibilityFilterManager(parameters), ls_gci_parameters);
4581 };
4582 const auto make_local_cheapest_insertion_filtered_heuristic =
4583 [this, ¶meters]() {
4584 return absl::make_unique<LocalCheapestInsertionFilteredHeuristic>(
4585 this, absl::bind_front(&RoutingModel::GetArcCostForVehicle, this),
4586 GetOrCreateFeasibilityFilterManager(parameters));
4587 };
4588 local_search_operators_[GLOBAL_CHEAPEST_INSERTION_CLOSE_NODES_LNS] =
4589 solver_->RevAlloc(new FilteredHeuristicCloseNodesLNSOperator(
4590 make_global_cheapest_insertion_filtered_heuristic(),
4591 parameters.heuristic_close_nodes_lns_num_nodes()));
4592
4593 local_search_operators_[LOCAL_CHEAPEST_INSERTION_CLOSE_NODES_LNS] =
4594 solver_->RevAlloc(new FilteredHeuristicCloseNodesLNSOperator(
4595 make_local_cheapest_insertion_filtered_heuristic(),
4596 parameters.heuristic_close_nodes_lns_num_nodes()));
4597
4598 local_search_operators_[GLOBAL_CHEAPEST_INSERTION_PATH_LNS] =
4599 solver_->RevAlloc(new FilteredHeuristicPathLNSOperator(
4600 make_global_cheapest_insertion_filtered_heuristic()));
4601
4602 local_search_operators_[LOCAL_CHEAPEST_INSERTION_PATH_LNS] =
4603 solver_->RevAlloc(new FilteredHeuristicPathLNSOperator(
4604 make_local_cheapest_insertion_filtered_heuristic()));
4605
4606 local_search_operators_
4607 [RELOCATE_PATH_GLOBAL_CHEAPEST_INSERTION_INSERT_UNPERFORMED] =
4608 solver_->RevAlloc(
4609 new RelocatePathAndHeuristicInsertUnperformedOperator(
4610 make_global_cheapest_insertion_filtered_heuristic()));
4611
4612 local_search_operators_[GLOBAL_CHEAPEST_INSERTION_EXPENSIVE_CHAIN_LNS] =
4613 solver_->RevAlloc(new FilteredHeuristicExpensiveChainLNSOperator(
4614 make_global_cheapest_insertion_filtered_heuristic(),
4615 parameters.heuristic_expensive_chain_lns_num_arcs_to_consider(),
4616 arc_cost_for_path_start));
4617
4618 local_search_operators_[LOCAL_CHEAPEST_INSERTION_EXPENSIVE_CHAIN_LNS] =
4619 solver_->RevAlloc(new FilteredHeuristicExpensiveChainLNSOperator(
4620 make_local_cheapest_insertion_filtered_heuristic(),
4621 parameters.heuristic_expensive_chain_lns_num_arcs_to_consider(),
4622 arc_cost_for_path_start));
4623 }
4624
4625 #define CP_ROUTING_PUSH_OPERATOR(operator_type, operator_method, operators) \
4626 if (search_parameters.local_search_operators().use_##operator_method() == \
4627 BOOL_TRUE) { \
4628 operators.push_back(local_search_operators_[operator_type]); \
4629 }
4630
ConcatenateOperators(const RoutingSearchParameters & search_parameters,const std::vector<LocalSearchOperator * > & operators) const4631 LocalSearchOperator* RoutingModel::ConcatenateOperators(
4632 const RoutingSearchParameters& search_parameters,
4633 const std::vector<LocalSearchOperator*>& operators) const {
4634 if (search_parameters.use_multi_armed_bandit_concatenate_operators()) {
4635 return solver_->MultiArmedBanditConcatenateOperators(
4636 operators,
4637 search_parameters
4638 .multi_armed_bandit_compound_operator_memory_coefficient(),
4639 search_parameters
4640 .multi_armed_bandit_compound_operator_exploration_coefficient(),
4641 /*maximize=*/false);
4642 }
4643 return solver_->ConcatenateOperators(operators);
4644 }
4645
GetNeighborhoodOperators(const RoutingSearchParameters & search_parameters) const4646 LocalSearchOperator* RoutingModel::GetNeighborhoodOperators(
4647 const RoutingSearchParameters& search_parameters) const {
4648 std::vector<LocalSearchOperator*> operator_groups;
4649 std::vector<LocalSearchOperator*> operators = extra_operators_;
4650 if (!pickup_delivery_pairs_.empty()) {
4651 CP_ROUTING_PUSH_OPERATOR(RELOCATE_PAIR, relocate_pair, operators);
4652 // Only add the light version of relocate pair if the normal version has not
4653 // already been added as it covers a subset of its neighborhood.
4654 if (search_parameters.local_search_operators().use_relocate_pair() ==
4655 BOOL_FALSE) {
4656 CP_ROUTING_PUSH_OPERATOR(LIGHT_RELOCATE_PAIR, light_relocate_pair,
4657 operators);
4658 }
4659 CP_ROUTING_PUSH_OPERATOR(EXCHANGE_PAIR, exchange_pair, operators);
4660 CP_ROUTING_PUSH_OPERATOR(NODE_PAIR_SWAP, node_pair_swap_active, operators);
4661 CP_ROUTING_PUSH_OPERATOR(RELOCATE_SUBTRIP, relocate_subtrip, operators);
4662 CP_ROUTING_PUSH_OPERATOR(EXCHANGE_SUBTRIP, exchange_subtrip, operators);
4663 }
4664 if (vehicles_ > 1) {
4665 if (GetNumOfSingletonNodes() > 0) {
4666 // If there are only pairs in the model the only case where Relocate will
4667 // work is for intra-route moves, already covered by OrOpt.
4668 // We are not disabling Exchange and Cross because there are no
4669 // intra-route equivalents.
4670 CP_ROUTING_PUSH_OPERATOR(RELOCATE, relocate, operators);
4671 }
4672 CP_ROUTING_PUSH_OPERATOR(EXCHANGE, exchange, operators);
4673 CP_ROUTING_PUSH_OPERATOR(CROSS, cross, operators);
4674 }
4675 if (!pickup_delivery_pairs_.empty() ||
4676 search_parameters.local_search_operators().use_relocate_neighbors() ==
4677 BOOL_TRUE) {
4678 operators.push_back(local_search_operators_[RELOCATE_NEIGHBORS]);
4679 }
4680 const LocalSearchMetaheuristic::Value local_search_metaheuristic =
4681 search_parameters.local_search_metaheuristic();
4682 if (local_search_metaheuristic != LocalSearchMetaheuristic::TABU_SEARCH &&
4683 local_search_metaheuristic !=
4684 LocalSearchMetaheuristic::GENERIC_TABU_SEARCH &&
4685 local_search_metaheuristic !=
4686 LocalSearchMetaheuristic::SIMULATED_ANNEALING) {
4687 CP_ROUTING_PUSH_OPERATOR(LIN_KERNIGHAN, lin_kernighan, operators);
4688 }
4689 CP_ROUTING_PUSH_OPERATOR(TWO_OPT, two_opt, operators);
4690 CP_ROUTING_PUSH_OPERATOR(OR_OPT, or_opt, operators);
4691 CP_ROUTING_PUSH_OPERATOR(RELOCATE_EXPENSIVE_CHAIN, relocate_expensive_chain,
4692 operators);
4693 if (!disjunctions_.empty()) {
4694 CP_ROUTING_PUSH_OPERATOR(MAKE_INACTIVE, make_inactive, operators);
4695 CP_ROUTING_PUSH_OPERATOR(MAKE_CHAIN_INACTIVE, make_chain_inactive,
4696 operators);
4697 CP_ROUTING_PUSH_OPERATOR(MAKE_ACTIVE, make_active, operators);
4698
4699 // The relocate_and_make_active parameter activates all neighborhoods
4700 // relocating a node together with making another active.
4701 CP_ROUTING_PUSH_OPERATOR(RELOCATE_AND_MAKE_ACTIVE, relocate_and_make_active,
4702 operators);
4703 CP_ROUTING_PUSH_OPERATOR(MAKE_ACTIVE_AND_RELOCATE, relocate_and_make_active,
4704 operators);
4705
4706 CP_ROUTING_PUSH_OPERATOR(SWAP_ACTIVE, swap_active, operators);
4707 CP_ROUTING_PUSH_OPERATOR(EXTENDED_SWAP_ACTIVE, extended_swap_active,
4708 operators);
4709 }
4710 operator_groups.push_back(ConcatenateOperators(search_parameters, operators));
4711
4712 // Second local search loop: LNS-like operators.
4713 operators.clear();
4714 if (vehicles() > 1) {
4715 // NOTE: The following heuristic path LNS with a single vehicle are
4716 // equivalent to using the heuristic as first solution strategy, so we only
4717 // add these moves if we have at least 2 vehicles in the model.
4718 CP_ROUTING_PUSH_OPERATOR(GLOBAL_CHEAPEST_INSERTION_PATH_LNS,
4719 global_cheapest_insertion_path_lns, operators);
4720 CP_ROUTING_PUSH_OPERATOR(LOCAL_CHEAPEST_INSERTION_PATH_LNS,
4721 local_cheapest_insertion_path_lns, operators);
4722 CP_ROUTING_PUSH_OPERATOR(
4723 RELOCATE_PATH_GLOBAL_CHEAPEST_INSERTION_INSERT_UNPERFORMED,
4724 relocate_path_global_cheapest_insertion_insert_unperformed, operators);
4725 }
4726 CP_ROUTING_PUSH_OPERATOR(GLOBAL_CHEAPEST_INSERTION_EXPENSIVE_CHAIN_LNS,
4727 global_cheapest_insertion_expensive_chain_lns,
4728 operators);
4729 CP_ROUTING_PUSH_OPERATOR(LOCAL_CHEAPEST_INSERTION_EXPENSIVE_CHAIN_LNS,
4730 local_cheapest_insertion_expensive_chain_lns,
4731 operators);
4732 CP_ROUTING_PUSH_OPERATOR(GLOBAL_CHEAPEST_INSERTION_CLOSE_NODES_LNS,
4733 global_cheapest_insertion_close_nodes_lns,
4734 operators);
4735 CP_ROUTING_PUSH_OPERATOR(LOCAL_CHEAPEST_INSERTION_CLOSE_NODES_LNS,
4736 local_cheapest_insertion_close_nodes_lns, operators);
4737 operator_groups.push_back(ConcatenateOperators(search_parameters, operators));
4738
4739 // Third local search loop: Expensive LNS operators.
4740 operators.clear();
4741 if (local_search_metaheuristic != LocalSearchMetaheuristic::TABU_SEARCH &&
4742 local_search_metaheuristic !=
4743 LocalSearchMetaheuristic::GENERIC_TABU_SEARCH &&
4744 local_search_metaheuristic !=
4745 LocalSearchMetaheuristic::SIMULATED_ANNEALING) {
4746 CP_ROUTING_PUSH_OPERATOR(TSP_OPT, tsp_opt, operators);
4747 }
4748 if (local_search_metaheuristic != LocalSearchMetaheuristic::TABU_SEARCH &&
4749 local_search_metaheuristic !=
4750 LocalSearchMetaheuristic::GENERIC_TABU_SEARCH &&
4751 local_search_metaheuristic !=
4752 LocalSearchMetaheuristic::SIMULATED_ANNEALING) {
4753 CP_ROUTING_PUSH_OPERATOR(TSP_LNS, tsp_lns, operators);
4754 }
4755 CP_ROUTING_PUSH_OPERATOR(FULL_PATH_LNS, full_path_lns, operators);
4756 CP_ROUTING_PUSH_OPERATOR(PATH_LNS, path_lns, operators);
4757 if (!disjunctions_.empty()) {
4758 CP_ROUTING_PUSH_OPERATOR(INACTIVE_LNS, inactive_lns, operators);
4759 }
4760 operator_groups.push_back(ConcatenateOperators(search_parameters, operators));
4761
4762 return solver_->ConcatenateOperators(operator_groups);
4763 }
4764
4765 #undef CP_ROUTING_PUSH_OPERATOR
4766
HasUnaryDimension(const std::vector<RoutingDimension * > & dimensions)4767 bool HasUnaryDimension(const std::vector<RoutingDimension*>& dimensions) {
4768 for (const RoutingDimension* dimension : dimensions) {
4769 if (dimension->GetUnaryTransitEvaluator(0) != nullptr) return true;
4770 }
4771 return false;
4772 }
4773
4774 namespace {
4775
ConvertVectorInt64ToVectorInt(const std::vector<int64_t> & input,std::vector<int> * output)4776 void ConvertVectorInt64ToVectorInt(const std::vector<int64_t>& input,
4777 std::vector<int>* output) {
4778 const int n = input.size();
4779 output->resize(n);
4780 int* data = output->data();
4781 for (int i = 0; i < n; ++i) {
4782 const int element = static_cast<int>(input[i]);
4783 DCHECK_EQ(input[i], static_cast<int64_t>(element));
4784 data[i] = element;
4785 }
4786 }
4787
4788 } // namespace
4789
4790 std::vector<LocalSearchFilterManager::FilterEvent>
GetOrCreateLocalSearchFilters(const RoutingSearchParameters & parameters,bool filter_cost)4791 RoutingModel::GetOrCreateLocalSearchFilters(
4792 const RoutingSearchParameters& parameters, bool filter_cost) {
4793 const auto kAccept = LocalSearchFilterManager::FilterEventType::kAccept;
4794 const auto kRelax = LocalSearchFilterManager::FilterEventType::kRelax;
4795 // As of 2013/01, three filters evaluate sub-parts of the objective
4796 // function:
4797 // - NodeDisjunctionFilter: takes disjunction penalty costs into account,
4798 // - PathCumulFilter: takes dimension span costs into account,
4799 // - ObjectiveFilter:
4800 // - VehicleAmortizedCostFilter, which considers the part of the cost
4801 // related to amortized linear and quadratic vehicle cost factors.
4802 // - LocalSearchObjectiveFilter, which takes dimension "arc" costs into
4803 // account.
4804 std::vector<LocalSearchFilterManager::FilterEvent> filters;
4805 // VehicleAmortizedCostFilter can have a negative value, so it must be first.
4806 if (filter_cost && vehicle_amortized_cost_factors_set_) {
4807 filters.push_back({MakeVehicleAmortizedCostFilter(*this), kAccept});
4808 }
4809
4810 // The SumObjectiveFilter has the best reject/second ratio in practice,
4811 // so it is the earliest.
4812 if (filter_cost) {
4813 if (CostsAreHomogeneousAcrossVehicles()) {
4814 LocalSearchFilter* sum = solver_->MakeSumObjectiveFilter(
4815 nexts_,
4816 [this](int64_t i, int64_t j) { return GetHomogeneousCost(i, j); },
4817 Solver::LE);
4818 filters.push_back({sum, kAccept});
4819 } else {
4820 LocalSearchFilter* sum = solver_->MakeSumObjectiveFilter(
4821 nexts_, vehicle_vars_,
4822 [this](int64_t i, int64_t j, int64_t k) {
4823 return GetArcCostForVehicle(i, j, k);
4824 },
4825 Solver::LE);
4826 filters.push_back({sum, kAccept});
4827 }
4828 }
4829
4830 filters.push_back({solver_->MakeVariableDomainFilter(), kAccept});
4831
4832 if (vehicles_ > max_active_vehicles_) {
4833 filters.push_back({MakeMaxActiveVehiclesFilter(*this), kAccept});
4834 }
4835
4836 if (!disjunctions_.empty()) {
4837 if (filter_cost || HasMandatoryDisjunctions() ||
4838 HasMaxCardinalityConstrainedDisjunctions()) {
4839 filters.push_back(
4840 {MakeNodeDisjunctionFilter(*this, filter_cost), kAccept});
4841 }
4842 }
4843
4844 // If vehicle costs are not homogeneous, vehicle variables will be added to
4845 // local search deltas and their domain will be checked by
4846 // VariableDomainFilter.
4847 if (CostsAreHomogeneousAcrossVehicles()) {
4848 filters.push_back({MakeVehicleVarFilter(*this), kAccept});
4849 }
4850
4851 const PathState* path_state_reference = nullptr;
4852 if (HasUnaryDimension(GetDimensions())) {
4853 std::vector<int> path_starts;
4854 std::vector<int> path_ends;
4855 ConvertVectorInt64ToVectorInt(starts_, &path_starts);
4856 ConvertVectorInt64ToVectorInt(ends_, &path_ends);
4857
4858 auto path_state = absl::make_unique<PathState>(
4859 Size() + vehicles(), std::move(path_starts), std::move(path_ends));
4860 path_state_reference = path_state.get();
4861 filters.push_back(
4862 {MakePathStateFilter(solver_.get(), std::move(path_state), Nexts()),
4863 kRelax});
4864 AppendLightWeightDimensionFilters(path_state_reference, GetDimensions(),
4865 &filters);
4866 }
4867
4868 // As of 10/2021, TypeRegulationsFilter assumes pickup and delivery
4869 // constraints are enforced, therefore PickupDeliveryFilter must be
4870 // called first.
4871 if (!pickup_delivery_pairs_.empty()) {
4872 filters.push_back(
4873 {MakePickupDeliveryFilter(*this, pickup_delivery_pairs_,
4874 vehicle_pickup_delivery_policy_),
4875 kAccept});
4876 }
4877
4878 if (HasTypeRegulations()) {
4879 filters.push_back({MakeTypeRegulationsFilter(*this), kAccept});
4880 }
4881
4882 AppendDimensionCumulFilters(GetDimensions(), parameters, filter_cost,
4883 /* filter_light_weight_unary_dimensions */ false,
4884 &filters);
4885
4886 for (const RoutingDimension* dimension : dimensions_) {
4887 if (!dimension->HasBreakConstraints()) continue;
4888 filters.push_back({MakeVehicleBreaksFilter(*this, *dimension), kAccept});
4889 }
4890 filters.insert(filters.end(), extra_filters_.begin(), extra_filters_.end());
4891 return filters;
4892 }
4893
GetOrCreateLocalSearchFilterManager(const RoutingSearchParameters & parameters)4894 LocalSearchFilterManager* RoutingModel::GetOrCreateLocalSearchFilterManager(
4895 const RoutingSearchParameters& parameters) {
4896 if (!local_search_filter_manager_) {
4897 local_search_filter_manager_ =
4898 solver_->RevAlloc(new LocalSearchFilterManager(
4899 GetOrCreateLocalSearchFilters(parameters)));
4900 }
4901 return local_search_filter_manager_;
4902 }
4903
4904 std::vector<LocalSearchFilterManager::FilterEvent>
GetOrCreateFeasibilityFilters(const RoutingSearchParameters & parameters)4905 RoutingModel::GetOrCreateFeasibilityFilters(
4906 const RoutingSearchParameters& parameters) {
4907 return GetOrCreateLocalSearchFilters(parameters, false);
4908 }
4909
GetOrCreateFeasibilityFilterManager(const RoutingSearchParameters & parameters)4910 LocalSearchFilterManager* RoutingModel::GetOrCreateFeasibilityFilterManager(
4911 const RoutingSearchParameters& parameters) {
4912 if (!feasibility_filter_manager_) {
4913 feasibility_filter_manager_ =
4914 solver_->RevAlloc(new LocalSearchFilterManager(
4915 GetOrCreateFeasibilityFilters(parameters)));
4916 }
4917 return feasibility_filter_manager_;
4918 }
4919
4920 LocalSearchFilterManager*
GetOrCreateStrongFeasibilityFilterManager(const RoutingSearchParameters & parameters)4921 RoutingModel::GetOrCreateStrongFeasibilityFilterManager(
4922 const RoutingSearchParameters& parameters) {
4923 if (!strong_feasibility_filter_manager_) {
4924 std::vector<LocalSearchFilterManager::FilterEvent> filters =
4925 GetOrCreateFeasibilityFilters(parameters);
4926 filters.push_back({MakeCPFeasibilityFilter(this),
4927 LocalSearchFilterManager::FilterEventType::kAccept});
4928 strong_feasibility_filter_manager_ =
4929 solver_->RevAlloc(new LocalSearchFilterManager(std::move(filters)));
4930 }
4931 return strong_feasibility_filter_manager_;
4932 }
4933
4934 namespace {
AllTransitsPositive(const RoutingDimension & dimension)4935 bool AllTransitsPositive(const RoutingDimension& dimension) {
4936 for (int vehicle = 0; vehicle < dimension.model()->vehicles(); vehicle++) {
4937 if (!dimension.AreVehicleTransitsPositive(vehicle)) {
4938 return false;
4939 }
4940 }
4941 return true;
4942 }
4943 } // namespace
4944
StoreDimensionCumulOptimizers(const RoutingSearchParameters & parameters)4945 void RoutingModel::StoreDimensionCumulOptimizers(
4946 const RoutingSearchParameters& parameters) {
4947 Assignment* packed_dimensions_collector_assignment =
4948 solver_->MakeAssignment();
4949 packed_dimensions_collector_assignment->AddObjective(CostVar());
4950 const int num_dimensions = dimensions_.size();
4951 local_optimizer_index_.resize(num_dimensions, -1);
4952 global_optimizer_index_.resize(num_dimensions, -1);
4953 for (DimensionIndex dim = DimensionIndex(0); dim < num_dimensions; dim++) {
4954 RoutingDimension* dimension = dimensions_[dim];
4955 DCHECK_EQ(dimension->model(), this);
4956 const int num_resource_groups =
4957 GetDimensionResourceGroupIndices(dimension).size();
4958 bool needs_optimizer = false;
4959 if (dimension->global_span_cost_coefficient() > 0 ||
4960 !dimension->GetNodePrecedences().empty() || num_resource_groups > 1) {
4961 // Use global optimizer.
4962 needs_optimizer = true;
4963 global_optimizer_index_[dim] = global_dimension_optimizers_.size();
4964 global_dimension_optimizers_.push_back(
4965 absl::make_unique<GlobalDimensionCumulOptimizer>(
4966 dimension, parameters.continuous_scheduling_solver()));
4967 global_dimension_mp_optimizers_.push_back(
4968 absl::make_unique<GlobalDimensionCumulOptimizer>(
4969 dimension, parameters.mixed_integer_scheduling_solver()));
4970 if (!AllTransitsPositive(*dimension)) {
4971 dimension->SetOffsetForGlobalOptimizer(0);
4972 } else {
4973 int64_t offset =
4974 vehicles() == 0 ? 0 : std::numeric_limits<int64_t>::max();
4975 for (int vehicle = 0; vehicle < vehicles(); ++vehicle) {
4976 DCHECK_GE(dimension->CumulVar(Start(vehicle))->Min(), 0);
4977 offset =
4978 std::min(offset, dimension->CumulVar(Start(vehicle))->Min() - 1);
4979 }
4980 dimension->SetOffsetForGlobalOptimizer(std::max(Zero(), offset));
4981 }
4982 }
4983 // Check if we need the local optimizer.
4984 bool has_span_cost = false;
4985 bool has_span_limit = false;
4986 std::vector<int64_t> vehicle_offsets(vehicles());
4987 for (int vehicle = 0; vehicle < vehicles(); ++vehicle) {
4988 if (dimension->GetSpanCostCoefficientForVehicle(vehicle) > 0) {
4989 has_span_cost = true;
4990 }
4991 if (dimension->GetSpanUpperBoundForVehicle(vehicle) <
4992 std::numeric_limits<int64_t>::max()) {
4993 has_span_limit = true;
4994 }
4995 DCHECK_GE(dimension->CumulVar(Start(vehicle))->Min(), 0);
4996 vehicle_offsets[vehicle] =
4997 dimension->AreVehicleTransitsPositive(vehicle)
4998 ? std::max(Zero(), dimension->CumulVar(Start(vehicle))->Min() - 1)
4999 : 0;
5000 }
5001 bool has_soft_lower_bound = false;
5002 bool has_soft_upper_bound = false;
5003 for (int i = 0; i < dimension->cumuls().size(); ++i) {
5004 if (dimension->HasCumulVarSoftLowerBound(i)) {
5005 has_soft_lower_bound = true;
5006 }
5007 if (dimension->HasCumulVarSoftUpperBound(i)) {
5008 has_soft_upper_bound = true;
5009 }
5010 }
5011 int num_linear_constraints = 0;
5012 if (has_span_cost) ++num_linear_constraints;
5013 if (has_span_limit) ++num_linear_constraints;
5014 if (dimension->HasSoftSpanUpperBounds()) ++num_linear_constraints;
5015 if (has_soft_lower_bound) ++num_linear_constraints;
5016 if (has_soft_upper_bound) ++num_linear_constraints;
5017 if (dimension->HasBreakConstraints()) ++num_linear_constraints;
5018 if (num_resource_groups > 0 || num_linear_constraints >= 2) {
5019 needs_optimizer = true;
5020 dimension->SetVehicleOffsetsForLocalOptimizer(std::move(vehicle_offsets));
5021 local_optimizer_index_[dim] = local_dimension_optimizers_.size();
5022 local_dimension_optimizers_.push_back(
5023 absl::make_unique<LocalDimensionCumulOptimizer>(
5024 dimension, parameters.continuous_scheduling_solver()));
5025 local_dimension_mp_optimizers_.push_back(
5026 absl::make_unique<LocalDimensionCumulOptimizer>(
5027 dimension, parameters.mixed_integer_scheduling_solver()));
5028 }
5029 if (needs_optimizer) {
5030 packed_dimensions_collector_assignment->Add(dimension->cumuls());
5031 }
5032 }
5033
5034 // NOTE(b/129252839): We also add all other extra variables to the
5035 // packed_dimensions_collector_assignment to make sure the necessary
5036 // propagations on these variables after packing are correctly stored.
5037 for (IntVar* const extra_var : extra_vars_) {
5038 packed_dimensions_collector_assignment->Add(extra_var);
5039 }
5040 for (IntervalVar* const extra_interval : extra_intervals_) {
5041 packed_dimensions_collector_assignment->Add(extra_interval);
5042 }
5043
5044 packed_dimensions_assignment_collector_ = solver_->MakeFirstSolutionCollector(
5045 packed_dimensions_collector_assignment);
5046 }
5047
GetDimensionsWithSoftOrSpanCosts() const5048 std::vector<RoutingDimension*> RoutingModel::GetDimensionsWithSoftOrSpanCosts()
5049 const {
5050 std::vector<RoutingDimension*> dimensions;
5051 for (RoutingDimension* dimension : dimensions_) {
5052 bool has_soft_or_span_cost = false;
5053 for (int vehicle = 0; vehicle < vehicles(); ++vehicle) {
5054 if (dimension->GetSpanCostCoefficientForVehicle(vehicle) > 0) {
5055 has_soft_or_span_cost = true;
5056 break;
5057 }
5058 }
5059 if (!has_soft_or_span_cost) {
5060 for (int i = 0; i < dimension->cumuls().size(); ++i) {
5061 if (dimension->HasCumulVarSoftUpperBound(i) ||
5062 dimension->HasCumulVarSoftLowerBound(i)) {
5063 has_soft_or_span_cost = true;
5064 break;
5065 }
5066 }
5067 }
5068 if (has_soft_or_span_cost) dimensions.push_back(dimension);
5069 }
5070 return dimensions;
5071 }
5072
5073 DecisionBuilder*
CreateFinalizerForMinimizedAndMaximizedVariables()5074 RoutingModel::CreateFinalizerForMinimizedAndMaximizedVariables() {
5075 std::stable_sort(finalizer_variable_cost_pairs_.begin(),
5076 finalizer_variable_cost_pairs_.end(),
5077 [](const std::pair<IntVar*, int64_t>& var_cost1,
5078 const std::pair<IntVar*, int64_t>& var_cost2) {
5079 return var_cost1.second > var_cost2.second;
5080 });
5081 const int num_variables = finalizer_variable_cost_pairs_.size() +
5082 finalizer_variable_target_pairs_.size();
5083 std::vector<IntVar*> variables;
5084 std::vector<int64_t> targets;
5085 variables.reserve(num_variables);
5086 targets.reserve(num_variables);
5087 for (const auto& variable_cost : finalizer_variable_cost_pairs_) {
5088 variables.push_back(variable_cost.first);
5089 targets.push_back(std::numeric_limits<int64_t>::min());
5090 }
5091 for (const auto& variable_target : finalizer_variable_target_pairs_) {
5092 variables.push_back(variable_target.first);
5093 targets.push_back(variable_target.second);
5094 }
5095 return MakeSetValuesFromTargets(solver(), std::move(variables),
5096 std::move(targets));
5097 }
5098
CreateSolutionFinalizer(SearchLimit * lns_limit)5099 DecisionBuilder* RoutingModel::CreateSolutionFinalizer(SearchLimit* lns_limit) {
5100 std::vector<DecisionBuilder*> decision_builders;
5101 decision_builders.push_back(solver_->MakePhase(
5102 nexts_, Solver::CHOOSE_FIRST_UNBOUND, Solver::ASSIGN_MIN_VALUE));
5103 bool routes_are_interdependent = false;
5104 for (RoutingDimension* const dim : dimensions_) {
5105 if (!GetDimensionResourceGroupIndices(dim).empty() ||
5106 GetMutableGlobalCumulOptimizer(*dim) != nullptr) {
5107 routes_are_interdependent = true;
5108 break;
5109 }
5110 }
5111 if (!routes_are_interdependent) {
5112 // When routes are interdependent, optimal dimension values of unchanged
5113 // routes might be affected by changes on other routes, so we only add the
5114 // RestoreDimensionValuesForUnchangedRoutes decision builder when routes
5115 // aren't interdependent.
5116 decision_builders.push_back(
5117 MakeRestoreDimensionValuesForUnchangedRoutes(this));
5118 }
5119 if (!local_dimension_optimizers_.empty()) {
5120 decision_builders.push_back(
5121 solver_->RevAlloc(new SetCumulsFromLocalDimensionCosts(
5122 &local_dimension_optimizers_, &local_dimension_mp_optimizers_,
5123 [this](const RoutingDimension* dim) {
5124 // Don't set cumuls of dimensions with resources or having a
5125 // global optimizer.
5126 return GetDimensionResourceGroupIndices(dim).empty() &&
5127 GetMutableGlobalCumulOptimizer(*dim) == nullptr;
5128 },
5129 lns_limit)));
5130 }
5131 // Add a specific DB for setting cumuls of dimensions with a single resource
5132 // and no global optimizer.
5133 for (const RoutingDimension* const dim : dimensions_) {
5134 if (GetMutableGlobalCumulOptimizer(*dim) != nullptr) continue;
5135 DCHECK_LE(GetDimensionResourceGroupIndices(dim).size(), 1);
5136 if (GetDimensionResourceGroupIndices(dim).size() != 1) continue;
5137
5138 LocalDimensionCumulOptimizer* const optimizer =
5139 GetMutableLocalCumulOptimizer(*dim);
5140 DCHECK_NE(optimizer, nullptr);
5141 LocalDimensionCumulOptimizer* const mp_optimizer =
5142 GetMutableLocalCumulMPOptimizer(*dim);
5143 DCHECK_NE(mp_optimizer, nullptr);
5144 decision_builders.push_back(
5145 solver_->RevAlloc(new SetCumulsFromResourceAssignmentCosts(
5146 optimizer, mp_optimizer, lns_limit)));
5147 }
5148
5149 if (!global_dimension_optimizers_.empty()) {
5150 decision_builders.push_back(
5151 solver_->RevAlloc(new SetCumulsFromGlobalDimensionCosts(
5152 &global_dimension_optimizers_, &global_dimension_mp_optimizers_,
5153 lns_limit)));
5154 }
5155 decision_builders.push_back(
5156 CreateFinalizerForMinimizedAndMaximizedVariables());
5157
5158 return solver_->Compose(decision_builders);
5159 }
5160
CreateFirstSolutionDecisionBuilders(const RoutingSearchParameters & search_parameters)5161 void RoutingModel::CreateFirstSolutionDecisionBuilders(
5162 const RoutingSearchParameters& search_parameters) {
5163 first_solution_decision_builders_.resize(
5164 FirstSolutionStrategy_Value_Value_ARRAYSIZE, nullptr);
5165 first_solution_filtered_decision_builders_.resize(
5166 FirstSolutionStrategy_Value_Value_ARRAYSIZE, nullptr);
5167 DecisionBuilder* const finalize_solution =
5168 CreateSolutionFinalizer(GetOrCreateLargeNeighborhoodSearchLimit());
5169 // Default heuristic
5170 first_solution_decision_builders_
5171 [FirstSolutionStrategy::FIRST_UNBOUND_MIN_VALUE] = finalize_solution;
5172 // Global cheapest addition heuristic.
5173 first_solution_decision_builders_
5174 [FirstSolutionStrategy::GLOBAL_CHEAPEST_ARC] = solver_->MakePhase(
5175 nexts_,
5176 [this](int64_t i, int64_t j) {
5177 return GetArcCostForFirstSolution(i, j);
5178 },
5179 Solver::CHOOSE_STATIC_GLOBAL_BEST);
5180 // Cheapest addition heuristic.
5181 Solver::IndexEvaluator2 eval = [this](int64_t i, int64_t j) {
5182 return GetArcCostForFirstSolution(i, j);
5183 };
5184 first_solution_decision_builders_[FirstSolutionStrategy::LOCAL_CHEAPEST_ARC] =
5185 solver_->MakePhase(nexts_, Solver::CHOOSE_FIRST_UNBOUND, eval);
5186 // Path-based cheapest addition heuristic.
5187 first_solution_decision_builders_[FirstSolutionStrategy::PATH_CHEAPEST_ARC] =
5188 solver_->MakePhase(nexts_, Solver::CHOOSE_PATH, eval);
5189 if (!search_parameters.use_unfiltered_first_solution_strategy()) {
5190 first_solution_filtered_decision_builders_
5191 [FirstSolutionStrategy::PATH_CHEAPEST_ARC] =
5192 solver_->RevAlloc(new IntVarFilteredDecisionBuilder(
5193 absl::make_unique<EvaluatorCheapestAdditionFilteredHeuristic>(
5194 this,
5195 [this](int64_t i, int64_t j) {
5196 return GetArcCostForFirstSolution(i, j);
5197 },
5198 GetOrCreateFeasibilityFilterManager(search_parameters))));
5199 first_solution_decision_builders_
5200 [FirstSolutionStrategy::PATH_CHEAPEST_ARC] =
5201 solver_->Try(first_solution_filtered_decision_builders_
5202 [FirstSolutionStrategy::PATH_CHEAPEST_ARC],
5203 first_solution_decision_builders_
5204 [FirstSolutionStrategy::PATH_CHEAPEST_ARC]);
5205 }
5206 // Path-based most constrained arc addition heuristic.
5207 Solver::VariableValueComparator comp = [this](int64_t i, int64_t j,
5208 int64_t k) {
5209 return ArcIsMoreConstrainedThanArc(i, j, k);
5210 };
5211
5212 first_solution_decision_builders_
5213 [FirstSolutionStrategy::PATH_MOST_CONSTRAINED_ARC] =
5214 solver_->MakePhase(nexts_, Solver::CHOOSE_PATH, comp);
5215 if (!search_parameters.use_unfiltered_first_solution_strategy()) {
5216 first_solution_filtered_decision_builders_
5217 [FirstSolutionStrategy::PATH_MOST_CONSTRAINED_ARC] =
5218 solver_->RevAlloc(new IntVarFilteredDecisionBuilder(
5219 absl::make_unique<ComparatorCheapestAdditionFilteredHeuristic>(
5220 this, comp,
5221 GetOrCreateFeasibilityFilterManager(search_parameters))));
5222 first_solution_decision_builders_
5223 [FirstSolutionStrategy::PATH_MOST_CONSTRAINED_ARC] = solver_->Try(
5224 first_solution_filtered_decision_builders_
5225 [FirstSolutionStrategy::PATH_MOST_CONSTRAINED_ARC],
5226 first_solution_decision_builders_
5227 [FirstSolutionStrategy::PATH_MOST_CONSTRAINED_ARC]);
5228 }
5229 // Evaluator-based path heuristic.
5230 if (first_solution_evaluator_ != nullptr) {
5231 first_solution_decision_builders_
5232 [FirstSolutionStrategy::EVALUATOR_STRATEGY] = solver_->MakePhase(
5233 nexts_, Solver::CHOOSE_PATH, first_solution_evaluator_);
5234 } else {
5235 first_solution_decision_builders_
5236 [FirstSolutionStrategy::EVALUATOR_STRATEGY] = nullptr;
5237 }
5238 // All unperformed heuristic.
5239 first_solution_decision_builders_[FirstSolutionStrategy::ALL_UNPERFORMED] =
5240 MakeAllUnperformed(this);
5241 // Best insertion heuristic.
5242 RegularLimit* const ls_limit = solver_->MakeLimit(
5243 GetTimeLimit(search_parameters), std::numeric_limits<int64_t>::max(),
5244 std::numeric_limits<int64_t>::max(), std::numeric_limits<int64_t>::max(),
5245 /*smart_time_check=*/true);
5246 DecisionBuilder* const finalize = solver_->MakeSolveOnce(
5247 finalize_solution, GetOrCreateLargeNeighborhoodSearchLimit());
5248 LocalSearchPhaseParameters* const insertion_parameters =
5249 solver_->MakeLocalSearchPhaseParameters(
5250 nullptr, CreateInsertionOperator(), finalize, ls_limit,
5251 GetOrCreateLocalSearchFilterManager(search_parameters));
5252 std::vector<IntVar*> decision_vars = nexts_;
5253 if (!CostsAreHomogeneousAcrossVehicles()) {
5254 decision_vars.insert(decision_vars.end(), vehicle_vars_.begin(),
5255 vehicle_vars_.end());
5256 }
5257 const int64_t optimization_step = std::max(
5258 MathUtil::FastInt64Round(search_parameters.optimization_step()), One());
5259 first_solution_decision_builders_[FirstSolutionStrategy::BEST_INSERTION] =
5260 solver_->MakeNestedOptimize(
5261 solver_->MakeLocalSearchPhase(decision_vars, MakeAllUnperformed(this),
5262 insertion_parameters),
5263 GetOrCreateAssignment(), false, optimization_step);
5264 first_solution_decision_builders_[FirstSolutionStrategy::BEST_INSERTION] =
5265 solver_->Compose(first_solution_decision_builders_
5266 [FirstSolutionStrategy::BEST_INSERTION],
5267 finalize);
5268
5269 // Parallel/Sequential Global cheapest insertion
5270 GlobalCheapestInsertionFilteredHeuristic::GlobalCheapestInsertionParameters
5271 gci_parameters;
5272 gci_parameters.is_sequential = false;
5273 gci_parameters.farthest_seeds_ratio =
5274 search_parameters.cheapest_insertion_farthest_seeds_ratio();
5275 gci_parameters.neighbors_ratio =
5276 search_parameters.cheapest_insertion_first_solution_neighbors_ratio();
5277 gci_parameters.min_neighbors =
5278 search_parameters.cheapest_insertion_first_solution_min_neighbors();
5279 gci_parameters.use_neighbors_ratio_for_initialization =
5280 search_parameters
5281 .cheapest_insertion_first_solution_use_neighbors_ratio_for_initialization(); // NOLINT
5282 gci_parameters.add_unperformed_entries =
5283 search_parameters.cheapest_insertion_add_unperformed_entries();
5284 for (bool is_sequential : {false, true}) {
5285 FirstSolutionStrategy::Value first_solution_strategy =
5286 is_sequential ? FirstSolutionStrategy::SEQUENTIAL_CHEAPEST_INSERTION
5287 : FirstSolutionStrategy::PARALLEL_CHEAPEST_INSERTION;
5288 gci_parameters.is_sequential = is_sequential;
5289
5290 first_solution_filtered_decision_builders_[first_solution_strategy] =
5291 solver_->RevAlloc(new IntVarFilteredDecisionBuilder(
5292 absl::make_unique<GlobalCheapestInsertionFilteredHeuristic>(
5293 this,
5294 [this](int64_t i, int64_t j, int64_t vehicle) {
5295 return GetArcCostForVehicle(i, j, vehicle);
5296 },
5297 [this](int64_t i) { return UnperformedPenaltyOrValue(0, i); },
5298 GetOrCreateFeasibilityFilterManager(search_parameters),
5299 gci_parameters)));
5300 IntVarFilteredDecisionBuilder* const strong_gci =
5301 solver_->RevAlloc(new IntVarFilteredDecisionBuilder(
5302 absl::make_unique<GlobalCheapestInsertionFilteredHeuristic>(
5303 this,
5304 [this](int64_t i, int64_t j, int64_t vehicle) {
5305 return GetArcCostForVehicle(i, j, vehicle);
5306 },
5307 [this](int64_t i) { return UnperformedPenaltyOrValue(0, i); },
5308 GetOrCreateStrongFeasibilityFilterManager(search_parameters),
5309 gci_parameters)));
5310 first_solution_decision_builders_[first_solution_strategy] = solver_->Try(
5311 first_solution_filtered_decision_builders_[first_solution_strategy],
5312 solver_->Try(strong_gci, first_solution_decision_builders_
5313 [FirstSolutionStrategy::BEST_INSERTION]));
5314 }
5315
5316 // Local cheapest insertion
5317 first_solution_filtered_decision_builders_
5318 [FirstSolutionStrategy::LOCAL_CHEAPEST_INSERTION] =
5319 solver_->RevAlloc(new IntVarFilteredDecisionBuilder(
5320 absl::make_unique<LocalCheapestInsertionFilteredHeuristic>(
5321 this,
5322 [this](int64_t i, int64_t j, int64_t vehicle) {
5323 return GetArcCostForVehicle(i, j, vehicle);
5324 },
5325 GetOrCreateFeasibilityFilterManager(search_parameters))));
5326 IntVarFilteredDecisionBuilder* const strong_lci =
5327 solver_->RevAlloc(new IntVarFilteredDecisionBuilder(
5328 absl::make_unique<LocalCheapestInsertionFilteredHeuristic>(
5329 this,
5330 [this](int64_t i, int64_t j, int64_t vehicle) {
5331 return GetArcCostForVehicle(i, j, vehicle);
5332 },
5333 GetOrCreateStrongFeasibilityFilterManager(search_parameters))));
5334 first_solution_decision_builders_
5335 [FirstSolutionStrategy::LOCAL_CHEAPEST_INSERTION] = solver_->Try(
5336 first_solution_filtered_decision_builders_
5337 [FirstSolutionStrategy::LOCAL_CHEAPEST_INSERTION],
5338 solver_->Try(strong_lci,
5339 first_solution_decision_builders_
5340 [FirstSolutionStrategy::BEST_INSERTION]));
5341 // Savings
5342 SavingsFilteredHeuristic::SavingsParameters savings_parameters;
5343 savings_parameters.neighbors_ratio =
5344 search_parameters.savings_neighbors_ratio();
5345 savings_parameters.max_memory_usage_bytes =
5346 search_parameters.savings_max_memory_usage_bytes();
5347 savings_parameters.add_reverse_arcs =
5348 search_parameters.savings_add_reverse_arcs();
5349 savings_parameters.arc_coefficient =
5350 search_parameters.savings_arc_coefficient();
5351 LocalSearchFilterManager* filter_manager = nullptr;
5352 if (!search_parameters.use_unfiltered_first_solution_strategy()) {
5353 filter_manager = GetOrCreateFeasibilityFilterManager(search_parameters);
5354 }
5355
5356 if (search_parameters.savings_parallel_routes()) {
5357 IntVarFilteredDecisionBuilder* savings_db =
5358 solver_->RevAlloc(new IntVarFilteredDecisionBuilder(
5359 absl::make_unique<ParallelSavingsFilteredHeuristic>(
5360 this, savings_parameters, filter_manager)));
5361 if (!search_parameters.use_unfiltered_first_solution_strategy()) {
5362 first_solution_filtered_decision_builders_
5363 [FirstSolutionStrategy::SAVINGS] = savings_db;
5364 }
5365
5366 first_solution_decision_builders_[FirstSolutionStrategy::SAVINGS] =
5367 solver_->Try(savings_db,
5368 solver_->RevAlloc(new IntVarFilteredDecisionBuilder(
5369 absl::make_unique<ParallelSavingsFilteredHeuristic>(
5370 this, savings_parameters,
5371 GetOrCreateStrongFeasibilityFilterManager(
5372 search_parameters)))));
5373 } else {
5374 IntVarFilteredDecisionBuilder* savings_db =
5375 solver_->RevAlloc(new IntVarFilteredDecisionBuilder(
5376 absl::make_unique<SequentialSavingsFilteredHeuristic>(
5377 this, savings_parameters, filter_manager)));
5378 if (!search_parameters.use_unfiltered_first_solution_strategy()) {
5379 first_solution_filtered_decision_builders_
5380 [FirstSolutionStrategy::SAVINGS] = savings_db;
5381 }
5382
5383 first_solution_decision_builders_[FirstSolutionStrategy::SAVINGS] =
5384 solver_->Try(savings_db,
5385 solver_->RevAlloc(new IntVarFilteredDecisionBuilder(
5386 absl::make_unique<SequentialSavingsFilteredHeuristic>(
5387 this, savings_parameters,
5388 GetOrCreateStrongFeasibilityFilterManager(
5389 search_parameters)))));
5390 }
5391 // Sweep
5392 first_solution_decision_builders_[FirstSolutionStrategy::SWEEP] =
5393 MakeSweepDecisionBuilder(this, true);
5394 DecisionBuilder* sweep_builder = MakeSweepDecisionBuilder(this, false);
5395 first_solution_decision_builders_[FirstSolutionStrategy::SWEEP] =
5396 solver_->Try(
5397 sweep_builder,
5398 first_solution_decision_builders_[FirstSolutionStrategy::SWEEP]);
5399 // Christofides
5400 first_solution_decision_builders_[FirstSolutionStrategy::CHRISTOFIDES] =
5401 solver_->RevAlloc(new IntVarFilteredDecisionBuilder(
5402 absl::make_unique<ChristofidesFilteredHeuristic>(
5403 this, GetOrCreateFeasibilityFilterManager(search_parameters),
5404 search_parameters.christofides_use_minimum_matching())));
5405 // Automatic
5406 const bool has_precedences = std::any_of(
5407 dimensions_.begin(), dimensions_.end(),
5408 [](RoutingDimension* dim) { return !dim->GetNodePrecedences().empty(); });
5409 bool has_single_vehicle_node = false;
5410 for (int node = 0; node < Size(); node++) {
5411 if (!IsStart(node) && !IsEnd(node) && allowed_vehicles_[node].size() == 1) {
5412 has_single_vehicle_node = true;
5413 break;
5414 }
5415 }
5416 automatic_first_solution_strategy_ =
5417 AutomaticFirstSolutionStrategy(!pickup_delivery_pairs_.empty(),
5418 has_precedences, has_single_vehicle_node);
5419 first_solution_decision_builders_[FirstSolutionStrategy::AUTOMATIC] =
5420 first_solution_decision_builders_[automatic_first_solution_strategy_];
5421 first_solution_decision_builders_[FirstSolutionStrategy::UNSET] =
5422 first_solution_decision_builders_[FirstSolutionStrategy::AUTOMATIC];
5423
5424 // Naming decision builders to clarify profiling.
5425 for (FirstSolutionStrategy_Value strategy =
5426 FirstSolutionStrategy_Value_Value_MIN;
5427 strategy <= FirstSolutionStrategy_Value_Value_MAX;
5428 strategy = FirstSolutionStrategy_Value(strategy + 1)) {
5429 if (first_solution_decision_builders_[strategy] == nullptr ||
5430 strategy == FirstSolutionStrategy::AUTOMATIC) {
5431 continue;
5432 }
5433 const std::string strategy_name =
5434 FirstSolutionStrategy_Value_Name(strategy);
5435 const std::string& log_tag = search_parameters.log_tag();
5436 if (!log_tag.empty() && log_tag != strategy_name) {
5437 first_solution_decision_builders_[strategy]->set_name(absl::StrFormat(
5438 "%s / %s", strategy_name, search_parameters.log_tag()));
5439 } else {
5440 first_solution_decision_builders_[strategy]->set_name(strategy_name);
5441 }
5442 }
5443 }
5444
GetFirstSolutionDecisionBuilder(const RoutingSearchParameters & search_parameters) const5445 DecisionBuilder* RoutingModel::GetFirstSolutionDecisionBuilder(
5446 const RoutingSearchParameters& search_parameters) const {
5447 const FirstSolutionStrategy::Value first_solution_strategy =
5448 search_parameters.first_solution_strategy();
5449 if (first_solution_strategy < first_solution_decision_builders_.size()) {
5450 return first_solution_decision_builders_[first_solution_strategy];
5451 } else {
5452 return nullptr;
5453 }
5454 }
5455
5456 IntVarFilteredDecisionBuilder*
GetFilteredFirstSolutionDecisionBuilderOrNull(const RoutingSearchParameters & search_parameters) const5457 RoutingModel::GetFilteredFirstSolutionDecisionBuilderOrNull(
5458 const RoutingSearchParameters& search_parameters) const {
5459 const FirstSolutionStrategy::Value first_solution_strategy =
5460 search_parameters.first_solution_strategy();
5461 return first_solution_filtered_decision_builders_[first_solution_strategy];
5462 }
5463
CreateLocalSearchParameters(const RoutingSearchParameters & search_parameters)5464 LocalSearchPhaseParameters* RoutingModel::CreateLocalSearchParameters(
5465 const RoutingSearchParameters& search_parameters) {
5466 SearchLimit* lns_limit = GetOrCreateLargeNeighborhoodSearchLimit();
5467 return solver_->MakeLocalSearchPhaseParameters(
5468 CostVar(), GetNeighborhoodOperators(search_parameters),
5469 solver_->MakeSolveOnce(CreateSolutionFinalizer(lns_limit), lns_limit),
5470 GetOrCreateLocalSearchLimit(),
5471 GetOrCreateLocalSearchFilterManager(search_parameters));
5472 }
5473
CreateLocalSearchDecisionBuilder(const RoutingSearchParameters & search_parameters)5474 DecisionBuilder* RoutingModel::CreateLocalSearchDecisionBuilder(
5475 const RoutingSearchParameters& search_parameters) {
5476 const int size = Size();
5477 DecisionBuilder* first_solution =
5478 GetFirstSolutionDecisionBuilder(search_parameters);
5479 LocalSearchPhaseParameters* const parameters =
5480 CreateLocalSearchParameters(search_parameters);
5481 SearchLimit* first_solution_lns_limit =
5482 GetOrCreateFirstSolutionLargeNeighborhoodSearchLimit();
5483 DecisionBuilder* const first_solution_sub_decision_builder =
5484 solver_->MakeSolveOnce(CreateSolutionFinalizer(first_solution_lns_limit),
5485 first_solution_lns_limit);
5486 if (CostsAreHomogeneousAcrossVehicles()) {
5487 return solver_->MakeLocalSearchPhase(nexts_, first_solution,
5488 first_solution_sub_decision_builder,
5489 parameters);
5490 } else {
5491 const int all_size = size + size + vehicles_;
5492 std::vector<IntVar*> all_vars(all_size);
5493 for (int i = 0; i < size; ++i) {
5494 all_vars[i] = nexts_[i];
5495 }
5496 for (int i = size; i < all_size; ++i) {
5497 all_vars[i] = vehicle_vars_[i - size];
5498 }
5499 return solver_->MakeLocalSearchPhase(all_vars, first_solution,
5500 first_solution_sub_decision_builder,
5501 parameters);
5502 }
5503 }
5504
SetupDecisionBuilders(const RoutingSearchParameters & search_parameters)5505 void RoutingModel::SetupDecisionBuilders(
5506 const RoutingSearchParameters& search_parameters) {
5507 if (search_parameters.use_depth_first_search()) {
5508 SearchLimit* first_lns_limit =
5509 GetOrCreateFirstSolutionLargeNeighborhoodSearchLimit();
5510 solve_db_ = solver_->Compose(
5511 GetFirstSolutionDecisionBuilder(search_parameters),
5512 solver_->MakeSolveOnce(CreateSolutionFinalizer(first_lns_limit),
5513 first_lns_limit));
5514 } else {
5515 solve_db_ = CreateLocalSearchDecisionBuilder(search_parameters);
5516 }
5517 CHECK(preassignment_ != nullptr);
5518 DecisionBuilder* restore_preassignment =
5519 solver_->MakeRestoreAssignment(preassignment_);
5520 solve_db_ = solver_->Compose(restore_preassignment, solve_db_);
5521 improve_db_ =
5522 solver_->Compose(restore_preassignment,
5523 solver_->MakeLocalSearchPhase(
5524 GetOrCreateAssignment(),
5525 CreateLocalSearchParameters(search_parameters)));
5526 restore_assignment_ = solver_->Compose(
5527 solver_->MakeRestoreAssignment(GetOrCreateAssignment()),
5528 CreateSolutionFinalizer(GetOrCreateLargeNeighborhoodSearchLimit()));
5529 restore_tmp_assignment_ = solver_->Compose(
5530 restore_preassignment,
5531 solver_->MakeRestoreAssignment(GetOrCreateTmpAssignment()),
5532 CreateSolutionFinalizer(GetOrCreateLargeNeighborhoodSearchLimit()));
5533 }
5534
SetupMetaheuristics(const RoutingSearchParameters & search_parameters)5535 void RoutingModel::SetupMetaheuristics(
5536 const RoutingSearchParameters& search_parameters) {
5537 SearchMonitor* optimize;
5538 const LocalSearchMetaheuristic::Value metaheuristic =
5539 search_parameters.local_search_metaheuristic();
5540 // Some metaheuristics will effectively never terminate; warn
5541 // user if they fail to set a time limit.
5542 bool limit_too_long =
5543 !search_parameters.has_time_limit() &&
5544 search_parameters.solution_limit() == std::numeric_limits<int64_t>::max();
5545 const int64_t optimization_step = std::max(
5546 MathUtil::FastInt64Round(search_parameters.optimization_step()), One());
5547 switch (metaheuristic) {
5548 case LocalSearchMetaheuristic::GUIDED_LOCAL_SEARCH:
5549 if (CostsAreHomogeneousAcrossVehicles()) {
5550 optimize = solver_->MakeGuidedLocalSearch(
5551 false, cost_,
5552 [this](int64_t i, int64_t j) { return GetHomogeneousCost(i, j); },
5553 optimization_step, nexts_,
5554 search_parameters.guided_local_search_lambda_coefficient());
5555 } else {
5556 optimize = solver_->MakeGuidedLocalSearch(
5557 false, cost_,
5558 [this](int64_t i, int64_t j, int64_t k) {
5559 return GetArcCostForVehicle(i, j, k);
5560 },
5561 optimization_step, nexts_, vehicle_vars_,
5562 search_parameters.guided_local_search_lambda_coefficient());
5563 }
5564 break;
5565 case LocalSearchMetaheuristic::SIMULATED_ANNEALING:
5566 optimize =
5567 solver_->MakeSimulatedAnnealing(false, cost_, optimization_step, 100);
5568 break;
5569 case LocalSearchMetaheuristic::TABU_SEARCH:
5570 optimize = solver_->MakeTabuSearch(false, cost_, optimization_step,
5571 nexts_, 10, 10, .8);
5572 break;
5573 case LocalSearchMetaheuristic::GENERIC_TABU_SEARCH: {
5574 std::vector<operations_research::IntVar*> tabu_vars;
5575 if (tabu_var_callback_) {
5576 tabu_vars = tabu_var_callback_(this);
5577 } else {
5578 tabu_vars.push_back(cost_);
5579 }
5580 optimize = solver_->MakeGenericTabuSearch(false, cost_, optimization_step,
5581 tabu_vars, 100);
5582 break;
5583 }
5584 default:
5585 limit_too_long = false;
5586 optimize = solver_->MakeMinimize(cost_, optimization_step);
5587 }
5588 if (limit_too_long) {
5589 LOG(WARNING) << LocalSearchMetaheuristic::Value_Name(metaheuristic)
5590 << " specified without sane timeout: solve may run forever.";
5591 }
5592 monitors_.push_back(optimize);
5593 }
5594
SetTabuVarsCallback(GetTabuVarsCallback tabu_var_callback)5595 void RoutingModel::SetTabuVarsCallback(GetTabuVarsCallback tabu_var_callback) {
5596 tabu_var_callback_ = std::move(tabu_var_callback);
5597 }
5598
SetupAssignmentCollector(const RoutingSearchParameters & search_parameters)5599 void RoutingModel::SetupAssignmentCollector(
5600 const RoutingSearchParameters& search_parameters) {
5601 Assignment* full_assignment = solver_->MakeAssignment();
5602 for (const RoutingDimension* const dimension : dimensions_) {
5603 full_assignment->Add(dimension->cumuls());
5604 }
5605 for (IntVar* const extra_var : extra_vars_) {
5606 full_assignment->Add(extra_var);
5607 }
5608 for (IntervalVar* const extra_interval : extra_intervals_) {
5609 full_assignment->Add(extra_interval);
5610 }
5611 full_assignment->Add(nexts_);
5612 full_assignment->Add(active_);
5613 full_assignment->Add(vehicle_vars_);
5614 full_assignment->AddObjective(cost_);
5615
5616 collect_assignments_ = solver_->MakeNBestValueSolutionCollector(
5617 full_assignment, search_parameters.number_of_solutions_to_collect(),
5618 false);
5619 collect_one_assignment_ =
5620 solver_->MakeFirstSolutionCollector(full_assignment);
5621 monitors_.push_back(collect_assignments_);
5622 }
5623
SetupTrace(const RoutingSearchParameters & search_parameters)5624 void RoutingModel::SetupTrace(
5625 const RoutingSearchParameters& search_parameters) {
5626 if (search_parameters.log_search()) {
5627 Solver::SearchLogParameters search_log_parameters;
5628 search_log_parameters.branch_period = 10000;
5629 search_log_parameters.objective = nullptr;
5630 search_log_parameters.variable = cost_;
5631 search_log_parameters.scaling_factor =
5632 search_parameters.log_cost_scaling_factor();
5633 search_log_parameters.offset = search_parameters.log_cost_offset();
5634 if (!search_parameters.log_tag().empty()) {
5635 const std::string& tag = search_parameters.log_tag();
5636 search_log_parameters.display_callback = [tag]() { return tag; };
5637 } else {
5638 search_log_parameters.display_callback = nullptr;
5639 }
5640 search_log_parameters.display_on_new_solutions_only = false;
5641 monitors_.push_back(solver_->MakeSearchLog(search_log_parameters));
5642 }
5643 }
5644
SetupImprovementLimit(const RoutingSearchParameters & search_parameters)5645 void RoutingModel::SetupImprovementLimit(
5646 const RoutingSearchParameters& search_parameters) {
5647 if (search_parameters.has_improvement_limit_parameters()) {
5648 monitors_.push_back(solver_->MakeImprovementLimit(
5649 cost_, /*maximize=*/false, search_parameters.log_cost_scaling_factor(),
5650 search_parameters.log_cost_offset(),
5651 search_parameters.improvement_limit_parameters()
5652 .improvement_rate_coefficient(),
5653 search_parameters.improvement_limit_parameters()
5654 .improvement_rate_solutions_distance()));
5655 }
5656 }
5657
SetupSearchMonitors(const RoutingSearchParameters & search_parameters)5658 void RoutingModel::SetupSearchMonitors(
5659 const RoutingSearchParameters& search_parameters) {
5660 monitors_.push_back(GetOrCreateLimit());
5661 SetupImprovementLimit(search_parameters);
5662 SetupMetaheuristics(search_parameters);
5663 SetupAssignmentCollector(search_parameters);
5664 SetupTrace(search_parameters);
5665 }
5666
UsesLightPropagation(const RoutingSearchParameters & search_parameters) const5667 bool RoutingModel::UsesLightPropagation(
5668 const RoutingSearchParameters& search_parameters) const {
5669 return !search_parameters.use_full_propagation() &&
5670 !search_parameters.use_depth_first_search() &&
5671 search_parameters.first_solution_strategy() !=
5672 FirstSolutionStrategy::FIRST_UNBOUND_MIN_VALUE;
5673 }
5674
AddWeightedVariableMinimizedByFinalizer(IntVar * var,int64_t cost)5675 void RoutingModel::AddWeightedVariableMinimizedByFinalizer(IntVar* var,
5676 int64_t cost) {
5677 CHECK(var != nullptr);
5678 const int index = gtl::LookupOrInsert(&finalizer_variable_cost_index_, var,
5679 finalizer_variable_cost_pairs_.size());
5680 if (index < finalizer_variable_cost_pairs_.size()) {
5681 const int64_t old_cost = finalizer_variable_cost_pairs_[index].second;
5682 finalizer_variable_cost_pairs_[index].second = CapAdd(old_cost, cost);
5683 } else {
5684 finalizer_variable_cost_pairs_.emplace_back(var, cost);
5685 }
5686 }
5687
AddVariableTargetToFinalizer(IntVar * var,int64_t target)5688 void RoutingModel::AddVariableTargetToFinalizer(IntVar* var, int64_t target) {
5689 CHECK(var != nullptr);
5690 if (finalizer_variable_target_set_.contains(var)) return;
5691 finalizer_variable_target_set_.insert(var);
5692 finalizer_variable_target_pairs_.emplace_back(var, target);
5693 }
5694
AddVariableMaximizedByFinalizer(IntVar * var)5695 void RoutingModel::AddVariableMaximizedByFinalizer(IntVar* var) {
5696 AddVariableTargetToFinalizer(var, std::numeric_limits<int64_t>::max());
5697 }
5698
AddVariableMinimizedByFinalizer(IntVar * var)5699 void RoutingModel::AddVariableMinimizedByFinalizer(IntVar* var) {
5700 AddVariableTargetToFinalizer(var, std::numeric_limits<int64_t>::min());
5701 }
5702
SetupSearch(const RoutingSearchParameters & search_parameters)5703 void RoutingModel::SetupSearch(
5704 const RoutingSearchParameters& search_parameters) {
5705 SetupDecisionBuilders(search_parameters);
5706 SetupSearchMonitors(search_parameters);
5707 }
5708
AddToAssignment(IntVar * const var)5709 void RoutingModel::AddToAssignment(IntVar* const var) {
5710 extra_vars_.push_back(var);
5711 }
5712
AddIntervalToAssignment(IntervalVar * const interval)5713 void RoutingModel::AddIntervalToAssignment(IntervalVar* const interval) {
5714 extra_intervals_.push_back(interval);
5715 }
5716
5717 namespace {
5718
5719 class PathSpansAndTotalSlacks : public Constraint {
5720 public:
PathSpansAndTotalSlacks(const RoutingModel * model,const RoutingDimension * dimension,std::vector<IntVar * > spans,std::vector<IntVar * > total_slacks)5721 PathSpansAndTotalSlacks(const RoutingModel* model,
5722 const RoutingDimension* dimension,
5723 std::vector<IntVar*> spans,
5724 std::vector<IntVar*> total_slacks)
5725 : Constraint(model->solver()),
5726 model_(model),
5727 dimension_(dimension),
5728 spans_(std::move(spans)),
5729 total_slacks_(std::move(total_slacks)) {
5730 CHECK_EQ(spans_.size(), model_->vehicles());
5731 CHECK_EQ(total_slacks_.size(), model_->vehicles());
5732 vehicle_demons_.resize(model_->vehicles());
5733 }
5734
DebugString() const5735 std::string DebugString() const override { return "PathSpansAndTotalSlacks"; }
5736
Post()5737 void Post() override {
5738 const int num_nodes = model_->VehicleVars().size();
5739 const int num_transits = model_->Nexts().size();
5740 for (int node = 0; node < num_nodes; ++node) {
5741 auto* demon = MakeConstraintDemon1(
5742 model_->solver(), this, &PathSpansAndTotalSlacks::PropagateNode,
5743 "PathSpansAndTotalSlacks::PropagateNode", node);
5744 dimension_->CumulVar(node)->WhenRange(demon);
5745 model_->VehicleVar(node)->WhenBound(demon);
5746 if (node < num_transits) {
5747 dimension_->TransitVar(node)->WhenRange(demon);
5748 dimension_->FixedTransitVar(node)->WhenBound(demon);
5749 model_->NextVar(node)->WhenBound(demon);
5750 }
5751 }
5752 for (int vehicle = 0; vehicle < spans_.size(); ++vehicle) {
5753 if (!spans_[vehicle] && !total_slacks_[vehicle]) continue;
5754 auto* demon = MakeDelayedConstraintDemon1(
5755 solver(), this, &PathSpansAndTotalSlacks::PropagateVehicle,
5756 "PathSpansAndTotalSlacks::PropagateVehicle", vehicle);
5757 vehicle_demons_[vehicle] = demon;
5758 if (spans_[vehicle]) spans_[vehicle]->WhenRange(demon);
5759 if (total_slacks_[vehicle]) total_slacks_[vehicle]->WhenRange(demon);
5760 if (dimension_->HasBreakConstraints()) {
5761 for (IntervalVar* b : dimension_->GetBreakIntervalsOfVehicle(vehicle)) {
5762 b->WhenAnything(demon);
5763 }
5764 }
5765 }
5766 }
5767
5768 // Call propagator on all vehicles.
InitialPropagate()5769 void InitialPropagate() override {
5770 for (int vehicle = 0; vehicle < spans_.size(); ++vehicle) {
5771 if (!spans_[vehicle] && !total_slacks_[vehicle]) continue;
5772 PropagateVehicle(vehicle);
5773 }
5774 }
5775
5776 private:
5777 // Called when a path/dimension variables of the node changes,
5778 // this delays propagator calls until path variables (Next and VehicleVar)
5779 // are instantiated, which saves fruitless and multiple identical calls.
PropagateNode(int node)5780 void PropagateNode(int node) {
5781 if (!model_->VehicleVar(node)->Bound()) return;
5782 const int vehicle = model_->VehicleVar(node)->Min();
5783 if (vehicle < 0 || vehicle_demons_[vehicle] == nullptr) return;
5784 EnqueueDelayedDemon(vehicle_demons_[vehicle]);
5785 }
5786
5787 // In order to make reasoning on span and total_slack of a vehicle uniform,
5788 // we rely on the fact that span == sum_fixed_transits + total_slack
5789 // to present both span and total_slack in terms of span and fixed transit.
5790 // This allows to use the same code whether there actually are variables
5791 // for span and total_slack or not.
SpanMin(int vehicle,int64_t sum_fixed_transits)5792 int64_t SpanMin(int vehicle, int64_t sum_fixed_transits) {
5793 DCHECK_GE(sum_fixed_transits, 0);
5794 const int64_t span_min = spans_[vehicle]
5795 ? spans_[vehicle]->Min()
5796 : std::numeric_limits<int64_t>::max();
5797 const int64_t total_slack_min = total_slacks_[vehicle]
5798 ? total_slacks_[vehicle]->Min()
5799 : std::numeric_limits<int64_t>::max();
5800 return std::min(span_min, CapAdd(total_slack_min, sum_fixed_transits));
5801 }
SpanMax(int vehicle,int64_t sum_fixed_transits)5802 int64_t SpanMax(int vehicle, int64_t sum_fixed_transits) {
5803 DCHECK_GE(sum_fixed_transits, 0);
5804 const int64_t span_max = spans_[vehicle]
5805 ? spans_[vehicle]->Max()
5806 : std::numeric_limits<int64_t>::min();
5807 const int64_t total_slack_max = total_slacks_[vehicle]
5808 ? total_slacks_[vehicle]->Max()
5809 : std::numeric_limits<int64_t>::min();
5810 return std::max(span_max, CapAdd(total_slack_max, sum_fixed_transits));
5811 }
SetSpanMin(int vehicle,int64_t min,int64_t sum_fixed_transits)5812 void SetSpanMin(int vehicle, int64_t min, int64_t sum_fixed_transits) {
5813 DCHECK_GE(sum_fixed_transits, 0);
5814 if (spans_[vehicle]) {
5815 spans_[vehicle]->SetMin(min);
5816 }
5817 if (total_slacks_[vehicle]) {
5818 total_slacks_[vehicle]->SetMin(CapSub(min, sum_fixed_transits));
5819 }
5820 }
SetSpanMax(int vehicle,int64_t max,int64_t sum_fixed_transits)5821 void SetSpanMax(int vehicle, int64_t max, int64_t sum_fixed_transits) {
5822 DCHECK_GE(sum_fixed_transits, 0);
5823 if (spans_[vehicle]) {
5824 spans_[vehicle]->SetMax(max);
5825 }
5826 if (total_slacks_[vehicle]) {
5827 total_slacks_[vehicle]->SetMax(CapSub(max, sum_fixed_transits));
5828 }
5829 }
5830 // Propagates span == sum_fixed_transits + total_slack.
5831 // This should be called at least once during PropagateVehicle().
SynchronizeSpanAndTotalSlack(int vehicle,int64_t sum_fixed_transits)5832 void SynchronizeSpanAndTotalSlack(int vehicle, int64_t sum_fixed_transits) {
5833 DCHECK_GE(sum_fixed_transits, 0);
5834 IntVar* span = spans_[vehicle];
5835 IntVar* total_slack = total_slacks_[vehicle];
5836 if (!span || !total_slack) return;
5837 span->SetMin(CapAdd(total_slack->Min(), sum_fixed_transits));
5838 span->SetMax(CapAdd(total_slack->Max(), sum_fixed_transits));
5839 total_slack->SetMin(CapSub(span->Min(), sum_fixed_transits));
5840 total_slack->SetMax(CapSub(span->Max(), sum_fixed_transits));
5841 }
5842
PropagateVehicle(int vehicle)5843 void PropagateVehicle(int vehicle) {
5844 DCHECK(spans_[vehicle] || total_slacks_[vehicle]);
5845 const int start = model_->Start(vehicle);
5846 const int end = model_->End(vehicle);
5847 // Record path, if it is not fixed from start to end, stop here.
5848 // TRICKY: do not put end node yet, we look only at transits in the next
5849 // reasonings, we will append the end when we look at cumuls.
5850 {
5851 path_.clear();
5852 int curr_node = start;
5853 while (!model_->IsEnd(curr_node)) {
5854 const IntVar* next_var = model_->NextVar(curr_node);
5855 if (!next_var->Bound()) return;
5856 path_.push_back(curr_node);
5857 curr_node = next_var->Value();
5858 }
5859 }
5860 // Compute the sum of fixed transits. Fixed transit variables should all be
5861 // fixed, otherwise we wait to get called later when propagation does it.
5862 int64_t sum_fixed_transits = 0;
5863 for (const int node : path_) {
5864 const IntVar* fixed_transit_var = dimension_->FixedTransitVar(node);
5865 if (!fixed_transit_var->Bound()) return;
5866 sum_fixed_transits =
5867 CapAdd(sum_fixed_transits, fixed_transit_var->Value());
5868 }
5869
5870 SynchronizeSpanAndTotalSlack(vehicle, sum_fixed_transits);
5871
5872 // The amount of break time that must occur during the route must be smaller
5873 // than span max - sum_fixed_transits. A break must occur on the route if it
5874 // must be after the route's start and before the route's end.
5875 // Propagate lower bound on span, then filter out values
5876 // that would force more breaks in route than possible.
5877 if (dimension_->HasBreakConstraints() &&
5878 !dimension_->GetBreakIntervalsOfVehicle(vehicle).empty()) {
5879 const int64_t vehicle_start_max = dimension_->CumulVar(start)->Max();
5880 const int64_t vehicle_end_min = dimension_->CumulVar(end)->Min();
5881 // Compute and propagate lower bound.
5882 int64_t min_break_duration = 0;
5883 for (IntervalVar* br : dimension_->GetBreakIntervalsOfVehicle(vehicle)) {
5884 if (!br->MustBePerformed()) continue;
5885 if (vehicle_start_max < br->EndMin() &&
5886 br->StartMax() < vehicle_end_min) {
5887 min_break_duration = CapAdd(min_break_duration, br->DurationMin());
5888 }
5889 }
5890 SetSpanMin(vehicle, CapAdd(min_break_duration, sum_fixed_transits),
5891 sum_fixed_transits);
5892 // If a break that is not inside the route may violate slack_max,
5893 // we can propagate in some cases: when the break must be before or
5894 // must be after the route.
5895 // In the other cases, we cannot deduce a better bound on a CumulVar or
5896 // on a break, so we do nothing.
5897 const int64_t slack_max =
5898 CapSub(SpanMax(vehicle, sum_fixed_transits), sum_fixed_transits);
5899 const int64_t max_additional_slack =
5900 CapSub(slack_max, min_break_duration);
5901 for (IntervalVar* br : dimension_->GetBreakIntervalsOfVehicle(vehicle)) {
5902 if (!br->MustBePerformed()) continue;
5903 // Break must be before end, detect whether it must be before start.
5904 if (vehicle_start_max >= br->EndMin() &&
5905 br->StartMax() < vehicle_end_min) {
5906 if (br->DurationMin() > max_additional_slack) {
5907 // Having the break inside would violate max_additional_slack..
5908 // Thus, it must be outside the route, in this case, before.
5909 br->SetEndMax(vehicle_start_max);
5910 dimension_->CumulVar(start)->SetMin(br->EndMin());
5911 }
5912 }
5913 // Break must be after start, detect whether it must be after end.
5914 // Same reasoning, in the case where the break is after.
5915 if (vehicle_start_max < br->EndMin() &&
5916 br->StartMax() >= vehicle_end_min) {
5917 if (br->DurationMin() > max_additional_slack) {
5918 br->SetStartMin(vehicle_end_min);
5919 dimension_->CumulVar(end)->SetMax(br->StartMax());
5920 }
5921 }
5922 }
5923 }
5924
5925 // Propagate span == cumul(end) - cumul(start).
5926 {
5927 IntVar* start_cumul = dimension_->CumulVar(start);
5928 IntVar* end_cumul = dimension_->CumulVar(end);
5929 const int64_t start_min = start_cumul->Min();
5930 const int64_t start_max = start_cumul->Max();
5931 const int64_t end_min = end_cumul->Min();
5932 const int64_t end_max = end_cumul->Max();
5933 // Propagate from cumuls to span.
5934 const int64_t span_lb = CapSub(end_min, start_max);
5935 SetSpanMin(vehicle, span_lb, sum_fixed_transits);
5936 const int64_t span_ub = CapSub(end_max, start_min);
5937 SetSpanMax(vehicle, span_ub, sum_fixed_transits);
5938 // Propagate from span to cumuls.
5939 const int64_t span_min = SpanMin(vehicle, sum_fixed_transits);
5940 const int64_t span_max = SpanMax(vehicle, sum_fixed_transits);
5941 const int64_t slack_from_lb = CapSub(span_max, span_lb);
5942 const int64_t slack_from_ub = CapSub(span_ub, span_min);
5943 // start >= start_max - (span_max - span_lb).
5944 start_cumul->SetMin(CapSub(start_max, slack_from_lb));
5945 // end <= end_min + (span_max - span_lb).
5946 end_cumul->SetMax(CapAdd(end_min, slack_from_lb));
5947 // // start <= start_min + (span_ub - span_min)
5948 start_cumul->SetMax(CapAdd(start_min, slack_from_ub));
5949 // // end >= end_max - (span_ub - span_min)
5950 end_cumul->SetMin(CapSub(end_max, slack_from_ub));
5951 }
5952
5953 // Propagate sum transits == span.
5954 {
5955 // Propagate from transits to span.
5956 int64_t span_lb = 0;
5957 int64_t span_ub = 0;
5958 for (const int node : path_) {
5959 span_lb = CapAdd(span_lb, dimension_->TransitVar(node)->Min());
5960 span_ub = CapAdd(span_ub, dimension_->TransitVar(node)->Max());
5961 }
5962 SetSpanMin(vehicle, span_lb, sum_fixed_transits);
5963 SetSpanMax(vehicle, span_ub, sum_fixed_transits);
5964 // Propagate from span to transits.
5965 // transit[i] <= transit_i_min + (span_max - span_lb)
5966 // transit[i] >= transit_i_max - (span_ub - span_min)
5967 const int64_t span_min = SpanMin(vehicle, sum_fixed_transits);
5968 const int64_t span_max = SpanMax(vehicle, sum_fixed_transits);
5969 const int64_t slack_from_lb = CapSub(span_max, span_lb);
5970 const int64_t slack_from_ub =
5971 span_ub < std::numeric_limits<int64_t>::max()
5972 ? CapSub(span_ub, span_min)
5973 : std::numeric_limits<int64_t>::max();
5974 for (const int node : path_) {
5975 IntVar* transit_var = dimension_->TransitVar(node);
5976 const int64_t transit_i_min = transit_var->Min();
5977 const int64_t transit_i_max = transit_var->Max();
5978 // TRICKY: the first propagation might change transit_var->Max(),
5979 // but we must use the same value of transit_i_max in the computation
5980 // of transit[i]'s lower bound that was used for span_ub.
5981 transit_var->SetMax(CapAdd(transit_i_min, slack_from_lb));
5982 transit_var->SetMin(CapSub(transit_i_max, slack_from_ub));
5983 }
5984 }
5985
5986 // TRICKY: add end node now, we will look at cumuls.
5987 path_.push_back(end);
5988
5989 // A stronger bound: from start min of the route, go to node i+1 with time
5990 // max(cumul[i] + fixed_transit, cumul[i+1].Min()).
5991 // Record arrival time (should be the same as end cumul min).
5992 // Then do the reverse route, going to time
5993 // min(cumul[i+1] - fixed_transit, cumul[i].Max())
5994 // Record final time as departure time.
5995 // Then arrival time - departure time is a valid lower bound of span.
5996 // First reasoning: start - end - start
5997 {
5998 int64_t arrival_time = dimension_->CumulVar(start)->Min();
5999 for (int i = 1; i < path_.size(); ++i) {
6000 arrival_time =
6001 std::max(CapAdd(arrival_time,
6002 dimension_->FixedTransitVar(path_[i - 1])->Min()),
6003 dimension_->CumulVar(path_[i])->Min());
6004 }
6005 int64_t departure_time = arrival_time;
6006 for (int i = path_.size() - 2; i >= 0; --i) {
6007 departure_time =
6008 std::min(CapSub(departure_time,
6009 dimension_->FixedTransitVar(path_[i])->Min()),
6010 dimension_->CumulVar(path_[i])->Max());
6011 }
6012 const int64_t span_lb = CapSub(arrival_time, departure_time);
6013 SetSpanMin(vehicle, span_lb, sum_fixed_transits);
6014 const int64_t maximum_deviation =
6015 CapSub(SpanMax(vehicle, sum_fixed_transits), span_lb);
6016 const int64_t start_lb = CapSub(departure_time, maximum_deviation);
6017 dimension_->CumulVar(start)->SetMin(start_lb);
6018 }
6019 // Second reasoning: end - start - end
6020 {
6021 int64_t departure_time = dimension_->CumulVar(end)->Max();
6022 for (int i = path_.size() - 2; i >= 0; --i) {
6023 const int curr_node = path_[i];
6024 departure_time =
6025 std::min(CapSub(departure_time,
6026 dimension_->FixedTransitVar(curr_node)->Min()),
6027 dimension_->CumulVar(curr_node)->Max());
6028 }
6029 int arrival_time = departure_time;
6030 for (int i = 1; i < path_.size(); ++i) {
6031 arrival_time =
6032 std::max(CapAdd(arrival_time,
6033 dimension_->FixedTransitVar(path_[i - 1])->Min()),
6034 dimension_->CumulVar(path_[i])->Min());
6035 }
6036 const int64_t span_lb = CapSub(arrival_time, departure_time);
6037 SetSpanMin(vehicle, span_lb, sum_fixed_transits);
6038 const int64_t maximum_deviation =
6039 CapSub(SpanMax(vehicle, sum_fixed_transits), span_lb);
6040 dimension_->CumulVar(end)->SetMax(
6041 CapAdd(arrival_time, maximum_deviation));
6042 }
6043 }
6044
6045 const RoutingModel* const model_;
6046 const RoutingDimension* const dimension_;
6047 std::vector<IntVar*> spans_;
6048 std::vector<IntVar*> total_slacks_;
6049 std::vector<int> path_;
6050 std::vector<Demon*> vehicle_demons_;
6051 };
6052
6053 } // namespace
6054
MakePathSpansAndTotalSlacks(const RoutingDimension * dimension,std::vector<IntVar * > spans,std::vector<IntVar * > total_slacks)6055 Constraint* RoutingModel::MakePathSpansAndTotalSlacks(
6056 const RoutingDimension* dimension, std::vector<IntVar*> spans,
6057 std::vector<IntVar*> total_slacks) {
6058 CHECK_EQ(vehicles_, spans.size());
6059 CHECK_EQ(vehicles_, total_slacks.size());
6060 return solver()->RevAlloc(
6061 new PathSpansAndTotalSlacks(this, dimension, spans, total_slacks));
6062 }
6063
6064 const char RoutingModelVisitor::kLightElement[] = "LightElement";
6065 const char RoutingModelVisitor::kLightElement2[] = "LightElement2";
6066 const char RoutingModelVisitor::kRemoveValues[] = "RemoveValues";
6067
RoutingDimension(RoutingModel * model,std::vector<int64_t> vehicle_capacities,const std::string & name,const RoutingDimension * base_dimension)6068 RoutingDimension::RoutingDimension(RoutingModel* model,
6069 std::vector<int64_t> vehicle_capacities,
6070 const std::string& name,
6071 const RoutingDimension* base_dimension)
6072 : vehicle_capacities_(std::move(vehicle_capacities)),
6073 base_dimension_(base_dimension),
6074 global_span_cost_coefficient_(0),
6075 model_(model),
6076 name_(name),
6077 global_optimizer_offset_(0) {
6078 CHECK(model != nullptr);
6079 vehicle_span_upper_bounds_.assign(model->vehicles(),
6080 std::numeric_limits<int64_t>::max());
6081 vehicle_span_cost_coefficients_.assign(model->vehicles(), 0);
6082 }
6083
RoutingDimension(RoutingModel * model,std::vector<int64_t> vehicle_capacities,const std::string & name,SelfBased)6084 RoutingDimension::RoutingDimension(RoutingModel* model,
6085 std::vector<int64_t> vehicle_capacities,
6086 const std::string& name, SelfBased)
6087 : RoutingDimension(model, std::move(vehicle_capacities), name, this) {}
6088
~RoutingDimension()6089 RoutingDimension::~RoutingDimension() {
6090 cumul_var_piecewise_linear_cost_.clear();
6091 }
6092
Initialize(const std::vector<int> & transit_evaluators,const std::vector<int> & state_dependent_transit_evaluators,int64_t slack_max)6093 void RoutingDimension::Initialize(
6094 const std::vector<int>& transit_evaluators,
6095 const std::vector<int>& state_dependent_transit_evaluators,
6096 int64_t slack_max) {
6097 InitializeCumuls();
6098 InitializeTransits(transit_evaluators, state_dependent_transit_evaluators,
6099 slack_max);
6100 }
6101
6102 namespace {
6103 // Very light version of the RangeLessOrEqual constraint (see ./range_cst.cc).
6104 // Only performs initial propagation and then checks the compatibility of the
6105 // variable domains without domain pruning.
6106 // This is useful when to avoid ping-pong effects with costly constraints
6107 // such as the PathCumul constraint.
6108 // This constraint has not been added to the cp library (in range_cst.cc) given
6109 // it only does checking and no propagation (except the initial propagation)
6110 // and is only fit for local search, in particular in the context of vehicle
6111 // routing.
6112 class LightRangeLessOrEqual : public Constraint {
6113 public:
6114 LightRangeLessOrEqual(Solver* const s, IntExpr* const l, IntExpr* const r);
~LightRangeLessOrEqual()6115 ~LightRangeLessOrEqual() override {}
6116 void Post() override;
6117 void InitialPropagate() override;
6118 std::string DebugString() const override;
Var()6119 IntVar* Var() override {
6120 return solver()->MakeIsLessOrEqualVar(left_, right_);
6121 }
6122 // TODO(user): introduce a kLightLessOrEqual tag.
Accept(ModelVisitor * const visitor) const6123 void Accept(ModelVisitor* const visitor) const override {
6124 visitor->BeginVisitConstraint(ModelVisitor::kLessOrEqual, this);
6125 visitor->VisitIntegerExpressionArgument(ModelVisitor::kLeftArgument, left_);
6126 visitor->VisitIntegerExpressionArgument(ModelVisitor::kRightArgument,
6127 right_);
6128 visitor->EndVisitConstraint(ModelVisitor::kLessOrEqual, this);
6129 }
6130
6131 private:
6132 void CheckRange();
6133
6134 IntExpr* const left_;
6135 IntExpr* const right_;
6136 Demon* demon_;
6137 };
6138
LightRangeLessOrEqual(Solver * const s,IntExpr * const l,IntExpr * const r)6139 LightRangeLessOrEqual::LightRangeLessOrEqual(Solver* const s, IntExpr* const l,
6140 IntExpr* const r)
6141 : Constraint(s), left_(l), right_(r), demon_(nullptr) {}
6142
Post()6143 void LightRangeLessOrEqual::Post() {
6144 demon_ = MakeConstraintDemon0(
6145 solver(), this, &LightRangeLessOrEqual::CheckRange, "CheckRange");
6146 left_->WhenRange(demon_);
6147 right_->WhenRange(demon_);
6148 }
6149
InitialPropagate()6150 void LightRangeLessOrEqual::InitialPropagate() {
6151 left_->SetMax(right_->Max());
6152 right_->SetMin(left_->Min());
6153 if (left_->Max() <= right_->Min()) {
6154 demon_->inhibit(solver());
6155 }
6156 }
6157
CheckRange()6158 void LightRangeLessOrEqual::CheckRange() {
6159 if (left_->Min() > right_->Max()) {
6160 solver()->Fail();
6161 }
6162 if (left_->Max() <= right_->Min()) {
6163 demon_->inhibit(solver());
6164 }
6165 }
6166
DebugString() const6167 std::string LightRangeLessOrEqual::DebugString() const {
6168 return left_->DebugString() + " < " + right_->DebugString();
6169 }
6170
6171 } // namespace
6172
InitializeCumuls()6173 void RoutingDimension::InitializeCumuls() {
6174 Solver* const solver = model_->solver();
6175 const int size = model_->Size() + model_->vehicles();
6176 const auto capacity_range = std::minmax_element(vehicle_capacities_.begin(),
6177 vehicle_capacities_.end());
6178 const int64_t min_capacity = *capacity_range.first;
6179 CHECK_GE(min_capacity, 0);
6180 const int64_t max_capacity = *capacity_range.second;
6181 solver->MakeIntVarArray(size, 0, max_capacity, name_, &cumuls_);
6182 // Refine the min/max for vehicle start/ends based on vehicle capacities.
6183 for (int v = 0; v < model_->vehicles(); v++) {
6184 const int64_t vehicle_capacity = vehicle_capacities_[v];
6185 cumuls_[model_->Start(v)]->SetMax(vehicle_capacity);
6186 cumuls_[model_->End(v)]->SetMax(vehicle_capacity);
6187 }
6188
6189 forbidden_intervals_.resize(size);
6190 capacity_vars_.clear();
6191 if (min_capacity != max_capacity) {
6192 solver->MakeIntVarArray(size, 0, std::numeric_limits<int64_t>::max(),
6193 &capacity_vars_);
6194 for (int i = 0; i < size; ++i) {
6195 IntVar* const capacity_var = capacity_vars_[i];
6196 if (i < model_->Size()) {
6197 IntVar* const capacity_active = solver->MakeBoolVar();
6198 solver->AddConstraint(
6199 solver->MakeLessOrEqual(model_->ActiveVar(i), capacity_active));
6200 solver->AddConstraint(solver->MakeIsLessOrEqualCt(
6201 cumuls_[i], capacity_var, capacity_active));
6202 } else {
6203 solver->AddConstraint(
6204 solver->MakeLessOrEqual(cumuls_[i], capacity_var));
6205 }
6206 }
6207 }
6208 }
6209
6210 namespace {
6211 template <int64_t value>
IthElementOrValue(const std::vector<int64_t> & v,int64_t index)6212 int64_t IthElementOrValue(const std::vector<int64_t>& v, int64_t index) {
6213 return index >= 0 ? v[index] : value;
6214 }
6215
ComputeTransitClasses(const std::vector<int> & evaluator_indices,std::vector<int> * class_evaluators,std::vector<int64_t> * vehicle_to_class)6216 void ComputeTransitClasses(const std::vector<int>& evaluator_indices,
6217 std::vector<int>* class_evaluators,
6218 std::vector<int64_t>* vehicle_to_class) {
6219 CHECK(class_evaluators != nullptr);
6220 CHECK(vehicle_to_class != nullptr);
6221 class_evaluators->clear();
6222 vehicle_to_class->resize(evaluator_indices.size(), -1);
6223 absl::flat_hash_map<int, int64_t> evaluator_to_class;
6224 for (int i = 0; i < evaluator_indices.size(); ++i) {
6225 const int evaluator_index = evaluator_indices[i];
6226 int evaluator_class = -1;
6227 if (!gtl::FindCopy(evaluator_to_class, evaluator_index, &evaluator_class)) {
6228 evaluator_class = class_evaluators->size();
6229 evaluator_to_class[evaluator_index] = evaluator_class;
6230 class_evaluators->push_back(evaluator_index);
6231 }
6232 (*vehicle_to_class)[i] = evaluator_class;
6233 }
6234 }
6235 } // namespace
6236
InitializeTransitVariables(int64_t slack_max)6237 void RoutingDimension::InitializeTransitVariables(int64_t slack_max) {
6238 CHECK(!class_evaluators_.empty());
6239 CHECK(base_dimension_ == nullptr ||
6240 !state_dependent_class_evaluators_.empty());
6241
6242 Solver* const solver = model_->solver();
6243 const int size = model_->Size();
6244 const Solver::IndexEvaluator1 dependent_vehicle_class_function =
6245 [this](int index) {
6246 return (0 <= index && index < state_dependent_vehicle_to_class_.size())
6247 ? state_dependent_vehicle_to_class_[index]
6248 : state_dependent_class_evaluators_.size();
6249 };
6250 const std::string slack_name = name_ + " slack";
6251 const std::string transit_name = name_ + " fixed transit";
6252
6253 for (int64_t i = 0; i < size; ++i) {
6254 fixed_transits_[i] = solver->MakeIntVar(std::numeric_limits<int64_t>::min(),
6255 std::numeric_limits<int64_t>::max(),
6256 absl::StrCat(transit_name, i));
6257 // Setting dependent_transits_[i].
6258 if (base_dimension_ != nullptr) {
6259 if (state_dependent_class_evaluators_.size() == 1) {
6260 std::vector<IntVar*> transition_variables(cumuls_.size(), nullptr);
6261 for (int64_t j = 0; j < cumuls_.size(); ++j) {
6262 transition_variables[j] =
6263 MakeRangeMakeElementExpr(
6264 model_
6265 ->StateDependentTransitCallback(
6266 state_dependent_class_evaluators_[0])(i, j)
6267 .transit,
6268 base_dimension_->CumulVar(i), solver)
6269 ->Var();
6270 }
6271 dependent_transits_[i] =
6272 solver->MakeElement(transition_variables, model_->NextVar(i))
6273 ->Var();
6274 } else {
6275 IntVar* const vehicle_class_var =
6276 solver
6277 ->MakeElement(dependent_vehicle_class_function,
6278 model_->VehicleVar(i))
6279 ->Var();
6280 std::vector<IntVar*> transit_for_vehicle;
6281 transit_for_vehicle.reserve(state_dependent_class_evaluators_.size() +
6282 1);
6283 for (int evaluator : state_dependent_class_evaluators_) {
6284 std::vector<IntVar*> transition_variables(cumuls_.size(), nullptr);
6285 for (int64_t j = 0; j < cumuls_.size(); ++j) {
6286 transition_variables[j] =
6287 MakeRangeMakeElementExpr(
6288 model_->StateDependentTransitCallback(evaluator)(i, j)
6289 .transit,
6290 base_dimension_->CumulVar(i), solver)
6291 ->Var();
6292 }
6293 transit_for_vehicle.push_back(
6294 solver->MakeElement(transition_variables, model_->NextVar(i))
6295 ->Var());
6296 }
6297 transit_for_vehicle.push_back(solver->MakeIntConst(0));
6298 dependent_transits_[i] =
6299 solver->MakeElement(transit_for_vehicle, vehicle_class_var)->Var();
6300 }
6301 } else {
6302 dependent_transits_[i] = solver->MakeIntConst(0);
6303 }
6304
6305 // Summing fixed transits, dependent transits and the slack.
6306 IntExpr* transit_expr = fixed_transits_[i];
6307 if (dependent_transits_[i]->Min() != 0 ||
6308 dependent_transits_[i]->Max() != 0) {
6309 transit_expr = solver->MakeSum(transit_expr, dependent_transits_[i]);
6310 }
6311
6312 if (slack_max == 0) {
6313 slacks_[i] = solver->MakeIntConst(0);
6314 } else {
6315 slacks_[i] =
6316 solver->MakeIntVar(0, slack_max, absl::StrCat(slack_name, i));
6317 transit_expr = solver->MakeSum(slacks_[i], transit_expr);
6318 }
6319 transits_[i] = transit_expr->Var();
6320 }
6321 }
6322
InitializeTransits(const std::vector<int> & transit_evaluators,const std::vector<int> & state_dependent_transit_evaluators,int64_t slack_max)6323 void RoutingDimension::InitializeTransits(
6324 const std::vector<int>& transit_evaluators,
6325 const std::vector<int>& state_dependent_transit_evaluators,
6326 int64_t slack_max) {
6327 CHECK_EQ(model_->vehicles(), transit_evaluators.size());
6328 CHECK(base_dimension_ == nullptr ||
6329 model_->vehicles() == state_dependent_transit_evaluators.size());
6330 const int size = model_->Size();
6331 transits_.resize(size, nullptr);
6332 fixed_transits_.resize(size, nullptr);
6333 slacks_.resize(size, nullptr);
6334 dependent_transits_.resize(size, nullptr);
6335 ComputeTransitClasses(transit_evaluators, &class_evaluators_,
6336 &vehicle_to_class_);
6337 if (base_dimension_ != nullptr) {
6338 ComputeTransitClasses(state_dependent_transit_evaluators,
6339 &state_dependent_class_evaluators_,
6340 &state_dependent_vehicle_to_class_);
6341 }
6342
6343 InitializeTransitVariables(slack_max);
6344 }
6345
FillPathEvaluation(const std::vector<int64_t> & path,const RoutingModel::TransitCallback2 & evaluator,std::vector<int64_t> * values)6346 void FillPathEvaluation(const std::vector<int64_t>& path,
6347 const RoutingModel::TransitCallback2& evaluator,
6348 std::vector<int64_t>* values) {
6349 const int num_nodes = path.size();
6350 values->resize(num_nodes - 1);
6351 for (int i = 0; i < num_nodes - 1; ++i) {
6352 (*values)[i] = evaluator(path[i], path[i + 1]);
6353 }
6354 }
6355
TypeRegulationsChecker(const RoutingModel & model)6356 TypeRegulationsChecker::TypeRegulationsChecker(const RoutingModel& model)
6357 : model_(model), occurrences_of_type_(model.GetNumberOfVisitTypes()) {}
6358
CheckVehicle(int vehicle,const std::function<int64_t (int64_t)> & next_accessor)6359 bool TypeRegulationsChecker::CheckVehicle(
6360 int vehicle, const std::function<int64_t(int64_t)>& next_accessor) {
6361 if (!HasRegulationsToCheck()) {
6362 return true;
6363 }
6364
6365 InitializeCheck(vehicle, next_accessor);
6366
6367 for (int pos = 0; pos < current_route_visits_.size(); pos++) {
6368 const int64_t current_visit = current_route_visits_[pos];
6369 const int type = model_.GetVisitType(current_visit);
6370 if (type < 0) {
6371 continue;
6372 }
6373 const VisitTypePolicy policy = model_.GetVisitTypePolicy(current_visit);
6374
6375 DCHECK_LT(type, occurrences_of_type_.size());
6376 int& num_type_added = occurrences_of_type_[type].num_type_added_to_vehicle;
6377 int& num_type_removed =
6378 occurrences_of_type_[type].num_type_removed_from_vehicle;
6379 DCHECK_LE(num_type_removed, num_type_added);
6380 if (policy == RoutingModel::ADDED_TYPE_REMOVED_FROM_VEHICLE &&
6381 num_type_removed == num_type_added) {
6382 // The type is not actually being removed as all added types have already
6383 // been removed.
6384 continue;
6385 }
6386
6387 if (!CheckTypeRegulations(type, policy, pos)) {
6388 return false;
6389 }
6390 // Update count of type based on the visit policy.
6391 if (policy == VisitTypePolicy::TYPE_ADDED_TO_VEHICLE ||
6392 policy == VisitTypePolicy::TYPE_SIMULTANEOUSLY_ADDED_AND_REMOVED) {
6393 num_type_added++;
6394 }
6395 if (policy == VisitTypePolicy::TYPE_SIMULTANEOUSLY_ADDED_AND_REMOVED ||
6396 policy == VisitTypePolicy::ADDED_TYPE_REMOVED_FROM_VEHICLE) {
6397 num_type_removed++;
6398 }
6399 }
6400 return FinalizeCheck();
6401 }
6402
InitializeCheck(int vehicle,const std::function<int64_t (int64_t)> & next_accessor)6403 void TypeRegulationsChecker::InitializeCheck(
6404 int vehicle, const std::function<int64_t(int64_t)>& next_accessor) {
6405 // Accumulates the count of types before the current node.
6406 // {0, 0, -1} does not compile on or-tools.
6407 std::fill(occurrences_of_type_.begin(), occurrences_of_type_.end(),
6408 TypeRegulationsChecker::TypePolicyOccurrence());
6409
6410 // TODO(user): Optimize the filter to avoid scanning the route an extra
6411 // time when there are no TYPE_ON_VEHICLE_UP_TO_VISIT policies on the route,
6412 // by passing a boolean to CheckVehicle() passed to InitializeCheck().
6413 current_route_visits_.clear();
6414 for (int64_t current = model_.Start(vehicle); !model_.IsEnd(current);
6415 current = next_accessor(current)) {
6416 const int type = model_.GetVisitType(current);
6417 if (type >= 0 && model_.GetVisitTypePolicy(current) ==
6418 VisitTypePolicy::TYPE_ON_VEHICLE_UP_TO_VISIT) {
6419 occurrences_of_type_[type].position_of_last_type_on_vehicle_up_to_visit =
6420 current_route_visits_.size();
6421 }
6422 current_route_visits_.push_back(current);
6423 }
6424
6425 OnInitializeCheck();
6426 }
6427
TypeOccursOnRoute(int type) const6428 bool TypeRegulationsChecker::TypeOccursOnRoute(int type) const {
6429 const TypePolicyOccurrence& occurrences = occurrences_of_type_[type];
6430 return occurrences.num_type_added_to_vehicle > 0 ||
6431 occurrences.position_of_last_type_on_vehicle_up_to_visit >= 0;
6432 }
6433
TypeCurrentlyOnRoute(int type,int pos) const6434 bool TypeRegulationsChecker::TypeCurrentlyOnRoute(int type, int pos) const {
6435 const TypePolicyOccurrence& occurrences = occurrences_of_type_[type];
6436 return occurrences.num_type_removed_from_vehicle <
6437 occurrences.num_type_added_to_vehicle ||
6438 occurrences.position_of_last_type_on_vehicle_up_to_visit >= pos;
6439 }
6440
TypeIncompatibilityChecker(const RoutingModel & model,bool check_hard_incompatibilities)6441 TypeIncompatibilityChecker::TypeIncompatibilityChecker(
6442 const RoutingModel& model, bool check_hard_incompatibilities)
6443 : TypeRegulationsChecker(model),
6444 check_hard_incompatibilities_(check_hard_incompatibilities) {}
6445
HasRegulationsToCheck() const6446 bool TypeIncompatibilityChecker::HasRegulationsToCheck() const {
6447 return model_.HasTemporalTypeIncompatibilities() ||
6448 (check_hard_incompatibilities_ &&
6449 model_.HasHardTypeIncompatibilities());
6450 }
6451
6452 // TODO(user): Remove the check_hard_incompatibilities_ boolean and always
6453 // check both incompatibilities to simplify the code?
6454 // TODO(user): Improve algorithm by only checking a given type if necessary?
6455 // - For temporal incompatibilities, only check if NonDeliveredType(count) == 1.
6456 // - For hard incompatibilities, only if NonDeliveryType(type) == 1.
CheckTypeRegulations(int type,VisitTypePolicy policy,int pos)6457 bool TypeIncompatibilityChecker::CheckTypeRegulations(int type,
6458 VisitTypePolicy policy,
6459 int pos) {
6460 if (policy == VisitTypePolicy::ADDED_TYPE_REMOVED_FROM_VEHICLE) {
6461 // NOTE: We don't need to check incompatibilities when the type is being
6462 // removed from the route.
6463 return true;
6464 }
6465 for (int incompatible_type :
6466 model_.GetTemporalTypeIncompatibilitiesOfType(type)) {
6467 if (TypeCurrentlyOnRoute(incompatible_type, pos)) {
6468 return false;
6469 }
6470 }
6471 if (check_hard_incompatibilities_) {
6472 for (int incompatible_type :
6473 model_.GetHardTypeIncompatibilitiesOfType(type)) {
6474 if (TypeOccursOnRoute(incompatible_type)) {
6475 return false;
6476 }
6477 }
6478 }
6479 return true;
6480 }
6481
HasRegulationsToCheck() const6482 bool TypeRequirementChecker::HasRegulationsToCheck() const {
6483 return model_.HasTemporalTypeRequirements() ||
6484 model_.HasSameVehicleTypeRequirements();
6485 }
6486
CheckRequiredTypesCurrentlyOnRoute(const std::vector<absl::flat_hash_set<int>> & required_type_alternatives,int pos)6487 bool TypeRequirementChecker::CheckRequiredTypesCurrentlyOnRoute(
6488 const std::vector<absl::flat_hash_set<int>>& required_type_alternatives,
6489 int pos) {
6490 for (const absl::flat_hash_set<int>& requirement_alternatives :
6491 required_type_alternatives) {
6492 bool has_one_of_alternatives = false;
6493 for (int type_alternative : requirement_alternatives) {
6494 if (TypeCurrentlyOnRoute(type_alternative, pos)) {
6495 has_one_of_alternatives = true;
6496 break;
6497 }
6498 }
6499 if (!has_one_of_alternatives) {
6500 return false;
6501 }
6502 }
6503 return true;
6504 }
6505
CheckTypeRegulations(int type,VisitTypePolicy policy,int pos)6506 bool TypeRequirementChecker::CheckTypeRegulations(int type,
6507 VisitTypePolicy policy,
6508 int pos) {
6509 if (policy == RoutingModel::TYPE_ADDED_TO_VEHICLE ||
6510 policy == RoutingModel::TYPE_SIMULTANEOUSLY_ADDED_AND_REMOVED) {
6511 if (!CheckRequiredTypesCurrentlyOnRoute(
6512 model_.GetRequiredTypeAlternativesWhenAddingType(type), pos)) {
6513 return false;
6514 }
6515 }
6516 if (policy != RoutingModel::TYPE_ADDED_TO_VEHICLE) {
6517 if (!CheckRequiredTypesCurrentlyOnRoute(
6518 model_.GetRequiredTypeAlternativesWhenRemovingType(type), pos)) {
6519 return false;
6520 }
6521 }
6522 if (policy != RoutingModel::ADDED_TYPE_REMOVED_FROM_VEHICLE &&
6523 !model_.GetSameVehicleRequiredTypeAlternativesOfType(type).empty()) {
6524 types_with_same_vehicle_requirements_on_route_.insert(type);
6525 }
6526 return true;
6527 }
6528
FinalizeCheck() const6529 bool TypeRequirementChecker::FinalizeCheck() const {
6530 for (int type : types_with_same_vehicle_requirements_on_route_) {
6531 for (const absl::flat_hash_set<int>& requirement_alternatives :
6532 model_.GetSameVehicleRequiredTypeAlternativesOfType(type)) {
6533 bool has_one_of_alternatives = false;
6534 for (const int type_alternative : requirement_alternatives) {
6535 if (TypeOccursOnRoute(type_alternative)) {
6536 has_one_of_alternatives = true;
6537 break;
6538 }
6539 }
6540 if (!has_one_of_alternatives) {
6541 return false;
6542 }
6543 }
6544 }
6545 return true;
6546 }
6547
TypeRegulationsConstraint(const RoutingModel & model)6548 TypeRegulationsConstraint::TypeRegulationsConstraint(const RoutingModel& model)
6549 : Constraint(model.solver()),
6550 model_(model),
6551 incompatibility_checker_(model, /*check_hard_incompatibilities*/ true),
6552 requirement_checker_(model),
6553 vehicle_demons_(model.vehicles()) {}
6554
PropagateNodeRegulations(int node)6555 void TypeRegulationsConstraint::PropagateNodeRegulations(int node) {
6556 DCHECK_LT(node, model_.Size());
6557 if (!model_.VehicleVar(node)->Bound() || !model_.NextVar(node)->Bound()) {
6558 // Vehicle var or Next var not bound.
6559 return;
6560 }
6561 const int vehicle = model_.VehicleVar(node)->Min();
6562 if (vehicle < 0) return;
6563 DCHECK(vehicle_demons_[vehicle] != nullptr);
6564 EnqueueDelayedDemon(vehicle_demons_[vehicle]);
6565 }
6566
CheckRegulationsOnVehicle(int vehicle)6567 void TypeRegulationsConstraint::CheckRegulationsOnVehicle(int vehicle) {
6568 const auto next_accessor = [this, vehicle](int64_t node) {
6569 if (model_.NextVar(node)->Bound()) {
6570 return model_.NextVar(node)->Value();
6571 }
6572 // Node not bound, skip to the end of the vehicle.
6573 return model_.End(vehicle);
6574 };
6575 if (!incompatibility_checker_.CheckVehicle(vehicle, next_accessor) ||
6576 !requirement_checker_.CheckVehicle(vehicle, next_accessor)) {
6577 model_.solver()->Fail();
6578 }
6579 }
6580
Post()6581 void TypeRegulationsConstraint::Post() {
6582 for (int vehicle = 0; vehicle < model_.vehicles(); vehicle++) {
6583 vehicle_demons_[vehicle] = MakeDelayedConstraintDemon1(
6584 solver(), this, &TypeRegulationsConstraint::CheckRegulationsOnVehicle,
6585 "CheckRegulationsOnVehicle", vehicle);
6586 }
6587 for (int node = 0; node < model_.Size(); node++) {
6588 Demon* node_demon = MakeConstraintDemon1(
6589 solver(), this, &TypeRegulationsConstraint::PropagateNodeRegulations,
6590 "PropagateNodeRegulations", node);
6591 model_.NextVar(node)->WhenBound(node_demon);
6592 model_.VehicleVar(node)->WhenBound(node_demon);
6593 }
6594 }
6595
InitialPropagate()6596 void TypeRegulationsConstraint::InitialPropagate() {
6597 for (int vehicle = 0; vehicle < model_.vehicles(); vehicle++) {
6598 CheckRegulationsOnVehicle(vehicle);
6599 }
6600 }
6601
CloseModel(bool use_light_propagation)6602 void RoutingDimension::CloseModel(bool use_light_propagation) {
6603 Solver* const solver = model_->solver();
6604 const auto capacity_lambda = [this](int64_t vehicle) {
6605 return vehicle >= 0 ? vehicle_capacities_[vehicle]
6606 : std::numeric_limits<int64_t>::max();
6607 };
6608 for (int i = 0; i < capacity_vars_.size(); ++i) {
6609 IntVar* const vehicle_var = model_->VehicleVar(i);
6610 IntVar* const capacity_var = capacity_vars_[i];
6611 if (use_light_propagation) {
6612 solver->AddConstraint(MakeLightElement(
6613 solver, capacity_var, vehicle_var, capacity_lambda,
6614 [this]() { return model_->enable_deep_serialization_; }));
6615 } else {
6616 solver->AddConstraint(solver->MakeEquality(
6617 capacity_var,
6618 solver->MakeElement(capacity_lambda, vehicle_var)->Var()));
6619 }
6620 }
6621 for (int i = 0; i < fixed_transits_.size(); ++i) {
6622 IntVar* const next_var = model_->NextVar(i);
6623 IntVar* const fixed_transit = fixed_transits_[i];
6624 const auto transit_vehicle_evaluator = [this, i](int64_t to,
6625 int64_t eval_index) {
6626 return eval_index >= 0 ? transit_evaluator(eval_index)(i, to) : 0;
6627 };
6628 if (use_light_propagation) {
6629 if (class_evaluators_.size() == 1) {
6630 const int class_evaluator_index = class_evaluators_[0];
6631 const auto& unary_callback =
6632 model_->UnaryTransitCallbackOrNull(class_evaluator_index);
6633 if (unary_callback == nullptr) {
6634 solver->AddConstraint(MakeLightElement(
6635 solver, fixed_transit, next_var,
6636 [this, i](int64_t to) {
6637 return model_->TransitCallback(class_evaluators_[0])(i, to);
6638 },
6639 [this]() { return model_->enable_deep_serialization_; }));
6640 } else {
6641 fixed_transit->SetValue(unary_callback(i));
6642 }
6643 } else {
6644 solver->AddConstraint(MakeLightElement2(
6645 solver, fixed_transit, next_var, model_->VehicleVar(i),
6646 transit_vehicle_evaluator,
6647 [this]() { return model_->enable_deep_serialization_; }));
6648 }
6649 } else {
6650 if (class_evaluators_.size() == 1) {
6651 const int class_evaluator_index = class_evaluators_[0];
6652 const auto& unary_callback =
6653 model_->UnaryTransitCallbackOrNull(class_evaluator_index);
6654 if (unary_callback == nullptr) {
6655 solver->AddConstraint(solver->MakeEquality(
6656 fixed_transit, solver
6657 ->MakeElement(
6658 [this, i](int64_t to) {
6659 return model_->TransitCallback(
6660 class_evaluators_[0])(i, to);
6661 },
6662 model_->NextVar(i))
6663 ->Var()));
6664 } else {
6665 fixed_transit->SetValue(unary_callback(i));
6666 }
6667 } else {
6668 solver->AddConstraint(solver->MakeEquality(
6669 fixed_transit, solver
6670 ->MakeElement(transit_vehicle_evaluator,
6671 next_var, model_->VehicleVar(i))
6672 ->Var()));
6673 }
6674 }
6675 }
6676 if (HasBreakConstraints()) {
6677 GlobalVehicleBreaksConstraint* constraint =
6678 model()->solver()->RevAlloc(new GlobalVehicleBreaksConstraint(this));
6679 solver->AddConstraint(constraint);
6680 }
6681 }
6682
GetTransitValue(int64_t from_index,int64_t to_index,int64_t vehicle) const6683 int64_t RoutingDimension::GetTransitValue(int64_t from_index, int64_t to_index,
6684 int64_t vehicle) const {
6685 DCHECK(transit_evaluator(vehicle) != nullptr);
6686 return transit_evaluator(vehicle)(from_index, to_index);
6687 }
6688
GetAllowedIntervalsInRange(int64_t index,int64_t min_value,int64_t max_value) const6689 SortedDisjointIntervalList RoutingDimension::GetAllowedIntervalsInRange(
6690 int64_t index, int64_t min_value, int64_t max_value) const {
6691 SortedDisjointIntervalList allowed;
6692 const SortedDisjointIntervalList& forbidden = forbidden_intervals_[index];
6693 IntVar* const cumul_var = cumuls_[index];
6694 const int64_t min = std::max(min_value, cumul_var->Min());
6695 const int64_t max = std::min(max_value, cumul_var->Max());
6696 int64_t next_start = min;
6697 for (SortedDisjointIntervalList::Iterator interval =
6698 forbidden.FirstIntervalGreaterOrEqual(min);
6699 interval != forbidden.end(); ++interval) {
6700 if (next_start > max) break;
6701 if (next_start < interval->start) {
6702 allowed.InsertInterval(next_start, CapSub(interval->start, 1));
6703 }
6704 next_start = CapAdd(interval->end, 1);
6705 }
6706 if (next_start <= max) {
6707 allowed.InsertInterval(next_start, max);
6708 }
6709 return allowed;
6710 }
6711
SetSpanUpperBoundForVehicle(int64_t upper_bound,int vehicle)6712 void RoutingDimension::SetSpanUpperBoundForVehicle(int64_t upper_bound,
6713 int vehicle) {
6714 CHECK_GE(vehicle, 0);
6715 CHECK_LT(vehicle, vehicle_span_upper_bounds_.size());
6716 CHECK_GE(upper_bound, 0);
6717 vehicle_span_upper_bounds_[vehicle] = upper_bound;
6718 }
6719
SetSpanCostCoefficientForVehicle(int64_t coefficient,int vehicle)6720 void RoutingDimension::SetSpanCostCoefficientForVehicle(int64_t coefficient,
6721 int vehicle) {
6722 CHECK_GE(vehicle, 0);
6723 CHECK_LT(vehicle, vehicle_span_cost_coefficients_.size());
6724 CHECK_GE(coefficient, 0);
6725 vehicle_span_cost_coefficients_[vehicle] = coefficient;
6726 }
6727
SetSpanCostCoefficientForAllVehicles(int64_t coefficient)6728 void RoutingDimension::SetSpanCostCoefficientForAllVehicles(
6729 int64_t coefficient) {
6730 CHECK_GE(coefficient, 0);
6731 vehicle_span_cost_coefficients_.assign(model_->vehicles(), coefficient);
6732 }
6733
SetGlobalSpanCostCoefficient(int64_t coefficient)6734 void RoutingDimension::SetGlobalSpanCostCoefficient(int64_t coefficient) {
6735 CHECK_GE(coefficient, 0);
6736 global_span_cost_coefficient_ = coefficient;
6737 }
6738
SetCumulVarPiecewiseLinearCost(int64_t index,const PiecewiseLinearFunction & cost)6739 void RoutingDimension::SetCumulVarPiecewiseLinearCost(
6740 int64_t index, const PiecewiseLinearFunction& cost) {
6741 if (!cost.IsNonDecreasing()) {
6742 LOG(WARNING) << "Only non-decreasing cost functions are supported.";
6743 return;
6744 }
6745 if (cost.Value(0) < 0) {
6746 LOG(WARNING) << "Only positive cost functions are supported.";
6747 return;
6748 }
6749 if (index >= cumul_var_piecewise_linear_cost_.size()) {
6750 cumul_var_piecewise_linear_cost_.resize(index + 1);
6751 }
6752 PiecewiseLinearCost& piecewise_linear_cost =
6753 cumul_var_piecewise_linear_cost_[index];
6754 piecewise_linear_cost.var = cumuls_[index];
6755 piecewise_linear_cost.cost = absl::make_unique<PiecewiseLinearFunction>(cost);
6756 }
6757
HasCumulVarPiecewiseLinearCost(int64_t index) const6758 bool RoutingDimension::HasCumulVarPiecewiseLinearCost(int64_t index) const {
6759 return (index < cumul_var_piecewise_linear_cost_.size() &&
6760 cumul_var_piecewise_linear_cost_[index].var != nullptr);
6761 }
6762
GetCumulVarPiecewiseLinearCost(int64_t index) const6763 const PiecewiseLinearFunction* RoutingDimension::GetCumulVarPiecewiseLinearCost(
6764 int64_t index) const {
6765 if (index < cumul_var_piecewise_linear_cost_.size() &&
6766 cumul_var_piecewise_linear_cost_[index].var != nullptr) {
6767 return cumul_var_piecewise_linear_cost_[index].cost.get();
6768 }
6769 return nullptr;
6770 }
6771
6772 namespace {
BuildVarFromExprAndIndexActiveState(const RoutingModel * model,IntExpr * expr,int index)6773 IntVar* BuildVarFromExprAndIndexActiveState(const RoutingModel* model,
6774 IntExpr* expr, int index) {
6775 Solver* const solver = model->solver();
6776 if (model->IsStart(index) || model->IsEnd(index)) {
6777 const int vehicle = model->VehicleIndex(index);
6778 DCHECK_GE(vehicle, 0);
6779 return solver->MakeProd(expr, model->VehicleRouteConsideredVar(vehicle))
6780 ->Var();
6781 }
6782 return solver->MakeProd(expr, model->ActiveVar(index))->Var();
6783 }
6784 } // namespace
6785
SetupCumulVarPiecewiseLinearCosts(std::vector<IntVar * > * cost_elements) const6786 void RoutingDimension::SetupCumulVarPiecewiseLinearCosts(
6787 std::vector<IntVar*>* cost_elements) const {
6788 CHECK(cost_elements != nullptr);
6789 Solver* const solver = model_->solver();
6790 for (int i = 0; i < cumul_var_piecewise_linear_cost_.size(); ++i) {
6791 const PiecewiseLinearCost& piecewise_linear_cost =
6792 cumul_var_piecewise_linear_cost_[i];
6793 if (piecewise_linear_cost.var != nullptr) {
6794 IntExpr* const expr = solver->MakePiecewiseLinearExpr(
6795 piecewise_linear_cost.var, *piecewise_linear_cost.cost);
6796 IntVar* cost_var = BuildVarFromExprAndIndexActiveState(model_, expr, i);
6797 cost_elements->push_back(cost_var);
6798 // TODO(user): Check if it wouldn't be better to minimize
6799 // piecewise_linear_cost.var here.
6800 model_->AddWeightedVariableMinimizedByFinalizer(cost_var, 0);
6801 }
6802 }
6803 }
6804
SetCumulVarSoftUpperBound(int64_t index,int64_t upper_bound,int64_t coefficient)6805 void RoutingDimension::SetCumulVarSoftUpperBound(int64_t index,
6806 int64_t upper_bound,
6807 int64_t coefficient) {
6808 if (index >= cumul_var_soft_upper_bound_.size()) {
6809 cumul_var_soft_upper_bound_.resize(index + 1, {nullptr, 0, 0});
6810 }
6811 cumul_var_soft_upper_bound_[index] = {cumuls_[index], upper_bound,
6812 coefficient};
6813 }
6814
HasCumulVarSoftUpperBound(int64_t index) const6815 bool RoutingDimension::HasCumulVarSoftUpperBound(int64_t index) const {
6816 return (index < cumul_var_soft_upper_bound_.size() &&
6817 cumul_var_soft_upper_bound_[index].var != nullptr);
6818 }
6819
GetCumulVarSoftUpperBound(int64_t index) const6820 int64_t RoutingDimension::GetCumulVarSoftUpperBound(int64_t index) const {
6821 if (index < cumul_var_soft_upper_bound_.size() &&
6822 cumul_var_soft_upper_bound_[index].var != nullptr) {
6823 return cumul_var_soft_upper_bound_[index].bound;
6824 }
6825 return cumuls_[index]->Max();
6826 }
6827
GetCumulVarSoftUpperBoundCoefficient(int64_t index) const6828 int64_t RoutingDimension::GetCumulVarSoftUpperBoundCoefficient(
6829 int64_t index) const {
6830 if (index < cumul_var_soft_upper_bound_.size() &&
6831 cumul_var_soft_upper_bound_[index].var != nullptr) {
6832 return cumul_var_soft_upper_bound_[index].coefficient;
6833 }
6834 return 0;
6835 }
6836
SetupCumulVarSoftUpperBoundCosts(std::vector<IntVar * > * cost_elements) const6837 void RoutingDimension::SetupCumulVarSoftUpperBoundCosts(
6838 std::vector<IntVar*>* cost_elements) const {
6839 CHECK(cost_elements != nullptr);
6840 Solver* const solver = model_->solver();
6841 for (int i = 0; i < cumul_var_soft_upper_bound_.size(); ++i) {
6842 const SoftBound& soft_bound = cumul_var_soft_upper_bound_[i];
6843 if (soft_bound.var != nullptr) {
6844 IntExpr* const expr = solver->MakeSemiContinuousExpr(
6845 solver->MakeSum(soft_bound.var, -soft_bound.bound), 0,
6846 soft_bound.coefficient);
6847 IntVar* cost_var = BuildVarFromExprAndIndexActiveState(model_, expr, i);
6848 cost_elements->push_back(cost_var);
6849 // NOTE: We minimize the cost here instead of minimizing the cumul
6850 // variable, to avoid setting the cumul to earlier than necessary.
6851 model_->AddWeightedVariableMinimizedByFinalizer(cost_var,
6852 soft_bound.coefficient);
6853 }
6854 }
6855 }
6856
SetCumulVarSoftLowerBound(int64_t index,int64_t lower_bound,int64_t coefficient)6857 void RoutingDimension::SetCumulVarSoftLowerBound(int64_t index,
6858 int64_t lower_bound,
6859 int64_t coefficient) {
6860 if (index >= cumul_var_soft_lower_bound_.size()) {
6861 cumul_var_soft_lower_bound_.resize(index + 1, {nullptr, 0, 0});
6862 }
6863 cumul_var_soft_lower_bound_[index] = {cumuls_[index], lower_bound,
6864 coefficient};
6865 }
6866
HasCumulVarSoftLowerBound(int64_t index) const6867 bool RoutingDimension::HasCumulVarSoftLowerBound(int64_t index) const {
6868 return (index < cumul_var_soft_lower_bound_.size() &&
6869 cumul_var_soft_lower_bound_[index].var != nullptr);
6870 }
6871
GetCumulVarSoftLowerBound(int64_t index) const6872 int64_t RoutingDimension::GetCumulVarSoftLowerBound(int64_t index) const {
6873 if (index < cumul_var_soft_lower_bound_.size() &&
6874 cumul_var_soft_lower_bound_[index].var != nullptr) {
6875 return cumul_var_soft_lower_bound_[index].bound;
6876 }
6877 return cumuls_[index]->Min();
6878 }
6879
GetCumulVarSoftLowerBoundCoefficient(int64_t index) const6880 int64_t RoutingDimension::GetCumulVarSoftLowerBoundCoefficient(
6881 int64_t index) const {
6882 if (index < cumul_var_soft_lower_bound_.size() &&
6883 cumul_var_soft_lower_bound_[index].var != nullptr) {
6884 return cumul_var_soft_lower_bound_[index].coefficient;
6885 }
6886 return 0;
6887 }
6888
SetupCumulVarSoftLowerBoundCosts(std::vector<IntVar * > * cost_elements) const6889 void RoutingDimension::SetupCumulVarSoftLowerBoundCosts(
6890 std::vector<IntVar*>* cost_elements) const {
6891 CHECK(cost_elements != nullptr);
6892 Solver* const solver = model_->solver();
6893 for (int i = 0; i < cumul_var_soft_lower_bound_.size(); ++i) {
6894 const SoftBound& soft_bound = cumul_var_soft_lower_bound_[i];
6895 if (soft_bound.var != nullptr) {
6896 IntExpr* const expr = solver->MakeSemiContinuousExpr(
6897 solver->MakeDifference(soft_bound.bound, soft_bound.var), 0,
6898 soft_bound.coefficient);
6899 IntVar* cost_var = BuildVarFromExprAndIndexActiveState(model_, expr, i);
6900 cost_elements->push_back(cost_var);
6901 // NOTE: We minimize the cost here instead of maximizing the cumul
6902 // variable, to avoid setting the cumul to later than necessary.
6903 model_->AddWeightedVariableMinimizedByFinalizer(cost_var,
6904 soft_bound.coefficient);
6905 }
6906 }
6907 }
6908
SetupGlobalSpanCost(std::vector<IntVar * > * cost_elements) const6909 void RoutingDimension::SetupGlobalSpanCost(
6910 std::vector<IntVar*>* cost_elements) const {
6911 CHECK(cost_elements != nullptr);
6912 Solver* const solver = model_->solver();
6913 if (global_span_cost_coefficient_ != 0) {
6914 std::vector<IntVar*> end_cumuls;
6915 for (int i = 0; i < model_->vehicles(); ++i) {
6916 end_cumuls.push_back(solver
6917 ->MakeProd(model_->vehicle_route_considered_[i],
6918 cumuls_[model_->End(i)])
6919 ->Var());
6920 }
6921 IntVar* const max_end_cumul = solver->MakeMax(end_cumuls)->Var();
6922 model_->AddWeightedVariableMinimizedByFinalizer(
6923 max_end_cumul, global_span_cost_coefficient_);
6924 std::vector<IntVar*> start_cumuls;
6925 for (int i = 0; i < model_->vehicles(); ++i) {
6926 IntVar* global_span_cost_start_cumul =
6927 solver->MakeIntVar(0, std::numeric_limits<int64_t>::max());
6928 solver->AddConstraint(solver->MakeIfThenElseCt(
6929 model_->vehicle_route_considered_[i], cumuls_[model_->Start(i)],
6930 max_end_cumul, global_span_cost_start_cumul));
6931 start_cumuls.push_back(global_span_cost_start_cumul);
6932 }
6933 IntVar* const min_start_cumul = solver->MakeMin(start_cumuls)->Var();
6934 model_->AddWeightedVariableMinimizedByFinalizer(
6935 min_start_cumul, global_span_cost_coefficient_);
6936 // If there is a single vehicle, model the cost as the sum of its transits
6937 // to avoid slow (infinite) propagation loops.
6938 // TODO(user): Avoid slow propagation in the path constraints.
6939 if (model_->vehicles() == 1) {
6940 for (int var_index = 0; var_index < model_->Size(); ++var_index) {
6941 model_->AddWeightedVariableMinimizedByFinalizer(
6942 slacks_[var_index], global_span_cost_coefficient_);
6943 cost_elements->push_back(
6944 solver
6945 ->MakeProd(
6946 model_->vehicle_route_considered_[0],
6947 solver->MakeProd(
6948 solver->MakeProd(
6949 solver->MakeSum(transits_[var_index],
6950 dependent_transits_[var_index]),
6951 global_span_cost_coefficient_),
6952 model_->ActiveVar(var_index)))
6953 ->Var());
6954 }
6955 } else {
6956 IntVar* const end_range =
6957 solver->MakeDifference(max_end_cumul, min_start_cumul)->Var();
6958 end_range->SetMin(0);
6959 cost_elements->push_back(
6960 solver->MakeProd(end_range, global_span_cost_coefficient_)->Var());
6961 }
6962 }
6963 }
6964
SetBreakIntervalsOfVehicle(std::vector<IntervalVar * > breaks,int vehicle,std::vector<int64_t> node_visit_transits)6965 void RoutingDimension::SetBreakIntervalsOfVehicle(
6966 std::vector<IntervalVar*> breaks, int vehicle,
6967 std::vector<int64_t> node_visit_transits) {
6968 if (breaks.empty()) return;
6969 const int visit_evaluator = model()->RegisterTransitCallback(
6970 [node_visit_transits](int64_t from, int64_t to) {
6971 return node_visit_transits[from];
6972 });
6973 SetBreakIntervalsOfVehicle(std::move(breaks), vehicle, visit_evaluator, -1);
6974 }
6975
SetBreakIntervalsOfVehicle(std::vector<IntervalVar * > breaks,int vehicle,std::vector<int64_t> node_visit_transits,std::function<int64_t (int64_t,int64_t)> delays)6976 void RoutingDimension::SetBreakIntervalsOfVehicle(
6977 std::vector<IntervalVar*> breaks, int vehicle,
6978 std::vector<int64_t> node_visit_transits,
6979 std::function<int64_t(int64_t, int64_t)> delays) {
6980 if (breaks.empty()) return;
6981 const int visit_evaluator = model()->RegisterTransitCallback(
6982 [node_visit_transits](int64_t from, int64_t to) {
6983 return node_visit_transits[from];
6984 });
6985 const int delay_evaluator =
6986 model()->RegisterTransitCallback(std::move(delays));
6987 SetBreakIntervalsOfVehicle(std::move(breaks), vehicle, visit_evaluator,
6988 delay_evaluator);
6989 }
6990
SetBreakIntervalsOfVehicle(std::vector<IntervalVar * > breaks,int vehicle,int pre_travel_evaluator,int post_travel_evaluator)6991 void RoutingDimension::SetBreakIntervalsOfVehicle(
6992 std::vector<IntervalVar*> breaks, int vehicle, int pre_travel_evaluator,
6993 int post_travel_evaluator) {
6994 DCHECK_LE(0, vehicle);
6995 DCHECK_LT(vehicle, model_->vehicles());
6996 if (breaks.empty()) return;
6997 if (!break_constraints_are_initialized_) InitializeBreaks();
6998 vehicle_break_intervals_[vehicle] = std::move(breaks);
6999 vehicle_pre_travel_evaluators_[vehicle] = pre_travel_evaluator;
7000 vehicle_post_travel_evaluators_[vehicle] = post_travel_evaluator;
7001 // Breaks intervals must be fixed by search.
7002 for (IntervalVar* const interval : vehicle_break_intervals_[vehicle]) {
7003 model_->AddIntervalToAssignment(interval);
7004 if (interval->MayBePerformed() && !interval->MustBePerformed()) {
7005 model_->AddVariableTargetToFinalizer(interval->PerformedExpr()->Var(), 0);
7006 }
7007 model_->AddVariableTargetToFinalizer(interval->SafeStartExpr(0)->Var(),
7008 std::numeric_limits<int64_t>::min());
7009 model_->AddVariableTargetToFinalizer(interval->SafeDurationExpr(0)->Var(),
7010 std::numeric_limits<int64_t>::min());
7011 }
7012 // When a vehicle has breaks, if its start and end are fixed,
7013 // then propagation keeps the cumuls min and max on its path feasible.
7014 model_->AddVariableTargetToFinalizer(CumulVar(model_->End(vehicle)),
7015 std::numeric_limits<int64_t>::min());
7016 model_->AddVariableTargetToFinalizer(CumulVar(model_->Start(vehicle)),
7017 std::numeric_limits<int64_t>::max());
7018 }
7019
InitializeBreaks()7020 void RoutingDimension::InitializeBreaks() {
7021 DCHECK(!break_constraints_are_initialized_);
7022 const int num_vehicles = model_->vehicles();
7023 vehicle_break_intervals_.resize(num_vehicles);
7024 vehicle_pre_travel_evaluators_.resize(num_vehicles, -1);
7025 vehicle_post_travel_evaluators_.resize(num_vehicles, -1);
7026 vehicle_break_distance_duration_.resize(num_vehicles);
7027 break_constraints_are_initialized_ = true;
7028 }
7029
HasBreakConstraints() const7030 bool RoutingDimension::HasBreakConstraints() const {
7031 return break_constraints_are_initialized_;
7032 }
7033
GetBreakIntervalsOfVehicle(int vehicle) const7034 const std::vector<IntervalVar*>& RoutingDimension::GetBreakIntervalsOfVehicle(
7035 int vehicle) const {
7036 DCHECK_LE(0, vehicle);
7037 DCHECK_LT(vehicle, vehicle_break_intervals_.size());
7038 return vehicle_break_intervals_[vehicle];
7039 }
7040
GetPreTravelEvaluatorOfVehicle(int vehicle) const7041 int RoutingDimension::GetPreTravelEvaluatorOfVehicle(int vehicle) const {
7042 DCHECK_LE(0, vehicle);
7043 DCHECK_LT(vehicle, vehicle_pre_travel_evaluators_.size());
7044 return vehicle_pre_travel_evaluators_[vehicle];
7045 }
7046
GetPostTravelEvaluatorOfVehicle(int vehicle) const7047 int RoutingDimension::GetPostTravelEvaluatorOfVehicle(int vehicle) const {
7048 DCHECK_LE(0, vehicle);
7049 DCHECK_LT(vehicle, vehicle_post_travel_evaluators_.size());
7050 return vehicle_post_travel_evaluators_[vehicle];
7051 }
7052
SetBreakDistanceDurationOfVehicle(int64_t distance,int64_t duration,int vehicle)7053 void RoutingDimension::SetBreakDistanceDurationOfVehicle(int64_t distance,
7054 int64_t duration,
7055 int vehicle) {
7056 DCHECK_LE(0, vehicle);
7057 DCHECK_LT(vehicle, model_->vehicles());
7058 if (!break_constraints_are_initialized_) InitializeBreaks();
7059 vehicle_break_distance_duration_[vehicle].emplace_back(distance, duration);
7060 // When a vehicle has breaks, if its start and end are fixed,
7061 // then propagation keeps the cumuls min and max on its path feasible.
7062 model_->AddVariableTargetToFinalizer(CumulVar(model_->End(vehicle)),
7063 std::numeric_limits<int64_t>::min());
7064 model_->AddVariableTargetToFinalizer(CumulVar(model_->Start(vehicle)),
7065 std::numeric_limits<int64_t>::max());
7066 }
7067
7068 const std::vector<std::pair<int64_t, int64_t>>&
GetBreakDistanceDurationOfVehicle(int vehicle) const7069 RoutingDimension::GetBreakDistanceDurationOfVehicle(int vehicle) const {
7070 DCHECK_LE(0, vehicle);
7071 DCHECK_LT(vehicle, vehicle_break_distance_duration_.size());
7072 return vehicle_break_distance_duration_[vehicle];
7073 }
7074
SetPickupToDeliveryLimitFunctionForPair(PickupToDeliveryLimitFunction limit_function,int pair_index)7075 void RoutingDimension::SetPickupToDeliveryLimitFunctionForPair(
7076 PickupToDeliveryLimitFunction limit_function, int pair_index) {
7077 CHECK_GE(pair_index, 0);
7078 if (pair_index >= pickup_to_delivery_limits_per_pair_index_.size()) {
7079 pickup_to_delivery_limits_per_pair_index_.resize(pair_index + 1);
7080 }
7081 pickup_to_delivery_limits_per_pair_index_[pair_index] =
7082 std::move(limit_function);
7083 }
7084
HasPickupToDeliveryLimits() const7085 bool RoutingDimension::HasPickupToDeliveryLimits() const {
7086 return !pickup_to_delivery_limits_per_pair_index_.empty();
7087 }
7088
GetPickupToDeliveryLimitForPair(int pair_index,int pickup,int delivery) const7089 int64_t RoutingDimension::GetPickupToDeliveryLimitForPair(int pair_index,
7090 int pickup,
7091 int delivery) const {
7092 DCHECK_GE(pair_index, 0);
7093
7094 if (pair_index >= pickup_to_delivery_limits_per_pair_index_.size()) {
7095 return std::numeric_limits<int64_t>::max();
7096 }
7097 const PickupToDeliveryLimitFunction& pickup_to_delivery_limit_function =
7098 pickup_to_delivery_limits_per_pair_index_[pair_index];
7099 if (!pickup_to_delivery_limit_function) {
7100 // No limit function set for this pair.
7101 return std::numeric_limits<int64_t>::max();
7102 }
7103 DCHECK_GE(pickup, 0);
7104 DCHECK_GE(delivery, 0);
7105 return pickup_to_delivery_limit_function(pickup, delivery);
7106 }
7107
SetupSlackAndDependentTransitCosts() const7108 void RoutingDimension::SetupSlackAndDependentTransitCosts() const {
7109 if (model_->vehicles() == 0) return;
7110 // Figure out whether all vehicles have the same span cost coefficient or not.
7111 bool all_vehicle_span_costs_are_equal = true;
7112 for (int i = 1; i < model_->vehicles(); ++i) {
7113 all_vehicle_span_costs_are_equal &= vehicle_span_cost_coefficients_[i] ==
7114 vehicle_span_cost_coefficients_[0];
7115 }
7116
7117 if (all_vehicle_span_costs_are_equal &&
7118 vehicle_span_cost_coefficients_[0] == 0) {
7119 return; // No vehicle span cost.
7120 }
7121
7122 // Make sure that the vehicle's start cumul will be maximized in the end;
7123 // and that the vehicle's end cumul and the node's slacks will be minimized.
7124 // Note that we don't do that if there was no span cost (see the return
7125 // clause above), because in that case we want the dimension cumul to
7126 // remain unconstrained. Since transitions depend on base dimensions, we
7127 // have to make sure the slacks of base dimensions are taken care of.
7128 // Also, it makes more sense to make decisions from the root of the tree
7129 // towards to leaves, and hence the slacks are pushed in reverse order.
7130 std::vector<const RoutingDimension*> dimensions_with_relevant_slacks = {this};
7131 while (true) {
7132 const RoutingDimension* next =
7133 dimensions_with_relevant_slacks.back()->base_dimension_;
7134 if (next == nullptr || next == dimensions_with_relevant_slacks.back()) {
7135 break;
7136 }
7137 dimensions_with_relevant_slacks.push_back(next);
7138 }
7139
7140 for (auto it = dimensions_with_relevant_slacks.rbegin();
7141 it != dimensions_with_relevant_slacks.rend(); ++it) {
7142 for (int i = 0; i < model_->vehicles(); ++i) {
7143 model_->AddVariableTargetToFinalizer((*it)->cumuls_[model_->End(i)],
7144 std::numeric_limits<int64_t>::min());
7145 model_->AddVariableTargetToFinalizer((*it)->cumuls_[model_->Start(i)],
7146 std::numeric_limits<int64_t>::max());
7147 }
7148 for (IntVar* const slack : (*it)->slacks_) {
7149 model_->AddVariableTargetToFinalizer(slack,
7150 std::numeric_limits<int64_t>::min());
7151 }
7152 }
7153 }
7154 } // namespace operations_research
7155