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