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