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