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