OR-Tools  9.2
routing.cc
Go to the documentation of this file.
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
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"
71#include "ortools/graph/graph.h"
73#include "ortools/util/bitset.h"
79#include "ortools/util/stats.h"
80
81namespace operations_research {
82class Cross;
83class Exchange;
84class ExtendedSwapActiveOperator;
85class LocalSearchPhaseParameters;
86class MakeActiveAndRelocate;
87class MakeActiveOperator;
88class MakeChainInactiveOperator;
89class MakeInactiveOperator;
90class Relocate;
91class RelocateAndMakeActiveOperator;
92class SwapActiveOperator;
93class 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
101namespace operations_research {
102
103namespace {
104using 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.
109class SetValuesFromTargets : public DecisionBuilder {
110 public:
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 }
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:
159 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
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
177namespace {
178
179bool 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
197bool 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.
210void 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
253class SetCumulsFromLocalDimensionCosts : public DecisionBuilder {
254 public:
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 }
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);
332 should_fail = true;
333 break;
334 }
335 // If relaxation is not feasible, try the MILP optimizer.
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) ==
343 should_fail = true;
344 break;
345 }
346 } else {
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
385class SetCumulsFromGlobalDimensionCosts : public DecisionBuilder {
386 public:
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 }
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;
420 !model->GetDimensionResourceGroupIndices(dimension).empty()
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
431 should_fail = true;
432 break;
433 }
434 // If relaxation is not feasible, try the MILP optimizer.
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 {
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
510class SetCumulsFromResourceAssignmentCosts : public DecisionBuilder {
511 public:
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
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
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(),
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 =
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
677 sweep_arranger_.reset(sweep_arranger);
678}
679
681 return sweep_arranger_.get();
682}
683
684namespace {
685// Constraint which ensures that var != values.
686class DifferentFromValues : public Constraint {
687 public:
688 DifferentFromValues(Solver* solver, IntVar* var, std::vector<int64_t> values)
689 : Constraint(solver), var_(var), values_(std::move(values)) {}
690 void Post() override {}
691 void InitialPropagate() override { var_->RemoveValues(values_); }
692 std::string DebugString() const override { return "DifferentFromValues"; }
693 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.
717template <typename F>
718class LightFunctionElementConstraint : public Constraint {
719 public:
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)) {}
728 ~LightFunctionElementConstraint() override {}
729
730 void Post() override {
731 Demon* demon = MakeConstraintDemon0(
732 solver(), this, &LightFunctionElementConstraint::IndexBound,
733 "IndexBound");
734 index_->WhenBound(demon);
735 }
736
737 void InitialPropagate() override {
738 if (index_->Bound()) {
739 IndexBound();
740 }
741 }
742
743 std::string DebugString() const override {
744 return "LightFunctionElementConstraint";
745 }
746
747 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:
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
770template <typename F>
771Constraint* 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.
783template <typename F>
784class LightFunctionElement2Constraint : public Constraint {
785 public:
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)) {}
796 ~LightFunctionElement2Constraint() override {}
797 void Post() override {
798 Demon* demon = MakeConstraintDemon0(
799 solver(), this, &LightFunctionElement2Constraint::IndexBound,
800 "IndexBound");
801 index1_->WhenBound(demon);
802 index2_->WhenBound(demon);
803 }
804 void InitialPropagate() override { IndexBound(); }
805
806 std::string DebugString() const override {
807 return "LightFunctionElement2Constraint";
808 }
809
810 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:
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_;
844 std::function<bool()> deep_serialize_;
845};
846
847template <typename F>
848Constraint* 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
857template <class A, class B>
858static int64_t ReturnZero(A a, B b) {
859 return 0;
860}
861
862bool 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
878static const int kUnassigned = -1;
879const int64_t RoutingModel::kNoPenalty = -1;
880
882
884
886 : RoutingModel(index_manager, DefaultRoutingModelParameters()) {}
887
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()
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
952void 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
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
994namespace {
995int RegisterCallback(RoutingTransitCallback2 callback, bool is_positive,
997 if (is_positive) {
998 return model->RegisterPositiveTransitCallback(std::move(callback));
999 }
1000 return model->RegisterTransitCallback(std::move(callback));
1001}
1002
1003int 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
1012int 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
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
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
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
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
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
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) {
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
1108void 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
1115bool 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
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
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
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
1153bool 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
1163bool 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
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
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
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
1223namespace {
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.
1227class RangeMakeElementExpr : public BaseIntExpr {
1228 public:
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
1236 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)
1242 }
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 }
1261 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)
1267 }
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 }
1286 void WhenRange(Demon* d) override { index_->WhenRange(d); }
1287
1288 private:
1289 const RangeIntToIntFunction* const callback_;
1290 IntVar* const index_;
1291};
1292
1293IntExpr* 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
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);
1307 pure_transits, dependent_transits, base_dimension, slack_max,
1308 std::move(vehicle_capacities), fix_start_cumul_to_zero, name);
1309}
1310
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) {
1316 /*zero_evaluator*/ 0, transit, dimension, slack_max, vehicle_capacity,
1317 fix_start_cumul_to_zero, name);
1318}
1319
1320bool 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
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
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
1365std::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
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
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
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
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
1422bool RoutingModel::HasDimension(const std::string& dimension_name) const {
1423 return gtl::ContainsKey(dimension_name_to_index_, dimension_name);
1424}
1425
1426RoutingModel::DimensionIndex RoutingModel::GetDimensionIndex(
1427 const std::string& dimension_name) const {
1428 return gtl::FindWithDefault(dimension_name_to_index_, dimension_name,
1429 kNoDimension);
1430}
1431
1433 const std::string& dimension_name) const {
1434 return *dimensions_[gtl::FindOrDie(dimension_name_to_index_, dimension_name)];
1435}
1436
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
1447ResourceGroup::Attributes::Attributes()
1448 : start_domain_(Domain::AllValues()), end_domain_(Domain::AllValues()) {
1450}
1451
1453 Domain end_domain)
1454 : start_domain_(std::move(start_domain)),
1455 end_domain_(std::move(end_domain)) {}
1456
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
1466void 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
1478const ResourceGroup::Attributes& ResourceGroup::Resource::GetDefaultAttributes()
1479 const {
1480 static const Attributes* const kAttributes = new Attributes();
1481 return *kAttributes;
1482}
1483
1485 resource_groups_.push_back(absl::make_unique<ResourceGroup>(this));
1486 return resource_groups_.size() - 1;
1487}
1488
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
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
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
1522 CHECK_LT(0, vehicles_);
1523 for (int i = 0; i < vehicles_; ++i) {
1524 SetArcCostEvaluatorOfVehicle(evaluator_index, i);
1525 }
1526}
1527
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
1536 for (int i = 0; i < vehicles_; ++i) {
1538 }
1539}
1540
1541int64_t RoutingModel::GetFixedCostOfVehicle(int vehicle) const {
1542 CHECK_LT(vehicle, vehicles_);
1543 return fixed_cost_of_vehicle_[vehicle];
1544}
1545
1546void 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
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
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
1572namespace {
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.
1575struct CostClassComparator {
1576 bool operator()(const RoutingModel::CostClass& a,
1577 const RoutingModel::CostClass& b) const {
1579 }
1580};
1581
1582struct VehicleClassComparator {
1583 bool operator()(const RoutingModel::VehicleClass& a,
1584 const RoutingModel::VehicleClass& b) const {
1586 }
1587};
1588} // namespace
1589
1590// static
1591const RoutingModel::CostClassIndex RoutingModel::kCostClassIndexOfZeroCost =
1592 CostClassIndex(0);
1593
1594void 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
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
1673void 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) {
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
1734void 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 = {
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
1781void 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.
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
1820void 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
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
1934 for (const auto& [indices, value] : disjunctions_) {
1935 if (value.penalty == kNoPenalty) return true;
1936 }
1937 return false;
1938}
1939
1941 for (const auto& [indices, value] : disjunctions_) {
1942 if (indices.size() > value.max_cardinality) return true;
1943 }
1944 return false;
1945}
1946
1947std::vector<std::pair<int64_t, int64_t>>
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
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
1981IntVar* 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
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
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
2028void RoutingModel::AddPickupAndDelivery(int64_t pickup, int64_t delivery) {
2029 AddPickupAndDeliverySetsInternal({pickup}, {delivery});
2030 pickup_delivery_disjunctions_.push_back({kNoDisjunction, kNoDisjunction});
2031}
2032
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
2043void 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
2066const 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
2072const 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
2079 PickupAndDeliveryPolicy policy, int vehicle) {
2080 CHECK_LT(vehicle, vehicles_);
2081 vehicle_pickup_delivery_policy_[vehicle] = policy;
2082}
2083
2085 PickupAndDeliveryPolicy policy) {
2086 CHECK_LT(0, vehicles_);
2087 for (int i = 0; i < vehicles_; ++i) {
2089 }
2090}
2091
2094 CHECK_LT(vehicle, vehicles_);
2095 return vehicle_pickup_delivery_policy_[vehicle];
2096}
2097
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
2110IntVar* 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
2141 extra_operators_.push_back(ls_operator);
2142}
2143
2144int64_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.
2150void 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
2177void 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
2217int 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
2225std::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() ==
2237 sweep_arranger() == nullptr) {
2238 return "Undefined sweep arranger for ROUTING_SWEEP strategy.";
2239 }
2240 return "";
2241}
2242
2243void RoutingModel::QuietCloseModel() {
2244 QuietCloseModelWithParameters(DefaultRoutingSearchParameters());
2245}
2246
2249}
2250
2252 public:
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 }
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 }
2281 void EndVisitConstraint(const std::string& type_name,
2282 const Constraint* const constraint) override {
2283 gtl::FindWithDefault(constraint_inspectors_, type_name, []() {})();
2284 }
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 }
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
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
2379void 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
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) {
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],
2662 }
2663 AddVariableTargetToFinalizer(dimension->CumulVar(End(vehicle)),
2665 AddVariableTargetToFinalizer(dimension->CumulVar(Start(vehicle)),
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);
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]) {
2770 break;
2772 lifo_vehicles.push_back(Start(i));
2773 break;
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(),
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(),
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(),
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(),
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
2943namespace {
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.
2950class RestoreDimensionValuesForUnchangedRoutes : public DecisionBuilder {
2951 public:
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.
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.
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
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
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
3102 return model->solver()->RevAlloc(
3103 new RestoreDimensionValuesForUnchangedRoutes(model));
3104}
3105
3107 monitors_.push_back(monitor);
3108}
3109
3110namespace {
3111class AtSolutionCallbackMonitor : public SearchMonitor {
3112 public:
3113 AtSolutionCallbackMonitor(Solver* solver, std::function<void()> callback)
3114 : SearchMonitor(solver), callback_(std::move(callback)) {}
3115 bool AtSolution() override {
3116 callback_();
3117 return false;
3118 }
3119
3120 private:
3121 std::function<void()> callback_;
3122};
3123} // namespace
3124
3126 AddSearchMonitor(solver_->RevAlloc(
3127 new AtSolutionCallbackMonitor(solver_.get(), std::move(callback))));
3128}
3129
3130const Assignment* RoutingModel::Solve(const Assignment* assignment) {
3131 return SolveFromAssignmentWithParameters(assignment,
3133}
3134
3137 std::vector<const Assignment*>* solutions) {
3138 return SolveFromAssignmentWithParameters(nullptr, parameters, solutions);
3139}
3140
3141namespace {
3142absl::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
3147absl::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
3154namespace {
3155void 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
3170bool 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
3184void 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
3202 const Assignment* assignment, const RoutingSearchParameters& parameters,
3203 std::vector<const Assignment*>* solutions) {
3205 solutions);
3206}
3207
3209 const std::vector<const Assignment*>& assignments,
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, &parameters]() {
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(),
3226 parameters.solution_limit());
3227 ls_limit_->UpdateLimits(time_left, std::numeric_limits<int64_t>::max(),
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(
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(),
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
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
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.
3389 if (!closed_) {
3390 LOG(WARNING) << "Non-closed model not supported.";
3391 return 0;
3392 }
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);
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
3438bool 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
3454bool 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
3533 const Assignment& assignment) const {
3534 return CompactAssignmentInternal(assignment, false);
3535}
3536
3538 const Assignment& assignment) const {
3539 return CompactAssignmentInternal(assignment, true);
3540}
3541
3542Assignment* RoutingModel::CompactAssignmentInternal(
3543 const Assignment& assignment, bool check_compact_assignment) const {
3544 CHECK_EQ(assignment.solver(), solver_.get());
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
3612int 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
3623IntVar* 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
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
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
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
3666bool 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
3675Assignment* 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
3685 QuietCloseModel();
3686 CHECK(assignment_ != nullptr);
3687 assignment_->CopyIntersection(&solution);
3688 return DoRestoreAssignment();
3689}
3690
3691Assignment* 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
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
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
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
3866std::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
3887int64_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
3927bool RoutingModel::IsStart(int64_t index) const {
3928 return !IsEnd(index) && index_to_vehicle_[index] != kUnassigned;
3929}
3930
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
3941int64_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
3949int64_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,
3954 } else {
3955 return 0;
3956 }
3957}
3958
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
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)
3985 // TODO(user): Take vehicle into account.
3986 return GetHomogeneousCost(from_index, to_index);
3987}
3988
3989int64_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
4004bool 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 =
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
4099void 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
4109 CHECK_LT(index, index_to_visit_type_.size());
4110 return index_to_visit_type_[index];
4111}
4112
4113const 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
4118const 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
4124 int64_t index) const {
4125 CHECK_LT(index, index_to_type_policy_.size());
4126 return index_to_type_policy_[index];
4127}
4128
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
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
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
4156const absl::flat_hash_set<int>&
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
4163const absl::flat_hash_set<int>&
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?
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
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
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
4237const std::vector<absl::flat_hash_set<int>>&
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
4245const std::vector<absl::flat_hash_set<int>>&
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
4252const std::vector<absl::flat_hash_set<int>>&
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
4259int64_t RoutingModel::UnperformedPenalty(int64_t var_index) const {
4260 return UnperformedPenaltyOrValue(0, var_index);
4261}
4262
4263int64_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
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
4350std::vector<std::vector<std::pair<int64_t, int64_t>>>
4351RoutingModel::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
4378Assignment* RoutingModel::GetOrCreateAssignment() {
4379 if (assignment_ == nullptr) {
4380 assignment_ = solver_->MakeAssignment();
4381 assignment_->Add(nexts_);
4383 assignment_->Add(vehicle_vars_);
4384 }
4385 assignment_->AddObjective(cost_);
4386 }
4387 return assignment_;
4388}
4389
4390Assignment* RoutingModel::GetOrCreateTmpAssignment() {
4391 if (tmp_assignment_ == nullptr) {
4392 tmp_assignment_ = solver_->MakeAssignment();
4393 tmp_assignment_->Add(nexts_);
4394 }
4395 return tmp_assignment_;
4396}
4397
4398RegularLimit* RoutingModel::GetOrCreateLimit() {
4399 if (limit_ == nullptr) {
4400 limit_ = solver_->MakeLimit(
4401 absl::InfiniteDuration(), std::numeric_limits<int64_t>::max(),
4403 std::numeric_limits<int64_t>::max(), /*smart_time_check=*/true);
4404 }
4405 return limit_;
4406}
4407
4408RegularLimit* RoutingModel::GetOrCreateLocalSearchLimit() {
4409 if (ls_limit_ == nullptr) {
4410 ls_limit_ = solver_->MakeLimit(absl::InfiniteDuration(),
4413 /*solutions=*/1, /*smart_time_check=*/true);
4414 }
4415 return ls_limit_;
4416}
4417
4418RegularLimit* RoutingModel::GetOrCreateLargeNeighborhoodSearchLimit() {
4419 if (lns_limit_ == nullptr) {
4420 lns_limit_ = solver_->MakeLimit(
4421 absl::InfiniteDuration(), std::numeric_limits<int64_t>::max(),
4423 std::numeric_limits<int64_t>::max(), /*smart_time_check=*/false);
4424 }
4425 return lns_limit_;
4426}
4427
4428RegularLimit*
4429RoutingModel::GetOrCreateFirstSolutionLargeNeighborhoodSearchLimit() {
4430 if (first_solution_lns_limit_ == nullptr) {
4431 first_solution_lns_limit_ = solver_->MakeLimit(
4432 absl::InfiniteDuration(), 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
4439LocalSearchOperator* 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
4455LocalSearchOperator* 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
4466void 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] =
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,
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] =
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, &parameters]() {
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, &parameters]() {
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
4631LocalSearchOperator* 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
4646LocalSearchOperator* 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 !=
4685 local_search_metaheuristic !=
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);
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 !=
4744 local_search_metaheuristic !=
4746 CP_ROUTING_PUSH_OPERATOR(TSP_OPT, tsp_opt, operators);
4747 }
4748 if (local_search_metaheuristic != LocalSearchMetaheuristic::TABU_SEARCH &&
4749 local_search_metaheuristic !=
4751 local_search_metaheuristic !=
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
4767bool 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
4774namespace {
4775
4776void 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
4790std::vector<LocalSearchFilterManager::FilterEvent>
4791RoutingModel::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) {
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() ||
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.
4848 filters.push_back({MakeVehicleVarFilter(*this), kAccept});
4849 }
4850
4851 const PathState* path_state_reference = nullptr;
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
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
4894LocalSearchFilterManager* 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
4904std::vector<LocalSearchFilterManager::FilterEvent>
4905RoutingModel::GetOrCreateFeasibilityFilters(
4906 const RoutingSearchParameters& parameters) {
4907 return GetOrCreateLocalSearchFilters(parameters, false);
4908}
4909
4910LocalSearchFilterManager* 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
4920LocalSearchFilterManager*
4921RoutingModel::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
4934namespace {
4935bool 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
4945void 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 =
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) <
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
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
5074RoutingModel::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
5099DecisionBuilder* RoutingModel::CreateSolutionFinalizer(SearchLimit* lns_limit) {
5100 std::vector<DecisionBuilder*> decision_builders;
5101 decision_builders.push_back(solver_->MakePhase(
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(
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;
5136 if (GetDimensionResourceGroupIndices(dim).size() != 1) continue;
5137
5138 LocalDimensionCumulOptimizer* const optimizer =
5140 DCHECK_NE(optimizer, nullptr);
5141 LocalDimensionCumulOptimizer* const mp_optimizer =
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
5161void RoutingModel::CreateFirstSolutionDecisionBuilders(
5162 const RoutingSearchParameters& search_parameters) {
5163 first_solution_decision_builders_.resize(
5165 first_solution_filtered_decision_builders_.resize(
5167 DecisionBuilder* const finalize_solution =
5168 CreateSolutionFinalizer(GetOrCreateLargeNeighborhoodSearchLimit());
5169 // Default heuristic
5170 first_solution_decision_builders_
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 },
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_
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_
5201 solver_->Try(first_solution_filtered_decision_builders_
5203 first_solution_decision_builders_
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_
5214 solver_->MakePhase(nexts_, Solver::CHOOSE_PATH, comp);
5215 if (!search_parameters.use_unfiltered_first_solution_strategy()) {
5216 first_solution_filtered_decision_builders_
5218 solver_->RevAlloc(new IntVarFilteredDecisionBuilder(
5219 absl::make_unique<ComparatorCheapestAdditionFilteredHeuristic>(
5220 this, comp,
5221 GetOrCreateFeasibilityFilterManager(search_parameters))));
5222 first_solution_decision_builders_
5224 first_solution_filtered_decision_builders_
5226 first_solution_decision_builders_
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_
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(),
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_;
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_
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 =
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_
5314 }
5315
5316 // Local cheapest insertion
5317 first_solution_filtered_decision_builders_
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_
5336 first_solution_filtered_decision_builders_
5338 solver_->Try(strong_lci,
5339 first_solution_decision_builders_
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 =
5428 strategy = FirstSolutionStrategy_Value(strategy + 1)) {
5429 if (first_solution_decision_builders_[strategy] == nullptr ||
5431 continue;
5432 }
5433 const std::string strategy_name =
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
5445DecisionBuilder* 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
5456IntVarFilteredDecisionBuilder*
5457RoutingModel::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
5464LocalSearchPhaseParameters* 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
5474DecisionBuilder* 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);
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
5505void 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
5535void 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) {
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;
5566 optimize =
5567 solver_->MakeSimulatedAnnealing(false, cost_, optimization_step, 100);
5568 break;
5570 optimize = solver_->MakeTabuSearch(false, cost_, optimization_step,
5571 nexts_, 10, 10, .8);
5572 break;
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) {
5590 << " specified without sane timeout: solve may run forever.";
5591 }
5592 monitors_.push_back(optimize);
5593}
5594
5596 tabu_var_callback_ = std::move(tabu_var_callback);
5597}
5598
5599void 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
5624void 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
5645void 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
5658void 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
5667bool 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() !=
5673}
5674
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
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
5697}
5698
5701}
5702
5703void RoutingModel::SetupSearch(
5704 const RoutingSearchParameters& search_parameters) {
5705 SetupDecisionBuilders(search_parameters);
5706 SetupSearchMonitors(search_parameters);
5707}
5708
5710 extra_vars_.push_back(var);
5711}
5712
5714 extra_intervals_.push_back(interval);
5715}
5716
5717namespace {
5718
5719class PathSpansAndTotalSlacks : public Constraint {
5720 public:
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
5735 std::string DebugString() const override { return "PathSpansAndTotalSlacks"; }
5736
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.
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.
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.
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()
5797 const int64_t total_slack_min = total_slacks_[vehicle]
5798 ? total_slacks_[vehicle]->Min()
5800 return std::min(span_min, CapAdd(total_slack_min, sum_fixed_transits));
5801 }
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()
5807 const int64_t total_slack_max = total_slacks_[vehicle]
5808 ? total_slacks_[vehicle]->Max()
5810 return std::max(span_max, CapAdd(total_slack_max, sum_fixed_transits));
5811 }
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 }
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().
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
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 =
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
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
6064const char RoutingModelVisitor::kLightElement[] = "LightElement";
6065const char RoutingModelVisitor::kLightElement2[] = "LightElement2";
6066const char RoutingModelVisitor::kRemoveValues[] = "RemoveValues";
6067
6068RoutingDimension::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(),
6081 vehicle_span_cost_coefficients_.assign(model->vehicles(), 0);
6082}
6083
6084RoutingDimension::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
6090 cumul_var_piecewise_linear_cost_.clear();
6091}
6092
6093void 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
6102namespace {
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.
6112class LightRangeLessOrEqual : public Constraint {
6113 public:
6114 LightRangeLessOrEqual(Solver* const s, IntExpr* const l, IntExpr* const r);
6115 ~LightRangeLessOrEqual() override {}
6116 void Post() override;
6117 void InitialPropagate() override;
6118 std::string DebugString() const override;
6119 IntVar* Var() override {
6120 return solver()->MakeIsLessOrEqualVar(left_, right_);
6121 }
6122 // TODO(user): introduce a kLightLessOrEqual tag.
6123 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
6139LightRangeLessOrEqual::LightRangeLessOrEqual(Solver* const s, IntExpr* const l,
6140 IntExpr* const r)
6141 : Constraint(s), left_(l), right_(r), demon_(nullptr) {}
6142
6143void LightRangeLessOrEqual::Post() {
6144 demon_ = MakeConstraintDemon0(
6145 solver(), this, &LightRangeLessOrEqual::CheckRange, "CheckRange");
6146 left_->WhenRange(demon_);
6147 right_->WhenRange(demon_);
6148}
6149
6150void 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
6158void 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
6167std::string LightRangeLessOrEqual::DebugString() const {
6168 return left_->DebugString() + " < " + right_->DebugString();
6169}
6170
6171} // namespace
6172
6173void 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
6210namespace {
6211template <int64_t value>
6212int64_t IthElementOrValue(const std::vector<int64_t>& v, int64_t index) {
6213 return index >= 0 ? v[index] : value;
6214}
6215
6216void 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
6237void 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(),
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
6323void 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
6346void 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
6357 : model_(model), occurrences_of_type_(model.GetNumberOfVisitTypes()) {}
6358
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);
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
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(),
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
6426}
6427
6429 const TypePolicyOccurrence& occurrences = occurrences_of_type_[type];
6430 return occurrences.num_type_added_to_vehicle > 0 ||
6432}
6433
6435 const TypePolicyOccurrence& occurrences = occurrences_of_type_[type];
6436 return occurrences.num_type_removed_from_vehicle <
6437 occurrences.num_type_added_to_vehicle ||
6439}
6440
6442 const RoutingModel& model, bool check_hard_incompatibilities)
6444 check_hard_incompatibilities_(check_hard_incompatibilities) {}
6445
6446bool TypeIncompatibilityChecker::HasRegulationsToCheck() const {
6448 (check_hard_incompatibilities_ &&
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.
6457bool 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 :
6467 if (TypeCurrentlyOnRoute(incompatible_type, pos)) {
6468 return false;
6469 }
6470 }
6471 if (check_hard_incompatibilities_) {
6472 for (int incompatible_type :
6474 if (TypeOccursOnRoute(incompatible_type)) {
6475 return false;
6476 }
6477 }
6478 }
6479 return true;
6480}
6481
6482bool TypeRequirementChecker::HasRegulationsToCheck() const {
6485}
6486
6487bool 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
6506bool TypeRequirementChecker::CheckTypeRegulations(int type,
6507 VisitTypePolicy policy,
6508 int pos) {
6511 if (!CheckRequiredTypesCurrentlyOnRoute(
6513 return false;
6514 }
6515 }
6517 if (!CheckRequiredTypesCurrentlyOnRoute(
6519 return false;
6520 }
6521 }
6524 types_with_same_vehicle_requirements_on_route_.insert(type);
6525 }
6526 return true;
6527}
6528
6529bool TypeRequirementChecker::FinalizeCheck() const {
6530 for (int type : types_with_same_vehicle_requirements_on_route_) {
6531 for (const absl::flat_hash_set<int>& requirement_alternatives :
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
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
6555void 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
6567void 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
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
6597 for (int vehicle = 0; vehicle < model_.vehicles(); vehicle++) {
6598 CheckRegulationsOnVehicle(vehicle);
6599 }
6600}
6601
6602void 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]
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
6683int64_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
6690 int64_t index, int64_t min_value, int64_t max_value) const {
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;
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
6713 int vehicle) {
6714 CHECK_GE(vehicle, 0);
6715 CHECK_LT(vehicle, vehicle_span_upper_bounds_.size());
6717 vehicle_span_upper_bounds_[vehicle] = upper_bound;
6718}
6719
6721 int vehicle) {
6722 CHECK_GE(vehicle, 0);
6723 CHECK_LT(vehicle, vehicle_span_cost_coefficients_.size());
6725 vehicle_span_cost_coefficients_[vehicle] = coefficient;
6726}
6727
6729 int64_t coefficient) {
6731 vehicle_span_cost_coefficients_.assign(model_->vehicles(), coefficient);
6732}
6733
6736 global_span_cost_coefficient_ = coefficient;
6737}
6738
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
6759 return (index < cumul_var_piecewise_linear_cost_.size() &&
6760 cumul_var_piecewise_linear_cost_[index].var != nullptr);
6761}
6762
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
6772namespace {
6773IntVar* 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
6786void 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
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
6816 return (index < cumul_var_soft_upper_bound_.size() &&
6817 cumul_var_soft_upper_bound_[index].var != nullptr);
6818}
6819
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
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
6837void 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.
6852 soft_bound.coefficient);
6853 }
6854 }
6855}
6856
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
6868 return (index < cumul_var_soft_lower_bound_.size() &&
6869 cumul_var_soft_lower_bound_[index].var != nullptr);
6870}
6871
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
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
6889void 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.
6904 soft_bound.coefficient);
6905 }
6906 }
6907}
6908
6909void 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();
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();
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) {
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
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
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
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]) {
7004 if (interval->MayBePerformed() && !interval->MustBePerformed()) {
7005 model_->AddVariableTargetToFinalizer(interval->PerformedExpr()->Var(), 0);
7006 }
7007 model_->AddVariableTargetToFinalizer(interval->SafeStartExpr(0)->Var(),
7009 model_->AddVariableTargetToFinalizer(interval->SafeDurationExpr(0)->Var(),
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)),
7016 model_->AddVariableTargetToFinalizer(CumulVar(model_->Start(vehicle)),
7018}
7019
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
7031 return break_constraints_are_initialized_;
7032}
7033
7034const 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
7042 DCHECK_LE(0, vehicle);
7043 DCHECK_LT(vehicle, vehicle_pre_travel_evaluators_.size());
7044 return vehicle_pre_travel_evaluators_[vehicle];
7045}
7046
7048 DCHECK_LE(0, vehicle);
7049 DCHECK_LT(vehicle, vehicle_post_travel_evaluators_.size());
7050 return vehicle_post_travel_evaluators_[vehicle];
7051}
7052
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)),
7064 model_->AddVariableTargetToFinalizer(CumulVar(model_->Start(vehicle)),
7066}
7067
7068const std::vector<std::pair<int64_t, int64_t>>&
7070 DCHECK_LE(0, vehicle);
7071 DCHECK_LT(vehicle, vehicle_break_distance_duration_.size());
7072 return vehicle_break_distance_duration_[vehicle];
7073}
7074
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
7086 return !pickup_to_delivery_limits_per_pair_index_.empty();
7087}
7088
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()) {
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.
7102 }
7103 DCHECK_GE(pickup, 0);
7104 DCHECK_GE(delivery, 0);
7105 return pickup_to_delivery_limit_function(pickup, delivery);
7106}
7107
7108void 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)],
7145 model_->AddVariableTargetToFinalizer((*it)->cumuls_[model_->Start(i)],
7147 }
7148 for (IntVar* const slack : (*it)->slacks_) {
7149 model_->AddVariableTargetToFinalizer(slack,
7151 }
7152 }
7153}
7154} // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
std::vector< int > dimensions
#define CHECK(condition)
Definition: base/logging.h:495
#define DCHECK_LE(val1, val2)
Definition: base/logging.h:892
#define DCHECK_NE(val1, val2)
Definition: base/logging.h:891
#define CHECK_LT(val1, val2)
Definition: base/logging.h:705
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:702
#define CHECK_GE(val1, val2)
Definition: base/logging.h:706
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:894
#define CHECK_NE(val1, val2)
Definition: base/logging.h:703
#define DCHECK_GT(val1, val2)
Definition: base/logging.h:895
#define DCHECK_LT(val1, val2)
Definition: base/logging.h:893
#define LOG(severity)
Definition: base/logging.h:420
#define DCHECK(condition)
Definition: base/logging.h:889
#define CHECK_LE(val1, val2)
Definition: base/logging.h:704
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:890
#define ABSL_DIE_IF_NULL
Definition: base/logging.h:43
#define VLOG(verboselevel)
Definition: base/logging.h:983
size_type size() const
An Assignment is a variable -> domains mapping, used to report solutions to the user.
bool Contains(const IntVar *const var) const
void SetValue(const IntVar *const var, int64_t value)
int64_t Max(const IntVar *const var) const
int64_t Value(const IntVar *const var) const
void CopyIntersection(const Assignment *assignment)
Copies the intersection of the two assignments to the current assignment.
void Copy(const Assignment *assignment)
Copies 'assignment' to the current assignment, clearing its previous content.
IntVarElement * Add(IntVar *const var)
bool Bound(const IntVar *const var) const
int64_t Min(const IntVar *const var) const
A constraint is the main modeling object.
A DecisionBuilder is responsible for creating the search tree.
A Demon is the base element of a propagation queue.
void inhibit(Solver *const s)
This method inhibits the demon in the search tree below the current position.
We call domain any subset of Int64 = [kint64min, kint64max].
ArcIndexType AddArc(NodeIndexType tail, NodeIndexType head)
Definition: ebert_graph.h:1002
static constexpr Value PARALLEL_CHEAPEST_INSERTION
static const std::string & Value_Name(T enum_t_value)
static constexpr Value SEQUENTIAL_CHEAPEST_INSERTION
Utility class to encapsulate an IntVarIterator and use it in a range-based loop.
The class IntExpr is the base of all integer expressions in constraint programming.
virtual bool Bound() const
Returns true if the min and the max of the expression are equal.
virtual void SetValue(int64_t v)
This method sets the value of the expression.
virtual int64_t Min() const =0
virtual IntVar * Var()=0
Creates a variable from the expression.
virtual int64_t Max() const =0
Decision builder building a solution using heuristics with local search filters to evaluate its feasi...
int64_t number_of_decisions() const
Returns statistics from its underlying heuristic.
The class IntVar is a subset of IntExpr.
virtual bool Contains(int64_t v) const =0
This method returns whether the value 'v' is in the domain of the variable.
virtual void WhenBound(Demon *d)=0
This method attaches a demon that will be awakened when the variable is bound.
virtual int64_t Value() const =0
This method returns the value of the variable.
virtual uint64_t Size() const =0
This method returns the number of values in the domain of the variable.
Interval variables are often used in scheduling.
virtual IntExpr * SafeStartExpr(int64_t unperformed_value)=0
These methods create expressions encapsulating the start, end and duration of the interval var.
virtual IntExpr * SafeEndExpr(int64_t unperformed_value)=0
void SetArcCost(ArcIndex arc, CostValue cost)
static const std::string & Value_Name(T enum_t_value)
The base class for all local search operators.
static int64_t FastInt64Round(double x)
Definition: mathutil.h:138
void EnqueueDelayedDemon(Demon *const d)
This method pushes the demon onto the propagation queue.
Usual limit based on wall_time, number of explored branches and number of failures in the search tree...
void UpdateLimits(absl::Duration time, int64_t branches, int64_t failures, int64_t solutions)
Definition: search.cc:4114
Dimensions represent quantities accumulated at nodes along the routes.
Definition: routing.h:2562
void SetSpanCostCoefficientForAllVehicles(int64_t coefficient)
Definition: routing.cc:6728
void SetCumulVarPiecewiseLinearCost(int64_t index, const PiecewiseLinearFunction &cost)
Sets a piecewise linear cost on the cumul variable of a given variable index.
Definition: routing.cc:6739
const std::string & name() const
Returns the name of the dimension.
Definition: routing.h:2826
const std::vector< IntVar * > & cumuls() const
Like CumulVar(), TransitVar(), SlackVar() but return the whole variable vectors instead (indexed by i...
Definition: routing.h:2591
const std::vector< int64_t > & vehicle_capacities() const
Returns the capacities for all vehicles.
Definition: routing.h:2640
bool HasCumulVarPiecewiseLinearCost(int64_t index) const
Returns true if a piecewise linear cost has been set for a given variable index.
Definition: routing.cc:6758
int64_t GetCumulVarSoftUpperBoundCoefficient(int64_t index) const
Returns the cost coefficient of the soft upper bound of a cumul variable for a given variable index.
Definition: routing.cc:6828
IntVar * CumulVar(int64_t index) const
Get the cumul, transit and slack variables for the given node (given as int64_t var index).
Definition: routing.h:2581
int64_t GetPickupToDeliveryLimitForPair(int pair_index, int pickup, int delivery) const
Definition: routing.cc:7089
bool HasCumulVarSoftLowerBound(int64_t index) const
Returns true if a soft lower bound has been set for a given variable index.
Definition: routing.cc:6867
void SetBreakDistanceDurationOfVehicle(int64_t distance, int64_t duration, int vehicle)
With breaks supposed to be consecutive, this forces the distance between breaks of size at least mini...
Definition: routing.cc:7053
const RoutingModel::TransitCallback2 & transit_evaluator(int vehicle) const
Returns the callback evaluating the transit value between two node indices for a given vehicle.
Definition: routing.h:2645
const std::vector< int64_t > & vehicle_span_cost_coefficients() const
Definition: routing.h:2893
bool HasBreakConstraints() const
Returns true if any break interval or break distance was defined.
Definition: routing.cc:7030
SortedDisjointIntervalList GetAllowedIntervalsInRange(int64_t index, int64_t min_value, int64_t max_value) const
Returns allowed intervals for a given node in a given interval.
Definition: routing.cc:6689
void InitializeBreaks()
Sets up vehicle_break_intervals_, vehicle_break_distance_duration_, pre_travel_evaluators and post_tr...
Definition: routing.cc:7020
std::function< int64_t(int, int)> PickupToDeliveryLimitFunction
Limits, in terms of maximum difference between the cumul variables, between the pickup and delivery a...
Definition: routing.h:2844
int GetPreTravelEvaluatorOfVehicle(int vehicle) const
!defined(SWIGPYTHON)
Definition: routing.cc:7041
bool HasCumulVarSoftUpperBound(int64_t index) const
Returns true if a soft upper bound has been set for a given variable index.
Definition: routing.cc:6815
RoutingModel * model() const
Returns the model on which the dimension was created.
Definition: routing.h:2566
const std::vector< IntervalVar * > & GetBreakIntervalsOfVehicle(int vehicle) const
Returns the break intervals set by SetBreakIntervalsOfVehicle().
Definition: routing.cc:7034
void SetPickupToDeliveryLimitFunctionForPair(PickupToDeliveryLimitFunction limit_function, int pair_index)
Definition: routing.cc:7075
int vehicle_to_class(int vehicle) const
Definition: routing.h:2673
const PiecewiseLinearFunction * GetCumulVarPiecewiseLinearCost(int64_t index) const
Returns the piecewise linear cost of a cumul variable for a given variable index.
Definition: routing.cc:6763
int64_t GetTransitValue(int64_t from_index, int64_t to_index, int64_t vehicle) const
Returns the transition value for a given pair of nodes (as var index); this value is the one taken by...
Definition: routing.cc:6683
void SetCumulVarSoftUpperBound(int64_t index, int64_t upper_bound, int64_t coefficient)
Sets a soft upper bound to the cumul variable of a given variable index.
Definition: routing.cc:6805
void SetBreakIntervalsOfVehicle(std::vector< IntervalVar * > breaks, int vehicle, int pre_travel_evaluator, int post_travel_evaluator)
Sets the breaks for a given vehicle.
Definition: routing.cc:6991
int64_t GetCumulVarSoftUpperBound(int64_t index) const
Returns the soft upper bound of a cumul variable for a given variable index.
Definition: routing.cc:6820
const std::vector< std::pair< int64_t, int64_t > > & GetBreakDistanceDurationOfVehicle(int vehicle) const
Returns the pairs (distance, duration) specified by break distance constraints.
Definition: routing.cc:7069
void SetSpanUpperBoundForVehicle(int64_t upper_bound, int vehicle)
!defined(SWIGCSHARP) && !defined(SWIGJAVA) !defined(SWIGPYTHON)
Definition: routing.cc:6712
void SetGlobalSpanCostCoefficient(int64_t coefficient)
Sets a cost proportional to the global dimension span, that is the difference between the largest val...
Definition: routing.cc:6734
int64_t GetCumulVarSoftLowerBoundCoefficient(int64_t index) const
Returns the cost coefficient of the soft lower bound of a cumul variable for a given variable index.
Definition: routing.cc:6880
int GetPostTravelEvaluatorOfVehicle(int vehicle) const
Definition: routing.cc:7047
void SetSpanCostCoefficientForVehicle(int64_t coefficient, int vehicle)
Sets a cost proportional to the dimension span on a given vehicle, or on all vehicles at once.
Definition: routing.cc:6720
void SetCumulVarSoftLowerBound(int64_t index, int64_t lower_bound, int64_t coefficient)
Sets a soft lower bound to the cumul variable of a given variable index.
Definition: routing.cc:6857
int64_t GetCumulVarSoftLowerBound(int64_t index) const
Returns the soft lower bound of a cumul variable for a given variable index.
Definition: routing.cc:6872
Manager for any NodeIndex <-> variable index conversion.
NodeIndex IndexToNode(int64_t index) const
std::vector< NodeIndex > GetIndexToNodeMap() const
A Resource sets attributes (costs/constraints) for a set of dimensions.
Definition: routing.h:416
const ResourceGroup::Attributes & GetDimensionAttributes(const RoutingDimension *dimension) const
Definition: routing.cc:1458
A ResourceGroup defines a set of available Resources with attributes on one or multiple dimensions.
Definition: routing.h:395
const std::vector< int > & GetVehiclesRequiringAResource() const
Definition: routing.h:447
bool VehicleRequiresAResource(int vehicle) const
Definition: routing.h:451
int AddResource(Attributes attributes, const RoutingDimension *dimension)
Adds a Resource with the given attributes for the corresponding dimension.
Definition: routing.cc:1489
const absl::flat_hash_set< DimensionIndex > & GetAffectedDimensionIndices() const
Definition: routing.h:460
void NotifyVehicleRequiresAResource(int vehicle)
Notifies that the given vehicle index requires a resource from this group if the vehicle is used (i....
Definition: routing.cc:1506
int64_t ComputeLowerBound()
Computes a lower bound to the routing problem solving a linear assignment problem.
Definition: routing.cc:3388
void AddAtSolutionCallback(std::function< void()> callback)
Adds a callback called each time a solution is found during the search.
Definition: routing.cc:3125
const Assignment * SolveFromAssignmentsWithParameters(const std::vector< const Assignment * > &assignments, const RoutingSearchParameters &search_parameters, std::vector< const Assignment * > *solutions=nullptr)
Same as above but will try all assignments in order as first solutions until one succeeds.
Definition: routing.cc:3208
Solver * solver() const
Returns the underlying constraint solver.
Definition: routing.h:1498
const TransitCallback2 & TransitCallback(int callback_index) const
Definition: routing.h:505
Assignment * RestoreAssignment(const Assignment &solution)
Restores an assignment as a solution in the routing model and returns the new solution.
Definition: routing.cc:3684
std::function< std::vector< operations_research::IntVar * >(RoutingModel *)> GetTabuVarsCallback
Sets the callback returning the variable to use for the Tabu Search metaheuristic.
Definition: routing.h:1539
void AddSearchMonitor(SearchMonitor *const monitor)
Adds a search monitor to the search used to solve the routing model.
Definition: routing.cc:3106
bool AddDimensionDependentDimensionWithVehicleCapacity(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)
Creates a dimension with transits depending on the cumuls of another dimension.
Definition: routing.h:602
VehicleClassIndex GetVehicleClassIndexOfVehicle(int64_t vehicle) const
Definition: routing.h:1429
void AddLocalSearchOperator(LocalSearchOperator *ls_operator)
Adds a local search operator to the set of operators used to solve the vehicle routing problem.
Definition: routing.cc:2140
std::pair< int, bool > AddMatrixDimension(std::vector< std::vector< int64_t > > values, int64_t capacity, bool fix_start_cumul_to_zero, const std::string &name)
Creates a dimension where the transit variable is constrained to be equal to 'values[i][next(i)]' for...
Definition: routing.cc:1214
const std::vector< int > & GetPairIndicesOfType(int type) const
Definition: routing.cc:4118
RoutingTransitCallback1 TransitCallback1
Definition: routing.h:239
const absl::flat_hash_set< int > & GetTemporalTypeIncompatibilitiesOfType(int type) const
Definition: routing.cc:4164
const std::vector< int > & GetDimensionResourceGroupIndices(const RoutingDimension *dimension) const
Returns the indices of resource groups for this dimension.
Definition: routing.cc:1513
std::string DebugOutputAssignment(const Assignment &solution_assignment, const std::string &dimension_to_print) const
Print some debugging information about an assignment, including the feasible intervals of the CumulVa...
Definition: routing.cc:4277
const std::vector< absl::flat_hash_set< int > > & GetRequiredTypeAlternativesWhenRemovingType(int type) const
Returns the set of requirement alternatives when removing the given type.
Definition: routing.cc:4253
bool HasMandatoryDisjunctions() const
Returns true if the model contains mandatory disjunctions (ones with kNoPenalty as penalty).
Definition: routing.cc:1933
int GetVehicleClassesCount() const
Returns the number of different vehicle classes in the model.
Definition: routing.h:1449
int64_t GetFixedCostOfVehicle(int vehicle) const
Returns the route fixed cost taken into account if the route of the vehicle is not empty,...
Definition: routing.cc:1541
std::pair< int, bool > AddVectorDimension(std::vector< int64_t > values, int64_t capacity, bool fix_start_cumul_to_zero, const std::string &name)
Creates a dimension where the transit variable is constrained to be equal to 'values[i]' for node i; ...
Definition: routing.cc:1205
PickupAndDeliveryPolicy GetPickupAndDeliveryPolicyOfVehicle(int vehicle) const
Definition: routing.cc:2093
bool IsStart(int64_t index) const
Returns true if 'index' represents the first node of a route.
Definition: routing.cc:3927
void SetPickupAndDeliveryPolicyOfAllVehicles(PickupAndDeliveryPolicy policy)
Sets the Pickup and delivery policy of all vehicles.
Definition: routing.cc:2084
void AddSoftSameVehicleConstraint(const std::vector< int64_t > &indices, int64_t cost)
Adds a soft constraint to force a set of variable indices to be on the same vehicle.
Definition: routing.cc:2007
Assignment * ReadAssignment(const std::string &file_name)
Reads an assignment from a file and returns the current solution.
Definition: routing.cc:3675
Assignment * CompactAssignment(const Assignment &assignment) const
Returns a compacted version of the given assignment, in which all vehicles with id lower or equal to ...
Definition: routing.cc:3532
int RegisterStateDependentTransitCallback(VariableIndexEvaluator2 callback)
Definition: routing.cc:1091
const std::vector< std::pair< int, int > > & GetDeliveryIndexPairs(int64_t node_index) const
Same as above for deliveries.
Definition: routing.cc:2072
void AddToAssignment(IntVar *const var)
Adds an extra variable to the vehicle routing assignment.
Definition: routing.cc:5709
IntVar * VehicleVar(int64_t index) const
Returns the vehicle variable of the node corresponding to index.
Definition: routing.h:1369
void AddVariableMinimizedByFinalizer(IntVar *var)
Adds a variable to minimize in the solution finalizer.
Definition: routing.cc:5699
VisitTypePolicy
Set the node visit types and incompatibilities/requirements between the types (see below).
Definition: routing.h:904
@ TYPE_ADDED_TO_VEHICLE
When visited, the number of types 'T' on the vehicle increases by one.
Definition: routing.h:906
@ ADDED_TYPE_REMOVED_FROM_VEHICLE
When visited, one instance of type 'T' previously added to the route (TYPE_ADDED_TO_VEHICLE),...
Definition: routing.h:911
@ TYPE_ON_VEHICLE_UP_TO_VISIT
With the following policy, the visit enforces that type 'T' is considered on the route from its start...
Definition: routing.h:914
@ TYPE_SIMULTANEOUSLY_ADDED_AND_REMOVED
The visit doesn't have an impact on the number of types 'T' on the route, as it's (virtually) added a...
Definition: routing.h:919
GlobalDimensionCumulOptimizer * GetMutableGlobalCumulMPOptimizer(const RoutingDimension &dimension) const
Definition: routing.cc:1386
Constraint * MakePathSpansAndTotalSlacks(const RoutingDimension *dimension, std::vector< IntVar * > spans, std::vector< IntVar * > total_slacks)
For every vehicle of the routing model:
Definition: routing.cc:6055
int64_t GetHomogeneousCost(int64_t from_index, int64_t to_index) const
Returns the cost of the segment between two nodes supposing all vehicle costs are the same (returns t...
Definition: routing.h:1391
LocalDimensionCumulOptimizer * GetMutableLocalCumulOptimizer(const RoutingDimension &dimension) const
Definition: routing.cc:1398
int RegisterUnaryTransitVector(std::vector< int64_t > values)
Registers 'callback' and returns its index.
Definition: routing.cc:1012
const std::vector< absl::flat_hash_set< int > > & GetSameVehicleRequiredTypeAlternativesOfType(int type) const
Returns the set of same-vehicle requirement alternatives for the given type.
Definition: routing.cc:4238
int64_t Size() const
Returns the number of next variables in the model.
Definition: routing.h:1518
RoutingDimension * GetMutableDimension(const std::string &dimension_name) const
Returns a dimension from its name.
Definition: routing.cc:1437
int GetVisitType(int64_t index) const
Definition: routing.cc:4108
bool RoutesToAssignment(const std::vector< std::vector< int64_t > > &routes, bool ignore_inactive_indices, bool close_routes, Assignment *const assignment) const
Fills an assignment from a specification of the routes of the vehicles.
Definition: routing.cc:3706
bool HasTemporalTypeRequirements() const
Definition: routing.h:1001
IntVar * NextVar(int64_t index) const
!defined(SWIGPYTHON)
Definition: routing.h:1353
static const int64_t kNoPenalty
Constant used to express a hard constraint instead of a soft penalty.
Definition: routing.h:476
void AddPickupAndDeliverySets(DisjunctionIndex pickup_disjunction, DisjunctionIndex delivery_disjunction)
Same as AddPickupAndDelivery but notifying that the performed node from the disjunction of index 'pic...
Definition: routing.cc:2033
RoutingTransitCallback2 TransitCallback2
Definition: routing.h:240
SweepArranger * sweep_arranger() const
Returns the sweep arranger to be used by routing heuristics.
Definition: routing.cc:680
std::vector< std::string > GetAllDimensionNames() const
Outputs the names of all dimensions added to the routing engine.
Definition: routing.cc:1365
std::pair< int, bool > AddConstantDimensionWithSlack(int64_t value, int64_t capacity, int64_t slack_max, bool fix_start_cumul_to_zero, const std::string &name)
Creates a dimension where the transit variable is constrained to be equal to 'value'; 'capacity' is t...
Definition: routing.cc:1194
Assignment * ReadAssignmentFromRoutes(const std::vector< std::vector< int64_t > > &routes, bool ignore_inactive_indices)
Restores the routes as the current solution.
Definition: routing.cc:3819
const Assignment * SolveFromAssignmentWithParameters(const Assignment *assignment, const RoutingSearchParameters &search_parameters, std::vector< const Assignment * > *solutions=nullptr)
Same as above, except that if assignment is not null, it will be used as the initial solution.
Definition: routing.cc:3201
@ ROUTING_SUCCESS
Problem solved successfully after calling RoutingModel::Solve().
Definition: routing.h:217
@ ROUTING_FAIL
No solution found to the problem after calling RoutingModel::Solve().
Definition: routing.h:219
@ ROUTING_INVALID
Model, model parameters or flags are not valid.
Definition: routing.h:223
@ ROUTING_FAIL_TIMEOUT
Time limit reached before finding a solution with RoutingModel::Solve().
Definition: routing.h:221
void SetVisitType(int64_t index, int type, VisitTypePolicy type_policy)
Definition: routing.cc:4099
int64_t GetDepot() const
Returns the variable index of the first starting or ending node of all routes.
Definition: routing.cc:2144
std::vector< RoutingDimension * > GetDimensionsWithSoftOrSpanCosts() const
Returns dimensions with soft or vehicle span costs.
Definition: routing.cc:5048
void SetSweepArranger(SweepArranger *sweep_arranger)
Definition: routing.cc:676
void AddTemporalTypeIncompatibility(int type1, int type2)
Definition: routing.cc:4147
const std::vector< int > & GetSingleNodesOfType(int type) const
Definition: routing.cc:4113
void SetFixedCostOfVehicle(int64_t cost, int vehicle)
Sets the fixed cost of one vehicle route.
Definition: routing.cc:1546
std::vector< std::vector< std::pair< int64_t, int64_t > > > GetCumulBounds(const Assignment &solution_assignment, const RoutingDimension &dimension)
Returns a vector cumul_bounds, for which cumul_bounds[i][j] is a pair containing the minimum and maxi...
Definition: routing.cc:4351
const std::vector< absl::flat_hash_set< int > > & GetRequiredTypeAlternativesWhenAddingType(int type) const
Returns the set of requirement alternatives when adding the given type.
Definition: routing.cc:4246
bool ArcIsMoreConstrainedThanArc(int64_t from, int64_t to1, int64_t to2)
Returns whether the arc from->to1 is more constrained than from->to2, taking into account,...
Definition: routing.cc:4004
void AddHardTypeIncompatibility(int type1, int type2)
Incompatibilities: Two nodes with "hard" incompatible types cannot share the same route at all,...
Definition: routing.cc:4138
void AddPickupAndDelivery(int64_t pickup, int64_t delivery)
Notifies that index1 and index2 form a pair of nodes which should belong to the same route.
Definition: routing.cc:2028
const TransitCallback1 & UnaryTransitCallbackOrNull(int callback_index) const
Definition: routing.h:509
int64_t GetArcCostForFirstSolution(int64_t from_index, int64_t to_index) const
Returns the cost of the arc in the context of the first solution strategy.
Definition: routing.cc:3970
bool IsVehicleAllowedForIndex(int vehicle, int64_t index)
Returns true if a vehicle is allowed to visit a given node.
Definition: routing.h:825
int RegisterPositiveUnaryTransitCallback(TransitCallback1 callback)
Definition: routing.cc:1050
void SetTabuVarsCallback(GetTabuVarsCallback tabu_var_callback)
Definition: routing.cc:5595
IntVar * ApplyLocks(const std::vector< int64_t > &locks)
Applies a lock chain to the next search.
Definition: routing.cc:3623
int64_t GetArcCostForVehicle(int64_t from_index, int64_t to_index, int64_t vehicle) const
Returns the cost of the transit arc between two nodes for a given vehicle.
Definition: routing.cc:3949
void CloseVisitTypes()
This function should be called once all node visit types have been set and prior to adding any incomp...
Definition: routing.cc:4129
void AssignmentToRoutes(const Assignment &assignment, std::vector< std::vector< int64_t > > *const routes) const
Converts the solution in the given assignment to routes for all vehicles.
Definition: routing.cc:3832
DisjunctionIndex AddDisjunction(const std::vector< int64_t > &indices, int64_t penalty=kNoPenalty, int64_t max_cardinality=1)
Adds a disjunction constraint on the indices: exactly 'max_cardinality' of the indices are active.
Definition: routing.cc:1917
const std::vector< RoutingDimension * > & GetDimensions() const
Returns all dimensions of the model.
Definition: routing.h:651
void IgnoreDisjunctionsAlreadyForcedToZero()
SPECIAL: Makes the solver ignore all the disjunctions whose active variables are all trivially zero (...
Definition: routing.cc:1965
void SetPickupAndDeliveryPolicyOfVehicle(PickupAndDeliveryPolicy policy, int vehicle)
Definition: routing.cc:2078
const Assignment * SolveWithParameters(const RoutingSearchParameters &search_parameters, std::vector< const Assignment * > *solutions=nullptr)
Solves the current routing model with the given parameters.
Definition: routing.cc:3135
int RegisterTransitCallback(TransitCallback2 callback)
Definition: routing.cc:1058
void AddVariableTargetToFinalizer(IntVar *var, int64_t target)
Add a variable to set the closest possible to the target value in the solution finalizer.
Definition: routing.cc:5688
const VariableIndexEvaluator2 & StateDependentTransitCallback(int callback_index) const
Definition: routing.h:513
const Assignment * PackCumulsOfOptimizerDimensionsFromAssignment(const Assignment *original_assignment, absl::Duration duration_limit)
For every dimension in the model with an optimizer in local/global_dimension_optimizers_,...
Definition: routing.cc:607
const std::vector< IntVar * > & Nexts() const
Returns all next variables of the model, such that Nexts(i) is the next variable of the node correspo...
Definition: routing.h:1340
const std::vector< DisjunctionIndex > & GetDisjunctionIndices(int64_t index) const
Returns the indices of the disjunctions to which an index belongs.
Definition: routing.h:756
int64_t UnperformedPenaltyOrValue(int64_t default_value, int64_t var_index) const
Same as above except that it returns default_value instead of 0 when penalty is not well defined (def...
Definition: routing.cc:4263
int AddResourceGroup()
Adds a resource group to the routing model.
Definition: routing.cc:1484
RoutingDimensionIndex DimensionIndex
Definition: routing.h:236
Assignment * CompactAndCheckAssignment(const Assignment &assignment) const
Same as CompactAssignment() but also checks the validity of the final compact solution; if it is not ...
Definition: routing.cc:3537
LocalDimensionCumulOptimizer * GetMutableLocalCumulMPOptimizer(const RoutingDimension &dimension) const
Definition: routing.cc:1410
bool HasHardTypeIncompatibilities() const
Returns true iff any hard (resp.
Definition: routing.h:953
void AddRequiredTypeAlternativesWhenRemovingType(int dependent_type, absl::flat_hash_set< int > required_type_alternatives)
The following requirements apply when visiting dependent nodes that remove their type from the route,...
Definition: routing.cc:4215
std::vector< std::vector< int64_t > > GetRoutesFromAssignment(const Assignment &assignment)
Converts the solution in the given assignment to routes for all vehicles.
Definition: routing.cc:3866
int64_t Next(const Assignment &assignment, int64_t index) const
Assignment inspection Returns the variable index of the node directly after the node corresponding to...
Definition: routing.cc:3941
void SetAmortizedCostFactorsOfAllVehicles(int64_t linear_cost_factor, int64_t quadratic_cost_factor)
The following methods set the linear and quadratic cost factors of vehicles (must be positive values)...
Definition: routing.cc:1552
int RegisterPositiveTransitCallback(TransitCallback2 callback)
Definition: routing.cc:1084
PickupAndDeliveryPolicy
Types of precedence policy applied to pickup and delivery pairs.
Definition: routing.h:227
@ PICKUP_AND_DELIVERY_LIFO
Deliveries must be performed in reverse order of pickups.
Definition: routing.h:231
@ PICKUP_AND_DELIVERY_NO_ORDER
Any precedence is accepted.
Definition: routing.h:229
@ PICKUP_AND_DELIVERY_FIFO
Deliveries must be performed in the same order as pickups.
Definition: routing.h:233
int64_t Start(int vehicle) const
Model inspection.
Definition: routing.h:1320
void CloseModelWithParameters(const RoutingSearchParameters &search_parameters)
Same as above taking search parameters (as of 10/2015 some the parameters have to be set when closing...
Definition: routing.cc:2425
int vehicles() const
Returns the number of vehicle routes in the model.
Definition: routing.h:1516
void SetAllowedVehiclesForIndex(const std::vector< int > &vehicles, int64_t index)
Sets the vehicles which can visit a given node.
Definition: routing.cc:2019
void AddVariableMaximizedByFinalizer(IntVar *var)
Adds a variable to maximize in the solution finalizer (see above for information on the solution fina...
Definition: routing.cc:5695
int64_t UnperformedPenalty(int64_t var_index) const
Get the "unperformed" penalty of a node.
Definition: routing.cc:4259
void SetAmortizedCostFactorsOfVehicle(int64_t linear_cost_factor, int64_t quadratic_cost_factor, int vehicle)
Sets the linear and quadratic cost factor of the given vehicle.
Definition: routing.cc:1560
IntVar * ActiveVar(int64_t index) const
Returns the active variable of the node corresponding to index.
Definition: routing.h:1355
int64_t GetNumberOfDecisionsInFirstSolution(const RoutingSearchParameters &search_parameters) const
Returns statistics on first solution search, number of decisions sent to filters, number of decisions...
Definition: routing.cc:3650
bool HasTypeRegulations() const
Returns true iff the model has any incompatibilities or requirements set on node types.
Definition: routing.h:1007
RoutingVehicleClassIndex VehicleClassIndex
Definition: routing.h:238
void AddWeightedVariableMinimizedByFinalizer(IntVar *var, int64_t cost)
Adds a variable to minimize in the solution finalizer, with a weighted priority: the higher the more ...
Definition: routing.cc:5675
void AddIntervalToAssignment(IntervalVar *const interval)
Definition: routing.cc:5713
void SetArcCostEvaluatorOfAllVehicles(int evaluator_index)
Sets the cost function of the model such that the cost of a segment of a route between node 'from' an...
Definition: routing.cc:1521
bool ApplyLocksToAllVehicles(const std::vector< std::vector< int64_t > > &locks, bool close_routes)
Applies lock chains to all vehicles to the next search, such that locks[p] is the lock chain for rout...
Definition: routing.cc:3644
std::vector< std::pair< int64_t, int64_t > > GetPerfectBinaryDisjunctions() const
Returns the list of all perfect binary disjunctions, as pairs of variable indices: a disjunction is "...
Definition: routing.cc:1948
std::function< StateDependentTransit(int64_t, int64_t)> VariableIndexEvaluator2
Definition: routing.h:266
GlobalDimensionCumulOptimizer * GetMutableGlobalCumulOptimizer(const RoutingDimension &dimension) const
Returns the global/local dimension cumul optimizer for a given dimension, or nullptr if there is none...
Definition: routing.cc:1374
bool AddDimensionWithVehicleCapacity(int evaluator_index, int64_t slack_max, std::vector< int64_t > vehicle_capacities, bool fix_start_cumul_to_zero, const std::string &name)
Definition: routing.cc:1134
IntVar * CostVar() const
Returns the global cost variable which is being minimized.
Definition: routing.h:1379
bool HasSameVehicleTypeRequirements() const
Returns true iff any same-route (resp.
Definition: routing.h:998
int64_t GetArcCostForClass(int64_t from_index, int64_t to_index, int64_t cost_class_index) const
Returns the cost of the segment between two nodes for a given cost class.
Definition: routing.cc:3959
void SetAssignmentFromOtherModelAssignment(Assignment *target_assignment, const RoutingModel *source_model, const Assignment *source_assignment)
Given a "source_model" and its "source_assignment", resets "target_assignment" with the IntVar variab...
Definition: routing.cc:3350
void AddSameVehicleRequiredTypeAlternatives(int dependent_type, absl::flat_hash_set< int > required_type_alternatives)
Requirements: NOTE: As of 2019-04, cycles in the requirement graph are not supported,...
Definition: routing.cc:4172
const absl::flat_hash_set< int > & GetHardTypeIncompatibilitiesOfType(int type) const
Returns visit types incompatible with a given type.
Definition: routing.cc:4157
const std::vector< std::pair< int, int > > & GetPickupIndexPairs(int64_t node_index) const
Returns pairs for which the node is a pickup; the first element of each pair is the index in the pick...
Definition: routing.cc:2066
bool IsMatchingModel() const
Returns true if a vehicle/node matching problem is detected.
Definition: routing_flow.cc:55
static RoutingModel::StateDependentTransit MakeStateDependentTransit(const std::function< int64_t(int64_t)> &f, int64_t domain_start, int64_t domain_end)
Creates a cached StateDependentTransit from an std::function.
Definition: routing.cc:1353
int RegisterUnaryTransitCallback(TransitCallback1 callback)
Definition: routing.cc:1023
int64_t GetNumberOfRejectsInFirstSolution(const RoutingSearchParameters &search_parameters) const
Definition: routing.cc:3658
bool IsEnd(int64_t index) const
Returns true if 'index' represents the last node of a route.
Definition: routing.h:1326
bool AddDimensionWithVehicleTransits(const std::vector< int > &evaluator_indices, int64_t slack_max, int64_t capacity, bool fix_start_cumul_to_zero, const std::string &name)
Definition: routing.cc:1125
bool WriteAssignment(const std::string &file_name) const
Writes the current solution to a file containing an AssignmentProto.
Definition: routing.cc:3666
RoutingCostClassIndex CostClassIndex
Definition: routing.h:235
bool HasTemporalTypeIncompatibilities() const
Definition: routing.h:956
bool HasMaxCardinalityConstrainedDisjunctions() const
Returns true if the model contains at least one disjunction which is constrained by its max_cardinali...
Definition: routing.cc:1940
int GetCostClassesCount() const
Returns the number of different cost classes in the model.
Definition: routing.h:1424
int GetNumOfSingletonNodes() const
Returns the number of non-start/end nodes which do not appear in a pickup/delivery pair.
Definition: routing.cc:2098
void AddRequiredTypeAlternativesWhenAddingType(int dependent_type, absl::flat_hash_set< int > required_type_alternatives)
If type_D depends on type_R when adding type_D, any node_D of type_D and VisitTypePolicy TYPE_ADDED_T...
Definition: routing.cc:4194
int RegisterTransitMatrix(std::vector< std::vector< int64_t > > values)
Definition: routing.cc:1031
void CloseModel()
Closes the current routing model; after this method is called, no modification to the model can be do...
Definition: routing.cc:2247
static const DimensionIndex kNoDimension
Constant used to express the "no dimension" index, returned when a dimension name does not correspond...
Definition: routing.h:484
bool CostsAreHomogeneousAcrossVehicles() const
Whether costs are homogeneous across all vehicles.
Definition: routing.h:1386
CostClassIndex GetCostClassIndexOfVehicle(int64_t vehicle) const
Get the cost class index of the given vehicle.
Definition: routing.h:1407
const Assignment * Solve(const Assignment *assignment=nullptr)
Solves the current routing model; closes the current model.
Definition: routing.cc:3130
static const DisjunctionIndex kNoDisjunction
Constant used to express the "no disjunction" index, returned when a node does not appear in any disj...
Definition: routing.h:480
void SetFixedCostOfAllVehicles(int64_t cost)
Sets the fixed cost of all vehicle routes.
Definition: routing.cc:1535
void SetArcCostEvaluatorOfVehicle(int evaluator_index, int vehicle)
Sets the cost function for a given vehicle route.
Definition: routing.cc:1528
bool HasDimension(const std::string &dimension_name) const
Returns true if a dimension exists for a given dimension name.
Definition: routing.cc:1422
VisitTypePolicy GetVisitTypePolicy(int64_t index) const
Definition: routing.cc:4123
bool IsVehicleUsed(const Assignment &assignment, int vehicle) const
Returns true if the route of 'vehicle' is non empty in 'assignment'.
Definition: routing.cc:3931
const std::string & GetPrimaryConstrainedDimension() const
Get the primary constrained dimension, or an empty string if it is unset.
Definition: routing.h:706
RoutingModel(const RoutingIndexManager &index_manager)
Constructor taking an index manager.
Definition: routing.cc:885
bool 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)
Definition: routing.cc:1144
RoutingDisjunctionIndex DisjunctionIndex
Definition: routing.h:237
int64_t End(int vehicle) const
Returns the variable index of the ending node of a vehicle route.
Definition: routing.h:1322
bool AddDimension(int evaluator_index, int64_t slack_max, int64_t capacity, bool fix_start_cumul_to_zero, const std::string &name)
Model creation.
Definition: routing.cc:1115
const RoutingDimension & GetDimensionOrDie(const std::string &dimension_name) const
Returns a dimension from its name. Dies if the dimension does not exist.
Definition: routing.cc:1432
const std::vector< int64_t > & GetDisjunctionNodeIndices(DisjunctionIndex index) const
Returns the variable indices of the nodes in the disjunction of index 'index'.
Definition: routing.h:777
RoutingModelInspector(RoutingModel *model)
Definition: routing.cc:2253
void VisitIntegerArrayArgument(const std::string &arg_name, const std::vector< int64_t > &values) override
Definition: routing.cc:2290
void EndVisitConstraint(const std::string &type_name, const Constraint *const constraint) override
Definition: routing.cc:2281
void EndVisitModel(const std::string &solver_name) override
Definition: routing.cc:2269
void VisitIntegerExpressionArgument(const std::string &type_name, IntExpr *const expr) override
Visit integer expression argument.
Definition: routing.cc:2285
static const char kLightElement[]
Constraint types.
Definition: routing.h:2140
A search monitor is a simple set of callbacks to monitor all search events.
int solution_count() const
Returns how many solutions were stored during the search.
Definition: search.cc:2385
Assignment * solution(int n) const
Returns the nth solution.
Definition: search.cc:2380
IntExpr * MakeSemiContinuousExpr(IntExpr *const expr, int64_t fixed_charge, int64_t step)
Semi continuous Expression (x <= 0 -> f(x) = 0; x > 0 -> f(x) = ax + b) a >= 0 and b >= 0.
Constraint * MakeEquality(IntExpr *const left, IntExpr *const right)
left == right
Definition: range_cst.cc:512
@ ASSIGN_MIN_VALUE
Selects the min value of the selected variable.
std::function< int64_t(int64_t, int64_t)> IndexEvaluator2
void AddConstraint(Constraint *const c)
Adds the constraint 'c' to the model.
@ FULLPATHLNS
Operator which relaxes one entire path and all inactive nodes, thus defining num_paths neighbors.
@ OROPT
Relocate: OROPT and RELOCATE.
@ PATHLNS
Operator which relaxes two sub-chains of three consecutive arcs each.
@ UNACTIVELNS
Operator which relaxes all inactive nodes and one sub-chain of six consecutive arcs.
IntExpr * MakeElement(const std::vector< int64_t > &values, IntVar *const index)
values[index]
Definition: element.cc:657
@ CHOOSE_STATIC_GLOBAL_BEST
Pairs are compared at the first call of the selector, and results are cached.
IntExpr * MakeMax(const std::vector< IntVar * > &vars)
std::max(vars)
Definition: expr_array.cc:3344
IntExpr * MakeDifference(IntExpr *const left, IntExpr *const right)
left - right
static ConstraintSolverParameters DefaultSolverParameters()
Create a ConstraintSolverParameters proto with all the default values.
@ CHOOSE_FIRST_UNBOUND
Select the first unbound variable.
@ CHOOSE_PATH
Selects the next unbound variable on a path, the path being defined by the variables: var[i] correspo...
IntExpr * MakeSum(IntExpr *const left, IntExpr *const right)
left + right.
std::function< int64_t(int64_t)> IndexEvaluator1
Callback typedefs.
IntExpr * MakeProd(IntExpr *const left, IntExpr *const right)
left * right
void Fail()
Abandon the current branch in the search tree. A backtrack will follow.
std::function< bool(int64_t, int64_t, int64_t)> VariableValueComparator
T * RevAlloc(T *object)
Registers the given object as being reversible.
EvaluatorLocalSearchOperators
This enum is used in Solver::MakeOperator associated with an evaluator to specify the neighborhood to...
@ TSPOPT
Sliding TSP operator.
@ LK
Lin-Kernighan local search.
@ LE
Move is accepted when the current objective value <= objective.Max.
This class represents a sorted list of disjoint, closed intervals.
Iterator InsertInterval(int64_t start, int64_t end)
Adds the interval [start..end] to the list, and merges overlapping or immediately adjacent intervals ...
Iterator FirstIntervalGreaterOrEqual(int64_t value) const
Returns an iterator to either:
Class to arrange indices by by their distance and their angles from the depot.
TypeIncompatibilityChecker(const RoutingModel &model, bool check_hard_incompatibilities)
Definition: routing.cc:6441
virtual bool HasRegulationsToCheck() const =0
virtual bool CheckTypeRegulations(int type, VisitTypePolicy policy, int pos)=0
bool CheckVehicle(int vehicle, const std::function< int64_t(int64_t)> &next_accessor)
Definition: routing.cc:6359
TypeRegulationsChecker(const RoutingModel &model)
Definition: routing.cc:6356
void InitializeCheck(int vehicle, const std::function< int64_t(int64_t)> &next_accessor)
Definition: routing.cc:6403
bool TypeCurrentlyOnRoute(int type, int pos) const
Returns true iff there's at least one instance of the given type on the route when scanning the route...
Definition: routing.cc:6434
bool TypeOccursOnRoute(int type) const
Returns true iff any occurrence of the given type was seen on the route, i.e.
Definition: routing.cc:6428
The following constraint ensures that incompatibilities and requirements between types are respected.
Definition: routing.h:2494
void Post() override
This method is called when the constraint is processed by the solver.
Definition: routing.cc:6581
void InitialPropagate() override
This method performs the initial propagation of the constraint.
Definition: routing.cc:6596
TypeRegulationsConstraint(const RoutingModel &model)
Definition: routing.cc:6548
IntegerRange< NodeIndex > AllNodes() const
Definition: graph.h:937
int64_t b
int64_t a
Block * next
SatParameters parameters
const std::string name
int64_t value
IntVar *const expr_
Definition: element.cc:87
IntVar * var
Definition: expr_array.cc:1874
const int64_t limit_
absl::Status status
Definition: g_gurobi.cc:35
double upper_bound
double lower_bound
GRBmodel * model
MPCallback * callback
const int WARNING
Definition: log_severity.h:31
const int INFO
Definition: log_severity.h:31
const int ERROR
Definition: log_severity.h:32
Definition: cleanup.h:22
void STLDeleteElements(T *container)
Definition: stl_util.h:372
Collection::value_type::second_type & LookupOrInsert(Collection *const collection, const typename Collection::value_type::first_type &key, const typename Collection::value_type::second_type &value)
Definition: map_util.h:237
bool FindCopy(const Collection &collection, const Key &key, Value *const value)
Definition: map_util.h:185
const Collection::value_type::second_type & FindOrDie(const Collection &collection, const typename Collection::value_type::first_type &key)
Definition: map_util.h:206
const Collection::value_type::second_type & FindWithDefault(const Collection &collection, const typename Collection::value_type::first_type &key, const typename Collection::value_type::second_type &value)
Definition: map_util.h:29
const Collection::value_type::second_type * FindOrNull(const Collection &collection, const typename Collection::value_type::first_type &key)
Definition: map_util.h:60
bool ContainsKey(const Collection &collection, const Key &key)
Definition: map_util.h:200
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:262
Collection of objects used to extend the Constraint Solver library.
bool SolveModelWithSat(const RoutingModel &model, const RoutingSearchParameters &search_parameters, const Assignment *initial_solution, Assignment *solution)
Attempts to solve the model using the cp-sat solver.
int64_t CapAdd(int64_t x, int64_t y)
RangeIntToIntFunction * MakeCachedIntToIntFunction(const std::function< int64_t(int64_t)> &f, int64_t domain_start, int64_t domain_end)
const std::string & FirstSolutionStrategy_Value_Name(T enum_t_value)
std::function< int64_t(int64_t, int64_t)> RoutingTransitCallback2
Definition: routing_types.h:43
DecisionBuilder * MakeAllUnperformed(RoutingModel *model)
constexpr int FirstSolutionStrategy_Value_Value_ARRAYSIZE
RoutingModelParameters DefaultRoutingModelParameters()
IntVarLocalSearchFilter * MakeVehicleBreaksFilter(const RoutingModel &routing_model, const RoutingDimension &dimension)
std::string FindErrorInRoutingSearchParameters(const RoutingSearchParameters &search_parameters)
Returns an empty std::string if the routing search parameters are valid, and a non-empty,...
int64_t CapSub(int64_t x, int64_t y)
IntVarLocalSearchFilter * MakeVehicleAmortizedCostFilter(const RoutingModel &routing_model)
Returns a filter computing vehicle amortized costs.
void SetAssignmentFromAssignment(Assignment *target_assignment, const std::vector< IntVar * > &target_vars, const Assignment *source_assignment, const std::vector< IntVar * > &source_vars)
NOLINT.
constexpr FirstSolutionStrategy_Value FirstSolutionStrategy_Value_Value_MIN
RangeMinMaxIndexFunction * MakeCachedRangeMinMaxIndexFunction(const std::function< int64_t(int64_t)> &f, int64_t domain_start, int64_t domain_end)
int64_t Zero()
NOLINT.
constexpr FirstSolutionStrategy_Value FirstSolutionStrategy_Value_Value_MAX
IntVarLocalSearchFilter * MakeCPFeasibilityFilter(RoutingModel *routing_model)
Returns a filter checking the current solution using CP propagation.
DecisionBuilder * MakeRestoreDimensionValuesForUnchangedRoutes(RoutingModel *model)
Definition: routing.cc:3100
DecisionBuilder * MakeSetValuesFromTargets(Solver *solver, std::vector< IntVar * > variables, std::vector< int64_t > targets)
A decision builder which tries to assign values to variables as close as possible to target values fi...
Definition: routing.cc:170
void AppendDimensionCumulFilters(const std::vector< RoutingDimension * > &dimensions, const RoutingSearchParameters &parameters, bool filter_objective_cost, bool filter_light_weight_unary_dimensions, std::vector< LocalSearchFilterManager::FilterEvent > *filters)
FirstSolutionStrategy::Value AutomaticFirstSolutionStrategy(bool has_pickup_deliveries, bool has_node_precedences, bool has_single_vehicle_node)
Returns the best value for the automatic first solution strategy, based on the given model parameters...
uint64_t ThoroughHash(const char *bytes, size_t len)
Definition: thorough_hash.h:33
int64_t One()
This method returns 1.
Demon * MakeConstraintDemon0(Solver *const s, T *const ct, void(T::*method)(), const std::string &name)
IntVarLocalSearchFilter * MakeMaxActiveVehiclesFilter(const RoutingModel &routing_model)
Returns a filter ensuring that max active vehicles constraints are enforced.
int64_t CapProd(int64_t x, int64_t y)
std::function< int64_t(int64_t)> RoutingTransitCallback1
Definition: routing_types.h:42
void FillPathEvaluation(const std::vector< int64_t > &path, const RoutingModel::TransitCallback2 &evaluator, std::vector< int64_t > *values)
Definition: routing.cc:6346
RoutingSearchParameters DefaultRoutingSearchParameters()
DecisionBuilder * MakeSweepDecisionBuilder(RoutingModel *model, bool check_assignment)
IntVarLocalSearchFilter * MakeVehicleVarFilter(const RoutingModel &routing_model)
Returns a filter checking that vehicle variable domains are respected.
Demon * MakeDelayedConstraintDemon1(Solver *const s, T *const ct, void(T::*method)(P), const std::string &name, P param1)
std::string MemoryUsage()
Definition: stats.cc:25
IntVarLocalSearchFilter * MakePickupDeliveryFilter(const RoutingModel &routing_model, const RoutingModel::IndexPairs &pairs, const std::vector< RoutingModel::PickupAndDeliveryPolicy > &vehicle_policies)
Returns a filter enforcing pickup and delivery constraints for the given pair of nodes and given poli...
IntVarLocalSearchFilter * MakeTypeRegulationsFilter(const RoutingModel &routing_model)
Returns a filter ensuring type regulation constraints are enforced.
bool HasUnaryDimension(const std::vector< RoutingDimension * > &dimensions)
Definition: routing.cc:4767
static const int kUnassigned
Definition: routing.cc:878
Demon * MakeConstraintDemon1(Solver *const s, T *const ct, void(T::*method)(P), const std::string &name, P param1)
LocalSearchFilter * MakePathStateFilter(Solver *solver, std::unique_ptr< PathState > path_state, const std::vector< IntVar * > &nexts)
void AppendLightWeightDimensionFilters(const PathState *path_state, const std::vector< RoutingDimension * > &dimensions, std::vector< LocalSearchFilterManager::FilterEvent > *filters)
Appends dimension-based filters to the given list of filters using a path state.
IntVarLocalSearchFilter * MakeNodeDisjunctionFilter(const RoutingModel &routing_model, bool filter_cost)
Returns a filter ensuring that node disjunction constraints are enforced.
STL namespace.
inline ::absl::StatusOr< absl::Duration > DecodeGoogleApiProto(const google::protobuf::Duration &proto)
Definition: protoutil.h:42
int index
Definition: pack.cc:509
static int input(yyscan_t yyscanner)
int64_t demand
Definition: resource.cc:125
IntervalVar * interval
Definition: resource.cc:100
#define CP_ROUTING_PUSH_OPERATOR(operator_type, operator_method, operators)
Definition: routing.cc:4625
int64_t coefficient
int64_t capacity
int64_t tail
int64_t cost
int64_t head
int vehicle_class
double distance
std::vector< int > var_indices
Rev< int64_t > start_max
Rev< int64_t > end_max
Rev< int64_t > start_min
Rev< int64_t > end_min
std::optional< int64_t > end
int64_t start
static bool LessThan(const CostClass &a, const CostClass &b)
Comparator for STL containers and algorithms.
Definition: routing.h:312
What follows is relevant for models with time/state dependent transits.
Definition: routing.h:261
static bool LessThan(const VehicleClass &a, const VehicleClass &b)
Comparator for STL containers and algorithms.
Definition: routing.cc:1655
int position_of_last_type_on_vehicle_up_to_visit
Position of the last node of policy TYPE_ON_VEHICLE_UP_TO_VISIT visited on the route.
Definition: routing.h:2382
int num_type_added_to_vehicle
Number of TYPE_ADDED_TO_VEHICLE and TYPE_SIMULTANEOUSLY_ADDED_AND_REMOVED node type policies seen on ...
Definition: routing.h:2371
int num_type_removed_from_vehicle
Number of ADDED_TYPE_REMOVED_FROM_VEHICLE (effectively removing a type from the route) and TYPE_SIMUL...
Definition: routing.h:2377
const double coeff