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