Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
routing_search.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
14// Implementation of all classes related to routing and search.
15// This includes decision builders, local search neighborhood operators
16// and local search filters.
17// TODO(user): Move all existing routing search code here.
18
20
21#include <algorithm>
22#include <cmath>
23#include <cstdint>
24#include <cstdlib>
25#include <deque>
26#include <functional>
27#include <limits>
28#include <map>
29#include <memory>
30#include <optional>
31#include <set>
32#include <string>
33#include <tuple>
34#include <utility>
35#include <vector>
36
37#include "absl/algorithm/container.h"
38#include "absl/base/attributes.h"
39#include "absl/container/btree_set.h"
40#include "absl/container/flat_hash_map.h"
41#include "absl/container/flat_hash_set.h"
42#include "absl/container/inlined_vector.h"
43#include "absl/flags/flag.h"
44#include "absl/log/check.h"
45#include "absl/log/die_if_null.h"
46#include "absl/strings/str_cat.h"
47#include "absl/strings/string_view.h"
48#include "absl/time/time.h"
49#include "absl/types/span.h"
56#include "ortools/base/types.h"
60#include "ortools/constraint_solver/routing_enums.pb.h"
61#include "ortools/constraint_solver/routing_parameters.pb.h"
65#include "ortools/util/bitset.h"
68
69namespace operations_research {
71} // namespace operations_research
72
73ABSL_FLAG(bool, routing_shift_insertion_cost_by_penalty, true,
74 "Shift insertion costs by the penalty of the inserted node(s).");
75
76ABSL_FLAG(int64_t, sweep_sectors, 1,
77 "The number of sectors the space is divided into before it is sweeped"
78 " by the ray.");
79
80namespace operations_research {
81
82namespace {
83void ConvertAssignment(const RoutingModel* src_model, const Assignment* src,
84 const RoutingModel* dst_model, Assignment* dst) {
85 DCHECK_EQ(src_model->Nexts().size(), dst_model->Nexts().size());
86 DCHECK_NE(src, nullptr);
87 DCHECK_EQ(src_model->solver(), src->solver());
88 DCHECK_EQ(dst_model->solver(), dst->solver());
89 for (int i = 0; i < src_model->Nexts().size(); i++) {
90 IntVar* const dst_next_var = dst_model->NextVar(i);
91 if (!dst->Contains(dst_next_var)) {
92 dst->Add(dst_next_var);
93 }
94 dst->SetValue(dst_next_var, src->Value(src_model->NextVar(i)));
95 }
96}
97
98bool AreAssignmentsEquivalent(const RoutingModel& model,
99 const Assignment& assignment1,
100 const Assignment& assignment2) {
101 for (IntVar* next_var : model.Nexts()) {
102 if (assignment1.Value(next_var) != assignment2.Value(next_var)) {
103 return false;
104 }
105 }
106 return true;
107}
108
109absl::Duration GetTimeLimit(const RoutingSearchParameters& search_parameters) {
110 return search_parameters.has_time_limit()
111 ? util_time::DecodeGoogleApiProto(search_parameters.time_limit())
112 .value()
113 : absl::InfiniteDuration();
114}
115
116// TODO(user): Refactor this function with other versions living in
117// routing.cc.
118// TODO(user): Should we update solution limits as well?
119bool UpdateTimeLimits(Solver* solver, int64_t start_time_ms,
120 absl::Duration time_limit,
121 RoutingSearchParameters& search_parameters) {
122 if (!search_parameters.has_time_limit()) return true;
123 const absl::Duration elapsed_time =
124 absl::Milliseconds(solver->wall_time() - start_time_ms);
125 const absl::Duration time_left =
126 std::min(time_limit - elapsed_time, GetTimeLimit(search_parameters));
127 if (time_left <= absl::ZeroDuration()) return false;
128 const absl::Status status = util_time::EncodeGoogleApiProto(
129 time_left, search_parameters.mutable_time_limit());
130 DCHECK_OK(status);
131 return true;
132}
133} // namespace
134
136 RoutingModel* primary_model,
137 const std::vector<RoutingModel*>& alternative_models,
138 const RoutingSearchParameters& parameters,
139 int max_non_improving_iterations) {
141 /*assignment=*/nullptr, primary_model, alternative_models, parameters,
142 max_non_improving_iterations);
143}
144
146 const Assignment* assignment, RoutingModel* primary_model,
147 const std::vector<RoutingModel*>& alternative_models,
148 const RoutingSearchParameters& parameters,
149 int max_non_improving_iterations) {
151 assignment, primary_model, parameters, alternative_models, {},
152 max_non_improving_iterations);
153}
154
156 const Assignment* assignment, RoutingModel* primary_model,
157 const RoutingSearchParameters& primary_parameters,
158 const std::vector<RoutingModel*>& alternative_models,
159 const std::vector<RoutingSearchParameters>& alternative_parameters,
160 int max_non_improving_iterations) {
161 const int64_t start_time_ms = primary_model->solver()->wall_time();
162 if (max_non_improving_iterations < 0) return nullptr;
163 // Shortcut if no alternative models will be explored.
164 if (max_non_improving_iterations == 0 || alternative_models.empty()) {
165 return primary_model->SolveFromAssignmentWithParameters(assignment,
166 primary_parameters);
167 }
168 RoutingSearchParameters mutable_search_parameters = primary_parameters;
169 // The first alternating phases are limited to greedy descent. The final pass
170 // at the end of this function will actually use the metaheuristic if needed.
171 // TODO(user): Add support for multiple metaheuristics.
172 const LocalSearchMetaheuristic_Value metaheuristic =
173 mutable_search_parameters.local_search_metaheuristic();
174 const bool run_metaheuristic_phase =
175 metaheuristic != LocalSearchMetaheuristic::GREEDY_DESCENT &&
176 metaheuristic != LocalSearchMetaheuristic::AUTOMATIC;
177 if (run_metaheuristic_phase) {
178 mutable_search_parameters.set_local_search_metaheuristic(
179 LocalSearchMetaheuristic::GREEDY_DESCENT);
180 }
181 std::vector<RoutingSearchParameters> mutable_alternative_parameters =
182 alternative_parameters;
183 DCHECK(mutable_alternative_parameters.empty() ||
184 mutable_alternative_parameters.size() == alternative_models.size());
185 for (RoutingSearchParameters& mutable_alternative_parameter :
186 mutable_alternative_parameters) {
187 if (!mutable_alternative_parameter.has_time_limit() &&
188 mutable_search_parameters.has_time_limit()) {
189 *mutable_alternative_parameter.mutable_time_limit() =
190 mutable_search_parameters.time_limit();
191 }
192 }
193 const absl::Duration time_limit = GetTimeLimit(mutable_search_parameters);
194 Assignment* best_assignment = primary_model->solver()->MakeAssignment();
195 std::vector<Assignment*> first_solutions;
196 first_solutions.reserve(alternative_models.size());
197 for (RoutingModel* model : alternative_models) {
198 first_solutions.push_back(model->solver()->MakeAssignment());
199 }
200 Assignment* primary_first_solution =
201 primary_model->solver()->MakeAssignment();
202 Assignment* current_solution = primary_model->solver()->MakeAssignment();
203 DCHECK(assignment == nullptr ||
204 assignment->solver() == primary_model->solver());
205 const Assignment* solution = primary_model->SolveFromAssignmentWithParameters(
206 assignment, mutable_search_parameters);
207 if (solution == nullptr) return nullptr;
208 best_assignment->Copy(solution);
209 int iteration = 0;
210 while (iteration < max_non_improving_iterations) {
211 if (best_assignment->ObjectiveValue() > solution->ObjectiveValue()) {
212 best_assignment->Copy(solution);
213 }
214 current_solution->Copy(solution);
215 for (int i = 0; i < alternative_models.size(); ++i) {
216 RoutingSearchParameters& mutable_alternative_parameter =
217 mutable_alternative_parameters.empty()
218 ? mutable_search_parameters
219 : mutable_alternative_parameters[i];
220 if (!UpdateTimeLimits(primary_model->solver(), start_time_ms, time_limit,
221 mutable_alternative_parameter)) {
222 return best_assignment;
223 }
224 ConvertAssignment(primary_model, solution, alternative_models[i],
225 first_solutions[i]);
226 solution = alternative_models[i]->SolveFromAssignmentWithParameters(
227 first_solutions[i], mutable_alternative_parameter);
228 if (solution == nullptr) {
229 solution = first_solutions[i];
230 }
231 if (!UpdateTimeLimits(primary_model->solver(), start_time_ms, time_limit,
232 mutable_search_parameters)) {
233 return best_assignment;
234 }
235 ConvertAssignment(alternative_models[i], solution, primary_model,
236 primary_first_solution);
237 solution = primary_model->SolveFromAssignmentWithParameters(
238 primary_first_solution, mutable_search_parameters);
239 if (solution == nullptr) return best_assignment;
240 }
241 // No modifications done in this iteration, no need to continue.
242 if (AreAssignmentsEquivalent(*primary_model, *solution,
243 *current_solution)) {
244 break;
245 }
246 // We're back to the best assignment which means we will cycle if we
247 // continue the search.
248 if (AreAssignmentsEquivalent(*primary_model, *solution, *best_assignment)) {
249 break;
250 }
251 if (solution->ObjectiveValue() >= best_assignment->ObjectiveValue()) {
252 ++iteration;
253 }
254 }
255 if (run_metaheuristic_phase &&
256 UpdateTimeLimits(primary_model->solver(), start_time_ms, time_limit,
257 mutable_search_parameters)) {
258 mutable_search_parameters.set_local_search_metaheuristic(metaheuristic);
259 return primary_model->SolveFromAssignmentWithParameters(
260 best_assignment, mutable_search_parameters);
261 }
262 return best_assignment;
263}
264
265// --- VehicleTypeCurator ---
266
267void VehicleTypeCurator::Reset(const std::function<bool(int)>& store_vehicle) {
268 const std::vector<std::set<VehicleClassEntry>>& all_vehicle_classes_per_type =
269 vehicle_type_container_->sorted_vehicle_classes_per_type;
270 sorted_vehicle_classes_per_type_.resize(all_vehicle_classes_per_type.size());
271 const std::vector<std::deque<int>>& all_vehicles_per_class =
272 vehicle_type_container_->vehicles_per_vehicle_class;
273 vehicles_per_vehicle_class_.resize(all_vehicles_per_class.size());
274
275 for (int type = 0; type < all_vehicle_classes_per_type.size(); type++) {
276 std::set<VehicleClassEntry>& stored_class_entries =
277 sorted_vehicle_classes_per_type_[type];
278 stored_class_entries.clear();
279 for (VehicleClassEntry class_entry : all_vehicle_classes_per_type[type]) {
280 const int vehicle_class = class_entry.vehicle_class;
281 std::vector<int>& stored_vehicles =
282 vehicles_per_vehicle_class_[vehicle_class];
283 stored_vehicles.clear();
284 for (int vehicle : all_vehicles_per_class[vehicle_class]) {
285 if (store_vehicle(vehicle)) {
286 stored_vehicles.push_back(vehicle);
287 }
288 }
289 if (!stored_vehicles.empty()) {
290 stored_class_entries.insert(class_entry);
291 }
292 }
293 }
294}
295
297 const std::function<bool(int)>& remove_vehicle) {
298 for (std::set<VehicleClassEntry>& class_entries :
299 sorted_vehicle_classes_per_type_) {
300 auto class_entry_it = class_entries.begin();
301 while (class_entry_it != class_entries.end()) {
302 const int vehicle_class = class_entry_it->vehicle_class;
303 std::vector<int>& vehicles = vehicles_per_vehicle_class_[vehicle_class];
304 vehicles.erase(std::remove_if(vehicles.begin(), vehicles.end(),
305 [&remove_vehicle](int vehicle) {
306 return remove_vehicle(vehicle);
307 }),
308 vehicles.end());
309 if (vehicles.empty()) {
310 class_entry_it = class_entries.erase(class_entry_it);
311 } else {
312 class_entry_it++;
313 }
314 }
315 }
316}
317
319 int type, const std::function<bool(int)>& vehicle_is_compatible) const {
320 for (const VehicleClassEntry& vehicle_class_entry :
321 sorted_vehicle_classes_per_type_[type]) {
322 for (int vehicle :
323 vehicles_per_vehicle_class_[vehicle_class_entry.vehicle_class]) {
324 if (vehicle_is_compatible(vehicle)) return true;
325 }
326 }
327 return false;
328}
329
331 int type, const std::function<bool(int)>& vehicle_is_compatible,
332 const std::function<bool(int)>& stop_and_return_vehicle) {
333 std::set<VehicleTypeCurator::VehicleClassEntry>& sorted_classes =
334 sorted_vehicle_classes_per_type_[type];
335 auto vehicle_class_it = sorted_classes.begin();
336
337 while (vehicle_class_it != sorted_classes.end()) {
338 const int vehicle_class = vehicle_class_it->vehicle_class;
339 std::vector<int>& vehicles = vehicles_per_vehicle_class_[vehicle_class];
340 DCHECK(!vehicles.empty());
341
342 for (auto vehicle_it = vehicles.begin(); vehicle_it != vehicles.end();
343 vehicle_it++) {
344 const int vehicle = *vehicle_it;
345 if (vehicle_is_compatible(vehicle)) {
346 vehicles.erase(vehicle_it);
347 if (vehicles.empty()) {
348 sorted_classes.erase(vehicle_class_it);
349 }
350 return {vehicle, -1};
351 }
352 if (stop_and_return_vehicle(vehicle)) {
353 return {-1, vehicle};
354 }
355 }
356 // If no compatible vehicle was found in this class, move on to the next
357 // vehicle class.
358 vehicle_class_it++;
359 }
360 // No compatible vehicle of the given type was found and the stopping
361 // condition wasn't met.
362 return {-1, -1};
363}
364
365// - Models with pickup/deliveries or node precedences are best handled by
366// PARALLEL_CHEAPEST_INSERTION.
367// - As of January 2018, models with single nodes and at least one node with
368// only one allowed vehicle are better solved by PATH_MOST_CONSTRAINED_ARC.
369// - In all other cases, PATH_CHEAPEST_ARC is used.
370// TODO(user): Make this smarter.
371FirstSolutionStrategy::Value AutomaticFirstSolutionStrategy(
372 bool has_pickup_deliveries, bool has_node_precedences,
373 bool has_single_vehicle_node) {
374 if (has_pickup_deliveries || has_node_precedences) {
375 return FirstSolutionStrategy::PARALLEL_CHEAPEST_INSERTION;
376 }
377 if (has_single_vehicle_node) {
378 return FirstSolutionStrategy::PATH_MOST_CONSTRAINED_ARC;
379 }
380 return FirstSolutionStrategy::PATH_CHEAPEST_ARC;
381}
382
383std::vector<int64_t> ComputeVehicleEndChainStarts(const RoutingModel& model) {
384 const int64_t size = model.Size();
385 const int num_vehicles = model.vehicles();
386 // Find the chains of nodes (when nodes have their "Next" value bound in the
387 // current solution, it forms a link in a chain). Eventually, starts[end]
388 // will contain the index of the first node of the chain ending at node 'end'
389 // and ends[start] will be the last node of the chain starting at node
390 // 'start'. Values of starts[node] and ends[node] for other nodes is used
391 // for intermediary computations and do not necessarily reflect actual chain
392 // starts and ends.
393 std::vector<int64_t> starts(size + num_vehicles, -1);
394 std::vector<int64_t> ends(size + num_vehicles, -1);
395 for (int node = 0; node < size + num_vehicles; ++node) {
396 // Each node starts as a singleton chain.
397 starts[node] = node;
398 ends[node] = node;
399 }
400 std::vector<bool> touched(size, false);
401 for (int node = 0; node < size; ++node) {
402 int current = node;
403 while (!model.IsEnd(current) && !touched[current]) {
404 touched[current] = true;
405 IntVar* const next_var = model.NextVar(current);
406 if (next_var->Bound()) {
407 current = next_var->Value();
408 }
409 }
410 // Merge the sub-chain starting from 'node' and ending at 'current' with
411 // the existing sub-chain starting at 'current'.
412 starts[ends[current]] = starts[node];
413 ends[starts[node]] = ends[current];
414 }
415
416 // Set the 'end_chain_starts' for every vehicle.
417 std::vector<int64_t> end_chain_starts(num_vehicles);
418 for (int vehicle = 0; vehicle < num_vehicles; ++vehicle) {
419 end_chain_starts[vehicle] = starts[model.End(vehicle)];
420 }
421 return end_chain_starts;
422}
423
424// --- First solution decision builder ---
425
426// IntVarFilteredDecisionBuilder
427
429 std::unique_ptr<IntVarFilteredHeuristic> heuristic)
430 : heuristic_(std::move(heuristic)) {}
431
433 Assignment* const assignment = heuristic_->BuildSolution();
434 if (assignment != nullptr) {
435 VLOG(2) << "Number of decisions: " << heuristic_->number_of_decisions();
436 VLOG(2) << "Number of rejected decisions: "
437 << heuristic_->number_of_rejects();
438 assignment->Restore();
439 } else {
440 solver->Fail();
441 }
442 return nullptr;
443}
444
446 return heuristic_->number_of_decisions();
447}
448
450 return heuristic_->number_of_rejects();
451}
452
454 return absl::StrCat("IntVarFilteredDecisionBuilder(",
455 heuristic_->DebugString(), ")");
456}
457
458// --- First solution heuristics ---
459
460// IntVarFilteredHeuristic
461
463 Solver* solver, const std::vector<IntVar*>& vars,
464 const std::vector<IntVar*>& secondary_vars,
465 LocalSearchFilterManager* filter_manager)
466 : assignment_(solver->MakeAssignment()),
467 solver_(solver),
468 vars_(vars),
469 base_vars_size_(vars.size()),
470 delta_(solver->MakeAssignment()),
471 empty_(solver->MakeAssignment()),
472 filter_manager_(filter_manager),
473 objective_upper_bound_(std::numeric_limits<int64_t>::max()),
474 number_of_decisions_(0),
475 number_of_rejects_(0) {
476 if (!secondary_vars.empty()) {
477 vars_.insert(vars_.end(), secondary_vars.begin(), secondary_vars.end());
478 }
479 assignment_->MutableIntVarContainer()->Resize(vars_.size());
480 is_in_delta_.resize(vars_.size(), false);
481 delta_indices_.reserve(vars_.size());
482}
483
485 number_of_decisions_ = 0;
486 number_of_rejects_ = 0;
487 // Wiping assignment when starting a new search.
488 assignment_->MutableIntVarContainer()->Clear();
489 delta_->MutableIntVarContainer()->Clear();
491 assignment_->MutableIntVarContainer()->Resize(vars_.size());
492}
493
495 // Initialize must be called before the state of the heuristic is changed, in
496 // particular before InitializeSolution() and BuildSolutionInternal().
497 Initialize();
498 if (!InitializeSolution()) {
499 return nullptr;
500 }
501 if (BuildSolutionInternal()) {
502 return assignment_;
503 }
504 return nullptr;
505}
506
508 const std::function<int64_t(int64_t)>& next_accessor) {
509 // Initialize must be called before the state of the heuristic is changed, in
510 // particular before InitializeSolution() and BuildSolutionInternal().
511 Initialize();
512 // NOTE(b/219043402): The filter manager must first be synchronized with a
513 // valid solution that properly connects route starts to route ends in order
514 // for future FilterAccept() calls to correctly detect infeasibilities.
515 if (!InitializeSolution()) {
516 return nullptr;
517 }
518
519 for (int v = 0; v < model_->vehicles(); v++) {
520 int64_t node = model_->Start(v);
521 while (!model_->IsEnd(node)) {
522 const int64_t next = next_accessor(node);
523 DCHECK_NE(next, node);
524 SetNext(node, next, v);
525 SetVehicleIndex(node, v);
526 node = next;
527 }
528 }
529 if (!Evaluate(/*commit=*/true).has_value()) {
531 return nullptr;
532 }
533 if (BuildSolutionInternal()) {
534 return assignment_;
535 }
536 return nullptr;
537}
538
540 bool commit, bool ignore_upper_bound, bool update_upper_bound) {
541 ++number_of_decisions_;
542 const bool accept = FilterAccept(ignore_upper_bound);
543 int64_t objective_upper_bound = objective_upper_bound_;
544 if (accept) {
545 if (filter_manager_ != nullptr) {
546 // objective upper_bound_ is used to reduce the number of potential
547 // insertion candidates, specifically when filter_manager_ filters cost.
548 // Rationale: the best cost candidate will always be valid and will be
549 // inserted so no use accepting degrading ones. However when a candidate
550 // is committed, the upper bound is relaxed to make sure further
551 // (cost-degrading) insertions will be accepted
552 // (cf. SynchronizeFilters()).
553 DCHECK(ignore_upper_bound ||
554 filter_manager_->GetAcceptedObjectiveValue() <=
555 objective_upper_bound_);
556 objective_upper_bound = filter_manager_->GetAcceptedObjectiveValue();
557 if (update_upper_bound) {
558 objective_upper_bound_ = objective_upper_bound;
559 }
560 }
561 if (commit) {
562 const Assignment::IntContainer& delta_container =
563 delta_->IntVarContainer();
564 const int delta_size = delta_container.Size();
565 Assignment::IntContainer* const container =
566 assignment_->MutableIntVarContainer();
567 for (int i = 0; i < delta_size; ++i) {
568 const IntVarElement& delta_element = delta_container.Element(i);
569 IntVar* const var = delta_element.Var();
570 DCHECK_EQ(var, vars_[delta_indices_[i]]);
571 container->AddAtPosition(var, delta_indices_[i])
572 ->SetValue(delta_element.Value());
573 }
575 }
576 } else {
577 ++number_of_rejects_;
578 }
579 // Reset is_in_delta to all false.
580 for (const int delta_index : delta_indices_) {
581 is_in_delta_[delta_index] = false;
582 }
583 delta_->Clear();
584 delta_indices_.clear();
585 return accept ? std::optional<int64_t>{objective_upper_bound} : std::nullopt;
586}
587
589 if (filter_manager_) filter_manager_->Synchronize(assignment_, delta_);
590 // Resetting the upper bound to allow cost-increasing insertions.
591 objective_upper_bound_ = std::numeric_limits<int64_t>::max();
592}
593
594bool IntVarFilteredHeuristic::FilterAccept(bool ignore_upper_bound) {
595 if (!filter_manager_) return true;
596 LocalSearchMonitor* const monitor = solver_->GetLocalSearchMonitor();
597 return filter_manager_->Accept(
598 monitor, delta_, empty_, std::numeric_limits<int64_t>::min(),
599 ignore_upper_bound ? std::numeric_limits<int64_t>::max()
600 : objective_upper_bound_);
601}
602
603// RoutingFilteredHeuristic
604
606 RoutingModel* model, std::function<bool()> stop_search,
607 LocalSearchFilterManager* filter_manager)
608 : IntVarFilteredHeuristic(model->solver(), model->Nexts(),
609 model->CostsAreHomogeneousAcrossVehicles()
610 ? std::vector<IntVar*>()
611 : model->VehicleVars(),
612 filter_manager),
613 model_(model),
614 stop_search_(std::move(stop_search)) {}
615
616bool RoutingFilteredHeuristic::InitializeSolution() {
619
620 // Start by adding partial start chains to current assignment.
621 start_chain_ends_.resize(model()->vehicles());
622 for (int vehicle = 0; vehicle < model()->vehicles(); ++vehicle) {
623 int64_t node = model()->Start(vehicle);
624 while (!model()->IsEnd(node) && Var(node)->Bound()) {
625 const int64_t next = Var(node)->Min();
626 SetNext(node, next, vehicle);
627 SetVehicleIndex(node, vehicle);
628 node = next;
629 }
630 start_chain_ends_[vehicle] = node;
631 }
632
633 end_chain_starts_ = ComputeVehicleEndChainStarts(*model_);
634
635 // Set each route to be the concatenation of the chain at its start and the
636 // chain at its end, without nodes in between.
637 for (int vehicle = 0; vehicle < model()->vehicles(); ++vehicle) {
638 int64_t node = start_chain_ends_[vehicle];
639 if (!model()->IsEnd(node)) {
640 int64_t next = end_chain_starts_[vehicle];
641 SetNext(node, next, vehicle);
642 SetVehicleIndex(node, vehicle);
643 node = next;
644 while (!model()->IsEnd(node)) {
645 next = Var(node)->Min();
646 SetNext(node, next, vehicle);
647 SetVehicleIndex(node, vehicle);
648 node = next;
649 }
650 }
651 }
652
653 if (!Evaluate(/*commit=*/true).has_value()) {
655 return false;
656 }
657 return true;
658}
659
661 model()->ForEachNodeInDisjunctionWithMaxCardinalityFromIndex(
662 node, 1, [this, node](int alternate) {
663 if (node != alternate && !Contains(alternate)) {
664 SetNext(alternate, alternate, -1);
665 }
666 });
667}
668
670 // TODO(user): check that delta_ is empty.
672 absl::btree_set<std::pair<int64_t, int>> empty_vehicles;
673 for (int vehicle = 0; vehicle < model_->vehicles(); ++vehicle) {
674 if (VehicleIsEmpty(vehicle)) {
675 empty_vehicles.insert({model()->GetFixedCostOfVehicle(vehicle), vehicle});
676 }
677 }
678 for (int index = 0; index < model_->Size(); ++index) {
679 if (StopSearch() || empty_vehicles.empty()) return;
680 DCHECK(!IsSecondaryVar(index));
681 if (Contains(index)) continue;
682 for (auto [cost, vehicle] : empty_vehicles) {
683 SetNext(model_->Start(vehicle), index, vehicle);
684 SetNext(index, model_->End(vehicle), vehicle);
685 if (Evaluate(/*commit=*/true).has_value()) {
686 empty_vehicles.erase({cost, vehicle});
687 break;
688 }
689 }
690 }
691}
692
694 // TODO(user): check that delta_ is empty.
696 for (int index = 0; index < model_->Size(); ++index) {
697 DCHECK(!IsSecondaryVar(index));
698 if (!Contains(index)) {
699 SetNext(index, index, -1);
700 }
701 }
702 return true;
703}
704
706 const int num_nexts = model()->Nexts().size();
707 std::vector<bool> to_make_unperformed(num_nexts, false);
708 for (const auto& [pickups, deliveries] :
709 model()->GetPickupAndDeliveryPairs()) {
710 int64_t performed_pickup = -1;
711 for (int64_t pickup : pickups) {
712 if (Contains(pickup) && Value(pickup) != pickup) {
713 performed_pickup = pickup;
714 break;
715 }
716 }
717 int64_t performed_delivery = -1;
718 for (int64_t delivery : deliveries) {
719 if (Contains(delivery) && Value(delivery) != delivery) {
720 performed_delivery = delivery;
721 break;
722 }
723 }
724 if ((performed_pickup == -1) != (performed_delivery == -1)) {
725 if (performed_pickup != -1) {
726 to_make_unperformed[performed_pickup] = true;
727 }
728 if (performed_delivery != -1) {
729 to_make_unperformed[performed_delivery] = true;
730 }
731 }
732 }
733 for (int index = 0; index < num_nexts; ++index) {
734 if (to_make_unperformed[index] || !Contains(index)) continue;
735 const int vehicle =
737 int64_t next = Value(index);
738 while (next < num_nexts && to_make_unperformed[next]) {
739 const int64_t next_of_next = Value(next);
740 SetNext(index, next_of_next, vehicle);
741 SetNext(next, next, -1);
742 next = next_of_next;
743 }
744 }
745}
746
747// CheapestInsertionFilteredHeuristic
748
750 RoutingModel* model, std::function<bool()> stop_search,
751 std::function<int64_t(int64_t, int64_t, int64_t)> evaluator,
752 std::function<int64_t(int64_t)> penalty_evaluator,
753 LocalSearchFilterManager* filter_manager)
754 : RoutingFilteredHeuristic(model, std::move(stop_search), filter_manager),
755 evaluator_(std::move(evaluator)),
756 penalty_evaluator_(std::move(penalty_evaluator)) {}
757
758namespace {
759void ProcessVehicleStartEndCosts(
760 const RoutingModel& model, int node,
761 const std::function<void(int64_t, int)>& process_cost,
762 const Bitset64<int>& vehicle_set, bool ignore_start = false,
763 bool ignore_end = false) {
764 const auto start_end_cost = [&model, ignore_start, ignore_end](int64_t node,
765 int v) {
766 const int64_t start_cost =
767 ignore_start ? 0 : model.GetArcCostForVehicle(model.Start(v), node, v);
768 const int64_t end_cost =
769 ignore_end ? 0 : model.GetArcCostForVehicle(model.End(v), node, v);
770 return CapAdd(start_cost, end_cost);
771 };
772
773 // Iterating over an IntVar domain is faster than calling Contains.
774 // Therefore we iterate on 'vehicles' only if it's smaller than the domain
775 // size of the VehicleVar.
776 const IntVar* const vehicle_var = model.VehicleVar(node);
777 if (vehicle_var->Size() < vehicle_set.size()) {
778 std::unique_ptr<IntVarIterator> it(vehicle_var->MakeDomainIterator(false));
779 for (const int v : InitAndGetValues(it.get())) {
780 if (v < 0 || !vehicle_set[v]) {
781 continue;
782 }
783 process_cost(start_end_cost(node, v), v);
784 }
785 } else {
786 for (const int v : vehicle_set) {
787 if (!vehicle_var->Contains(v)) continue;
788 process_cost(start_end_cost(node, v), v);
789 }
790 }
791}
792} // namespace
793
794std::vector<std::vector<CheapestInsertionFilteredHeuristic::StartEndValue>>
796 absl::Span<const int> vehicles) {
797 // TODO(user): consider checking search limits.
798 const RoutingModel& model = *this->model();
799 std::vector<std::vector<StartEndValue>> start_end_distances_per_node(
800 model.Size());
801
802 Bitset64<int> vehicle_set(model.vehicles());
803 for (int v : vehicles) vehicle_set.Set(v);
804
805 for (int node = 0; node < model.Size(); node++) {
806 if (Contains(node)) continue;
807 std::vector<StartEndValue>& start_end_distances =
808 start_end_distances_per_node[node];
809 start_end_distances.reserve(
810 std::min(model.VehicleVar(node)->Size(),
811 static_cast<uint64_t>(vehicles.size())));
812
813 ProcessVehicleStartEndCosts(
814 model, node,
815 [&start_end_distances](int64_t dist, int v) {
816 start_end_distances.push_back({dist, v});
817 },
818 vehicle_set);
819
820 // Sort the distances for the node to all start/ends of available vehicles
821 // in decreasing order.
822 absl::c_sort(start_end_distances,
823 [](const StartEndValue& first, const StartEndValue& second) {
824 return second < first;
825 });
826 }
827 return start_end_distances_per_node;
828}
829
831 int node, std::vector<StartEndValue>* start_end_distances, SeedQueue* sq) {
832 if (start_end_distances->empty()) {
833 return;
834 }
835
836 // Put the best StartEndValue for this node in the priority queue.
837 StartEndValue& start_end_value = start_end_distances->back();
839 start_end_value.distance = CapOpp(start_end_value.distance);
840 }
841 const int64_t num_allowed_vehicles = model()->VehicleVar(node)->Size();
842 const int64_t neg_penalty = CapOpp(model()->UnperformedPenalty(node));
843 sq->priority_queue.push({.properties = {num_allowed_vehicles, neg_penalty,
844 start_end_value.distance},
845 .vehicle = start_end_value.vehicle,
846 .is_node_index = true,
847 .index = node});
848 start_end_distances->pop_back();
849}
850
852 std::vector<std::vector<StartEndValue>>* start_end_distances_per_node,
853 SeedQueue* sq) {
854 const int num_nodes = model()->Size();
855 DCHECK_EQ(start_end_distances_per_node->size(), num_nodes);
856
857 for (int node = 0; node < num_nodes; node++) {
858 if (Contains(node)) continue;
859 AddSeedNodeToQueue(node, &start_end_distances_per_node->at(node), sq);
860 }
861}
862
864 int64_t predecessor,
865 int64_t successor,
866 int vehicle) {
867 SetValue(predecessor, node);
868 SetValue(node, successor);
870 if (HasSecondaryVars() && vehicle != -1) {
871 SetValue(SecondaryVarIndex(predecessor), vehicle);
872 SetValue(SecondaryVarIndex(node), vehicle);
873 SetValue(SecondaryVarIndex(successor), vehicle);
874 }
875}
876
878 int64_t node_to_insert, int64_t start, int64_t next_after_start,
879 int vehicle, bool ignore_cost,
880 std::vector<NodeInsertion>* node_insertions) {
881 DCHECK(node_insertions != nullptr);
882 int64_t insert_after = start;
883 if (!model()->VehicleVar(node_to_insert)->Contains(vehicle)) return;
884 while (!model()->IsEnd(insert_after)) {
885 const int64_t insert_before =
886 (insert_after == start) ? next_after_start : Value(insert_after);
887 const int hint_weight = IsHint(insert_after, node_to_insert) +
888 IsHint(node_to_insert, insert_before) -
889 IsHint(insert_after, insert_before);
890 if (evaluator_ == nullptr) {
891 InsertBetween(node_to_insert, insert_after, insert_before, vehicle);
892 std::optional<int64_t> insertion_cost =
893 Evaluate(/*commit=*/false, /*ignore_upper_bound=*/hint_weight > 0,
894 /*update_upper_bound=*/hint_weight >= 0);
895 if (insertion_cost.has_value()) {
896 node_insertions->push_back(
897 {insert_after, vehicle, -hint_weight, *insertion_cost});
898 }
899 } else {
900 node_insertions->push_back(
901 {insert_after, vehicle, -hint_weight,
902 ignore_cost
903 ? 0
904 : GetInsertionCostForNodeAtPosition(node_to_insert, insert_after,
905 insert_before, vehicle)});
906 }
907 insert_after = insert_before;
908 }
909}
910
912 int64_t node_to_insert, int64_t insert_after, int64_t insert_before,
913 int vehicle) const {
914 DCHECK(evaluator_ != nullptr);
915 return CapSub(CapAdd(evaluator_(insert_after, node_to_insert, vehicle),
916 evaluator_(node_to_insert, insert_before, vehicle)),
917 evaluator_(insert_after, insert_before, vehicle));
918}
919
921 int64_t node_to_insert) const {
922 if (penalty_evaluator_ != nullptr) {
923 return penalty_evaluator_(node_to_insert);
924 }
925 return std::numeric_limits<int64_t>::max();
926}
927
928// GlobalCheapestInsertionFilteredHeuristic
929
932 RoutingModel* model, std::function<bool()> stop_search,
933 std::function<int64_t(int64_t, int64_t, int64_t)> evaluator,
934 std::function<int64_t(int64_t)> penalty_evaluator,
935 LocalSearchFilterManager* filter_manager,
938 model, std::move(stop_search), std::move(evaluator),
939 std::move(penalty_evaluator), filter_manager),
940 gci_params_(parameters),
941 node_index_to_vehicle_(model->Size(), -1),
942 node_index_to_neighbors_by_cost_class_(nullptr),
943 empty_vehicle_type_curator_(nullptr) {
944 CHECK_GT(gci_params_.neighbors_ratio, 0);
945 CHECK_LE(gci_params_.neighbors_ratio, 1);
946 CHECK_GE(gci_params_.min_neighbors, 1);
947}
948
949bool GlobalCheapestInsertionFilteredHeuristic::CheckVehicleIndices() const {
950 std::vector<bool> node_is_visited(model()->Size(), false);
951 for (int v = 0; v < model()->vehicles(); v++) {
952 for (int node = model()->Start(v); !model()->IsEnd(node);
953 node = Value(node)) {
954 if (node_index_to_vehicle_[node] != v) {
955 return false;
956 }
957 node_is_visited[node] = true;
958 }
959 }
960
961 for (int node = 0; node < model()->Size(); node++) {
962 if (!node_is_visited[node] && node_index_to_vehicle_[node] != -1) {
963 return false;
964 }
965 }
966
967 return true;
968}
969
971 // Get neighbors.
972 double neighbors_ratio_used = 1;
973 node_index_to_neighbors_by_cost_class_ =
974 model()->GetOrCreateNodeNeighborsByCostClass(gci_params_.neighbors_ratio,
975 gci_params_.min_neighbors,
976 neighbors_ratio_used);
977 if (neighbors_ratio_used == 1) {
978 gci_params_.use_neighbors_ratio_for_initialization = false;
979 }
980
981 if (empty_vehicle_type_curator_ == nullptr) {
982 empty_vehicle_type_curator_ = std::make_unique<VehicleTypeCurator>(
983 model()->GetVehicleTypeContainer());
984 }
985 // Store all empty vehicles in the empty_vehicle_type_curator_.
986 empty_vehicle_type_curator_->Reset(
987 [this](int vehicle) { return VehicleIsEmpty(vehicle); });
988 // Insert partially inserted pairs.
989 const std::vector<PickupDeliveryPair>& pickup_delivery_pairs =
990 model()->GetPickupAndDeliveryPairs();
991 std::map<int64_t, std::vector<int>> pairs_to_insert_by_bucket;
992 absl::flat_hash_map<int, std::map<int64_t, std::vector<int>>>
993 vehicle_to_pair_nodes;
994 for (int index = 0; index < pickup_delivery_pairs.size(); index++) {
995 const PickupDeliveryPair& pickup_delivery_pair =
996 pickup_delivery_pairs[index];
997 const auto& [pickups, deliveries] = pickup_delivery_pair;
998 int pickup_vehicle = -1;
999 for (int64_t pickup : pickups) {
1000 if (Contains(pickup)) {
1001 pickup_vehicle = node_index_to_vehicle_[pickup];
1002 break;
1003 }
1004 }
1005 int delivery_vehicle = -1;
1006 for (int64_t delivery : deliveries) {
1007 if (Contains(delivery)) {
1008 delivery_vehicle = node_index_to_vehicle_[delivery];
1009 break;
1010 }
1011 }
1012 if (pickup_vehicle < 0 && delivery_vehicle < 0) {
1013 pairs_to_insert_by_bucket[GetBucketOfPair(pickup_delivery_pair)]
1014 .push_back(index);
1015 }
1016 if (pickup_vehicle >= 0 && delivery_vehicle < 0) {
1017 std::vector<int>& pair_nodes = vehicle_to_pair_nodes[pickup_vehicle][1];
1018 for (int64_t delivery : deliveries) {
1019 pair_nodes.push_back(delivery);
1020 }
1021 }
1022 if (pickup_vehicle < 0 && delivery_vehicle >= 0) {
1023 std::vector<int>& pair_nodes = vehicle_to_pair_nodes[delivery_vehicle][1];
1024 for (int64_t pickup : pickups) {
1025 pair_nodes.push_back(pickup);
1026 }
1027 }
1028 }
1029
1030 const auto unperform_unassigned_and_check = [this]() {
1032 Evaluate(/*commit=*/true).has_value();
1033 };
1034 for (const auto& [vehicle, nodes] : vehicle_to_pair_nodes) {
1035 if (!InsertNodesOnRoutes(nodes, {vehicle})) {
1036 return unperform_unassigned_and_check();
1037 }
1038 }
1039
1040 if (!InsertPairsAndNodesByRequirementTopologicalOrder()) {
1041 return unperform_unassigned_and_check();
1042 }
1043
1044 // TODO(user): Adapt the pair insertions to also support seed and
1045 // sequential insertion.
1046 if (!InsertPairs(pairs_to_insert_by_bucket)) {
1047 return unperform_unassigned_and_check();
1048 }
1049 std::map<int64_t, std::vector<int>> nodes_by_bucket;
1050 for (int node = 0; node < model()->Size(); ++node) {
1051 if (!Contains(node) && !model()->IsPickup(node) &&
1052 !model()->IsDelivery(node)) {
1053 nodes_by_bucket[GetBucketOfNode(node)].push_back(node);
1054 }
1055 }
1056 InsertFarthestNodesAsSeeds();
1057 if (gci_params_.is_sequential) {
1058 if (!SequentialInsertNodes(nodes_by_bucket)) {
1059 return unperform_unassigned_and_check();
1060 }
1061 } else if (!InsertNodesOnRoutes(nodes_by_bucket, {})) {
1062 return unperform_unassigned_and_check();
1063 }
1064 DCHECK(CheckVehicleIndices());
1065 return unperform_unassigned_and_check();
1066}
1067
1068bool GlobalCheapestInsertionFilteredHeuristic::
1069 InsertPairsAndNodesByRequirementTopologicalOrder() {
1070 const std::vector<PickupDeliveryPair>& pickup_delivery_pairs =
1071 model()->GetPickupAndDeliveryPairs();
1072 for (const std::vector<int>& types :
1073 model()->GetTopologicallySortedVisitTypes()) {
1074 for (int type : types) {
1075 std::map<int64_t, std::vector<int>> pairs_to_insert_by_bucket;
1076 for (int index : model()->GetPairIndicesOfType(type)) {
1077 pairs_to_insert_by_bucket[GetBucketOfPair(pickup_delivery_pairs[index])]
1078 .push_back(index);
1079 }
1080 if (!InsertPairs(pairs_to_insert_by_bucket)) return false;
1081 std::map<int64_t, std::vector<int>> nodes_by_bucket;
1082 for (int node : model()->GetSingleNodesOfType(type)) {
1083 nodes_by_bucket[GetBucketOfNode(node)].push_back(node);
1084 }
1085 if (!InsertNodesOnRoutes(nodes_by_bucket, {})) return false;
1086 }
1087 }
1088 return true;
1089}
1090
1091bool GlobalCheapestInsertionFilteredHeuristic::InsertPairs(
1092 const std::map<int64_t, std::vector<int>>& pair_indices_by_bucket) {
1093 AdjustablePriorityQueue<PairEntry> priority_queue;
1094 std::vector<PairEntries> pickup_to_entries;
1095 std::vector<PairEntries> delivery_to_entries;
1096 const std::vector<PickupDeliveryPair>& pickup_delivery_pairs =
1097 model()->GetPickupAndDeliveryPairs();
1098 auto pair_is_performed = [this, &pickup_delivery_pairs](int pair_index) {
1099 const auto& [pickups, deliveries] = pickup_delivery_pairs[pair_index];
1100 for (int64_t pickup : pickups) {
1101 if (Contains(pickup)) return true;
1102 }
1103 for (int64_t delivery : deliveries) {
1104 if (Contains(delivery)) return true;
1105 }
1106 return false;
1107 };
1108 absl::flat_hash_set<int> pair_indices_to_insert;
1109 for (const auto& [bucket, pair_indices] : pair_indices_by_bucket) {
1110 for (const int pair_index : pair_indices) {
1111 if (!pair_is_performed(pair_index)) {
1112 pair_indices_to_insert.insert(pair_index);
1113 }
1114 }
1115 if (!InitializePairPositions(pair_indices_to_insert, &priority_queue,
1116 &pickup_to_entries, &delivery_to_entries)) {
1117 return false;
1118 }
1119 while (!priority_queue.IsEmpty()) {
1120 if (StopSearchAndCleanup(&priority_queue)) {
1121 return false;
1122 }
1123 PairEntry* const entry = priority_queue.Top();
1124 const int64_t pickup = entry->pickup_to_insert();
1125 const int64_t delivery = entry->delivery_to_insert();
1126 if (Contains(pickup) || Contains(delivery)) {
1127 DeletePairEntry(entry, &priority_queue, &pickup_to_entries,
1128 &delivery_to_entries);
1129 continue;
1130 }
1131
1132 const int entry_vehicle = entry->vehicle();
1133 if (entry_vehicle == -1) {
1134 // Pair is unperformed.
1135 SetNext(pickup, pickup, -1);
1136 SetNext(delivery, delivery, -1);
1137 if (!Evaluate(/*commit=*/true).has_value()) {
1138 DeletePairEntry(entry, &priority_queue, &pickup_to_entries,
1139 &delivery_to_entries);
1140 }
1141 continue;
1142 }
1143
1144 // Pair is performed.
1145 if (UseEmptyVehicleTypeCuratorForVehicle(entry_vehicle)) {
1146 if (!InsertPairEntryUsingEmptyVehicleTypeCurator(
1147 pair_indices_to_insert, entry, &priority_queue,
1148 &pickup_to_entries, &delivery_to_entries)) {
1149 return false;
1150 }
1151 // The entry corresponded to an insertion on an empty vehicle, which was
1152 // handled by the call to InsertPairEntryUsingEmptyVehicleTypeCurator().
1153 continue;
1154 }
1155
1156 const int64_t pickup_insert_after = entry->pickup_insert_after();
1157 const int64_t pickup_insert_before = Value(pickup_insert_after);
1158 InsertBetween(pickup, pickup_insert_after, pickup_insert_before);
1159
1160 const int64_t delivery_insert_after = entry->delivery_insert_after();
1161 const int64_t delivery_insert_before = (delivery_insert_after == pickup)
1162 ? pickup_insert_before
1163 : Value(delivery_insert_after);
1164 InsertBetween(delivery, delivery_insert_after, delivery_insert_before);
1165 if (Evaluate(/*commit=*/true).has_value()) {
1166 if (!UpdateAfterPairInsertion(
1167 pair_indices_to_insert, entry_vehicle, pickup,
1168 pickup_insert_after, delivery, delivery_insert_after,
1169 &priority_queue, &pickup_to_entries, &delivery_to_entries)) {
1170 return false;
1171 }
1172 } else {
1173 DeletePairEntry(entry, &priority_queue, &pickup_to_entries,
1174 &delivery_to_entries);
1175 }
1176 }
1177 // In case all pairs could not be inserted, pushing uninserted ones to the
1178 // next bucket.
1179 for (auto it = pair_indices_to_insert.begin(),
1180 last = pair_indices_to_insert.end();
1181 it != last;) {
1182 if (pair_is_performed(*it)) {
1183 pair_indices_to_insert.erase(it++);
1184 } else {
1185 ++it;
1186 }
1187 }
1188 }
1189 return true;
1190}
1191
1192bool GlobalCheapestInsertionFilteredHeuristic::
1193 InsertPairEntryUsingEmptyVehicleTypeCurator(
1194 const absl::flat_hash_set<int>& pair_indices,
1195 GlobalCheapestInsertionFilteredHeuristic::PairEntry* const pair_entry,
1196 AdjustablePriorityQueue<
1197 GlobalCheapestInsertionFilteredHeuristic::PairEntry>*
1198 priority_queue,
1199 std::vector<GlobalCheapestInsertionFilteredHeuristic::PairEntries>*
1200 pickup_to_entries,
1201 std::vector<GlobalCheapestInsertionFilteredHeuristic::PairEntries>*
1202 delivery_to_entries) {
1203 const int entry_vehicle = pair_entry->vehicle();
1204 DCHECK(UseEmptyVehicleTypeCuratorForVehicle(entry_vehicle));
1205
1206 // Trying to insert on an empty vehicle.
1207 // As we only have one pair_entry per empty vehicle type, we try inserting on
1208 // all vehicles of this type with the same fixed cost, as they all have the
1209 // same insertion value.
1210 const int64_t pickup = pair_entry->pickup_to_insert();
1211 const int64_t delivery = pair_entry->delivery_to_insert();
1212 const int64_t entry_fixed_cost =
1213 model()->GetFixedCostOfVehicle(entry_vehicle);
1214 auto vehicle_is_compatible = [this, entry_fixed_cost, pickup,
1215 delivery](int vehicle) {
1216 if (model()->GetFixedCostOfVehicle(vehicle) != entry_fixed_cost) {
1217 return false;
1218 }
1219 // NOTE: Only empty vehicles should be in the vehicle_curator_.
1220 DCHECK(VehicleIsEmpty(vehicle));
1221 const int64_t end = model()->End(vehicle);
1222 InsertBetween(pickup, model()->Start(vehicle), end, vehicle);
1223 InsertBetween(delivery, pickup, end, vehicle);
1224 return Evaluate(/*commit=*/true).has_value();
1225 };
1226 // Since the vehicles of the same type are sorted by increasing fixed
1227 // cost by the curator, we can stop as soon as a vehicle with a fixed cost
1228 // higher than the entry_fixed_cost is found which is empty, and adapt the
1229 // pair entry with this new vehicle.
1230 auto stop_and_return_vehicle = [this, entry_fixed_cost](int vehicle) {
1231 return model()->GetFixedCostOfVehicle(vehicle) > entry_fixed_cost;
1232 };
1233 const auto [compatible_vehicle, next_fixed_cost_empty_vehicle] =
1234 empty_vehicle_type_curator_->GetCompatibleVehicleOfType(
1235 empty_vehicle_type_curator_->Type(entry_vehicle),
1236 vehicle_is_compatible, stop_and_return_vehicle);
1237 if (compatible_vehicle >= 0) {
1238 // The pair was inserted on this vehicle.
1239 const int64_t vehicle_start = model()->Start(compatible_vehicle);
1240 const int num_previous_vehicle_entries =
1241 pickup_to_entries->at(vehicle_start).size() +
1242 delivery_to_entries->at(vehicle_start).size();
1243 if (!UpdateAfterPairInsertion(
1244 pair_indices, compatible_vehicle, pickup, vehicle_start, delivery,
1245 pickup, priority_queue, pickup_to_entries, delivery_to_entries)) {
1246 return false;
1247 }
1248 if (compatible_vehicle != entry_vehicle) {
1249 // The pair was inserted on another empty vehicle of the same type
1250 // and same fixed cost as entry_vehicle.
1251 // Since this vehicle is empty and has the same fixed cost as the
1252 // entry_vehicle, it shouldn't be the representative of empty vehicles
1253 // for any pickup/delivery in the priority queue.
1254 DCHECK(
1255 num_previous_vehicle_entries == 0 ||
1256 model()->GetVehicleClassIndexOfVehicle(compatible_vehicle).value() !=
1257 model()->GetVehicleClassIndexOfVehicle(entry_vehicle).value());
1258 return true;
1259 }
1260 // The previously unused entry_vehicle is now used, so we use the next
1261 // available vehicle of the same type to compute and store insertions on
1262 // empty vehicles.
1263 const int new_empty_vehicle =
1264 empty_vehicle_type_curator_->GetLowestFixedCostVehicleOfType(
1265 empty_vehicle_type_curator_->Type(compatible_vehicle));
1266
1267 if (new_empty_vehicle >= 0) {
1268 DCHECK(VehicleIsEmpty(new_empty_vehicle));
1269 // Add node entries after this vehicle start for uninserted pairs which
1270 // don't have entries on this empty vehicle.
1271 // Clearing all existing entries before adding updated ones. Some could
1272 // have been added when next_fixed_cost_empty_vehicle >= 0 (see the next
1273 // branch).
1274 const int64_t new_empty_vehicle_start = model()->Start(new_empty_vehicle);
1275 const std::vector<PairEntry*> to_remove(
1276 pickup_to_entries->at(new_empty_vehicle_start).begin(),
1277 pickup_to_entries->at(new_empty_vehicle_start).end());
1278 for (PairEntry* entry : to_remove) {
1279 DeletePairEntry(entry, priority_queue, pickup_to_entries,
1280 delivery_to_entries);
1281 }
1282 if (!AddPairEntriesWithPickupAfter(
1283 pair_indices, new_empty_vehicle, new_empty_vehicle_start,
1284 /*skip_entries_inserting_delivery_after=*/-1, priority_queue,
1285 pickup_to_entries, delivery_to_entries)) {
1286 return false;
1287 }
1288 }
1289 } else if (next_fixed_cost_empty_vehicle >= 0) {
1290 // Could not insert on this vehicle or any other vehicle of the same type
1291 // with the same fixed cost, but found an empty vehicle of this type with
1292 // higher fixed cost.
1293 DCHECK(VehicleIsEmpty(next_fixed_cost_empty_vehicle));
1294 // Update the pair entry to correspond to an insertion on this
1295 // next_fixed_cost_empty_vehicle instead of the previous entry_vehicle.
1296 pair_entry->set_vehicle(next_fixed_cost_empty_vehicle);
1297 pickup_to_entries->at(pair_entry->pickup_insert_after()).erase(pair_entry);
1298 pair_entry->set_pickup_insert_after(
1299 model()->Start(next_fixed_cost_empty_vehicle));
1300 pickup_to_entries->at(pair_entry->pickup_insert_after()).insert(pair_entry);
1301 DCHECK_EQ(pair_entry->delivery_insert_after(), pickup);
1302 UpdatePairEntry(pair_entry, priority_queue);
1303 } else {
1304 DeletePairEntry(pair_entry, priority_queue, pickup_to_entries,
1305 delivery_to_entries);
1306 }
1307
1308 return true;
1309}
1310
1312 public:
1313 struct Entry {
1314 bool operator<(const Entry& other) const {
1315 if (bucket != other.bucket) {
1316 return bucket < other.bucket;
1317 }
1318 if (value != other.value) {
1319 return value < other.value;
1320 }
1321 if ((vehicle == -1) ^ (other.vehicle == -1)) {
1322 return other.vehicle == -1;
1323 }
1324 return std::tie(insert_after, node_to_insert, vehicle) <
1325 std::tie(other.insert_after, other.node_to_insert, other.vehicle);
1326 }
1327 int64_t value;
1332 };
1333
1334 explicit NodeEntryQueue(int num_nodes)
1335 : entries_(num_nodes), touched_entries_(num_nodes) {}
1336 void Clear() {
1337 priority_queue_.Clear();
1338 for (Entries& entries : entries_) entries.Clear();
1339 touched_entries_.SparseClearAll();
1340 }
1341 bool IsEmpty() const {
1342 return priority_queue_.IsEmpty() &&
1343 touched_entries_.NumberOfSetCallsWithDifferentArguments() == 0;
1344 }
1345 bool IsEmpty(int64_t insert_after) const {
1346 return insert_after >= entries_.size() ||
1347 entries_[insert_after].entries.empty();
1348 }
1350 DCHECK(!IsEmpty());
1351 for (int touched : touched_entries_.PositionsSetAtLeastOnce()) {
1352 SortInsertions(&entries_[touched]);
1353 }
1354 touched_entries_.SparseClearAll();
1355 DCHECK(!priority_queue_.IsEmpty());
1356 Entries* entries = priority_queue_.Top();
1357 DCHECK(!entries->entries.empty());
1358 return entries->Top();
1359 }
1360 void Pop() {
1361 if (IsEmpty()) return;
1362 CHECK_EQ(touched_entries_.NumberOfSetCallsWithDifferentArguments(), 0);
1363 Entries* top = priority_queue_.Top();
1364 if (top->IncrementTop()) {
1365 priority_queue_.NoteChangedPriority(top);
1366 } else {
1367 priority_queue_.Remove(top);
1368 top->Clear();
1369 }
1370 }
1371 void ClearInsertions(int64_t insert_after) {
1372 if (IsEmpty(insert_after)) return;
1373 Entries& entries = entries_[insert_after];
1374 if (priority_queue_.Contains(&entries)) {
1375 priority_queue_.Remove(&entries);
1376 }
1377 entries.Clear();
1378 }
1379 void PushInsertion(int64_t node, int64_t insert_after, int vehicle,
1380 int bucket, int64_t value) {
1381 entries_[insert_after].entries.push_back(
1382 {value, node, insert_after, vehicle, bucket});
1383 touched_entries_.Set(insert_after);
1384 }
1385
1386 private:
1387 struct Entries {
1388 bool operator<(const Entries& other) const {
1389 DCHECK(!entries.empty());
1390 DCHECK(!other.entries.empty());
1391 return other.entries[other.top] < entries[top];
1392 }
1393 void Clear() {
1394 entries.clear();
1395 top = 0;
1396 heap_index = -1;
1397 }
1398 void SetHeapIndex(int index) { heap_index = index; }
1399 int GetHeapIndex() const { return heap_index; }
1400 bool IncrementTop() {
1401 ++top;
1402 return top < entries.size();
1403 }
1404 Entry* Top() { return &entries[top]; }
1405
1406 std::vector<Entry> entries;
1407 int top = 0;
1408 int heap_index = -1;
1409 };
1410
1411 void SortInsertions(Entries* entries) {
1412 entries->top = 0;
1413 if (entries->entries.empty()) return;
1414 absl::c_sort(entries->entries);
1415 if (!priority_queue_.Contains(entries)) {
1416 priority_queue_.Add(entries);
1417 } else {
1418 priority_queue_.NoteChangedPriority(entries);
1419 }
1420 }
1421
1422 AdjustablePriorityQueue<Entries> priority_queue_;
1423 std::vector<Entries> entries_;
1424 SparseBitset<int> touched_entries_;
1425};
1426
1427bool GlobalCheapestInsertionFilteredHeuristic::InsertNodesOnRoutes(
1428 const std::map<int64_t, std::vector<int>>& nodes_by_bucket,
1429 const absl::flat_hash_set<int>& vehicles) {
1430 NodeEntryQueue queue(model()->Nexts().size());
1431 SparseBitset<int> nodes_to_insert(model()->Size());
1432 for (const auto& [bucket, nodes] : nodes_by_bucket) {
1433 for (int node : nodes) {
1434 nodes_to_insert.Set(node);
1435 }
1436 if (!InitializePositions(nodes_to_insert, vehicles, &queue)) {
1437 return false;
1438 }
1439 // The following boolean indicates whether or not all vehicles are being
1440 // considered for insertion of the nodes simultaneously.
1441 // In the sequential version of the heuristic, as well as when inserting
1442 // single pickup or deliveries from pickup/delivery pairs, this will be
1443 // false. In the general parallel version of the heuristic, all_vehicles is
1444 // true.
1445 const bool all_vehicles =
1446 vehicles.empty() || vehicles.size() == model()->vehicles();
1447
1448 while (!queue.IsEmpty()) {
1449 const NodeEntryQueue::Entry* node_entry = queue.Top();
1450 if (StopSearch()) return false;
1451 const int64_t node_to_insert = node_entry->node_to_insert;
1452 if (Contains(node_to_insert)) {
1453 queue.Pop();
1454 continue;
1455 }
1456
1457 const int entry_vehicle = node_entry->vehicle;
1458 if (entry_vehicle == -1) {
1459 DCHECK(all_vehicles);
1460 // Make node unperformed.
1461 SetNext(node_to_insert, node_to_insert, -1);
1462 if (!Evaluate(/*commit=*/true).has_value()) {
1463 queue.Pop();
1464 }
1465 continue;
1466 }
1467
1468 // Make node performed.
1469 if (UseEmptyVehicleTypeCuratorForVehicle(entry_vehicle, all_vehicles)) {
1470 DCHECK(all_vehicles);
1471 if (!InsertNodeEntryUsingEmptyVehicleTypeCurator(
1472 nodes_to_insert, all_vehicles, &queue)) {
1473 return false;
1474 }
1475 continue;
1476 }
1477
1478 const int64_t insert_after = node_entry->insert_after;
1479 InsertBetween(node_to_insert, insert_after, Value(insert_after));
1480 if (Evaluate(/*commit=*/true).has_value()) {
1481 if (!UpdateAfterNodeInsertion(nodes_to_insert, entry_vehicle,
1482 node_to_insert, insert_after,
1483 all_vehicles, &queue)) {
1484 return false;
1485 }
1486 } else {
1487 queue.Pop();
1488 }
1489 }
1490 // In case all nodes could not be inserted, pushing uninserted ones to the
1491 // next bucket.
1492 std::vector<int> non_inserted_nodes;
1493 non_inserted_nodes.reserve(
1494 nodes_to_insert.NumberOfSetCallsWithDifferentArguments());
1495 for (int node : nodes_to_insert.PositionsSetAtLeastOnce()) {
1496 if (!Contains(node)) non_inserted_nodes.push_back(node);
1497 }
1498 nodes_to_insert.SparseClearAll();
1499 for (int node : non_inserted_nodes) {
1500 nodes_to_insert.Set(node);
1501 }
1502 }
1503 return true;
1504}
1505
1506bool GlobalCheapestInsertionFilteredHeuristic::
1507 InsertNodeEntryUsingEmptyVehicleTypeCurator(const SparseBitset<int>& nodes,
1508 bool all_vehicles,
1509 NodeEntryQueue* queue) {
1510 const NodeEntryQueue::Entry* node_entry = queue->Top();
1511 const int entry_vehicle = node_entry->vehicle;
1512 DCHECK(UseEmptyVehicleTypeCuratorForVehicle(entry_vehicle, all_vehicles));
1513
1514 // Trying to insert on an empty vehicle, and all vehicles are being
1515 // considered simultaneously.
1516 // As we only have one node_entry per type, we try inserting on all vehicles
1517 // of this type with the same fixed cost as they all have the same insertion
1518 // value.
1519 const int64_t node_to_insert = node_entry->node_to_insert;
1520 const int bucket = node_entry->bucket;
1521 const int64_t entry_fixed_cost =
1522 model()->GetFixedCostOfVehicle(entry_vehicle);
1523 auto vehicle_is_compatible = [this, entry_fixed_cost,
1524 node_to_insert](int vehicle) {
1525 if (model()->GetFixedCostOfVehicle(vehicle) != entry_fixed_cost) {
1526 return false;
1527 }
1528 // NOTE: Only empty vehicles should be in the vehicle_curator_.
1529 DCHECK(VehicleIsEmpty(vehicle));
1530 InsertBetween(node_to_insert, model()->Start(vehicle),
1531 model()->End(vehicle), vehicle);
1532 return Evaluate(/*commit=*/true).has_value();
1533 };
1534 // Since the vehicles of the same type are sorted by increasing fixed
1535 // cost by the curator, we can stop as soon as an empty vehicle with a fixed
1536 // cost higher than the entry_fixed_cost is found, and add new entries for
1537 // this new vehicle.
1538 auto stop_and_return_vehicle = [this, entry_fixed_cost](int vehicle) {
1539 return model()->GetFixedCostOfVehicle(vehicle) > entry_fixed_cost;
1540 };
1541 const auto [compatible_vehicle, next_fixed_cost_empty_vehicle] =
1542 empty_vehicle_type_curator_->GetCompatibleVehicleOfType(
1543 empty_vehicle_type_curator_->Type(entry_vehicle),
1544 vehicle_is_compatible, stop_and_return_vehicle);
1545 if (compatible_vehicle >= 0) {
1546 // The node was inserted on this vehicle.
1547 const int64_t compatible_start = model()->Start(compatible_vehicle);
1548 const bool no_prior_entries_for_this_vehicle =
1549 queue->IsEmpty(compatible_start);
1550 if (!UpdateAfterNodeInsertion(nodes, compatible_vehicle, node_to_insert,
1551 compatible_start, all_vehicles, queue)) {
1552 return false;
1553 }
1554 if (compatible_vehicle != entry_vehicle) {
1555 // The node was inserted on another empty vehicle of the same type
1556 // and same fixed cost as entry_vehicle.
1557 // Since this vehicle is empty and has the same fixed cost as the
1558 // entry_vehicle, it shouldn't be the representative of empty vehicles
1559 // for any node in the priority queue.
1560 DCHECK(
1561 no_prior_entries_for_this_vehicle ||
1562 model()->GetVehicleClassIndexOfVehicle(compatible_vehicle).value() !=
1563 model()->GetVehicleClassIndexOfVehicle(entry_vehicle).value());
1564 return true;
1565 }
1566 // The previously unused entry_vehicle is now used, so we use the next
1567 // available vehicle of the same type to compute and store insertions on
1568 // empty vehicles.
1569 const int new_empty_vehicle =
1570 empty_vehicle_type_curator_->GetLowestFixedCostVehicleOfType(
1571 empty_vehicle_type_curator_->Type(compatible_vehicle));
1572
1573 if (new_empty_vehicle >= 0) {
1574 DCHECK(VehicleIsEmpty(new_empty_vehicle));
1575 // Add node entries after this vehicle start for uninserted nodes which
1576 // don't have entries on this empty vehicle.
1577 // Clearing all existing entries before adding updated ones. Some could
1578 // have been added when next_fixed_cost_empty_vehicle >= 0 (see the next
1579 // branch).
1580 const int64_t new_empty_vehicle_start = model()->Start(new_empty_vehicle);
1581 queue->ClearInsertions(new_empty_vehicle_start);
1582 if (!AddNodeEntriesAfter(nodes, new_empty_vehicle,
1583 new_empty_vehicle_start, all_vehicles, queue)) {
1584 return false;
1585 }
1586 }
1587 } else if (next_fixed_cost_empty_vehicle >= 0) {
1588 // Could not insert on this vehicle or any other vehicle of the same
1589 // type with the same fixed cost, but found an empty vehicle of this type
1590 // with higher fixed cost.
1591 DCHECK(VehicleIsEmpty(next_fixed_cost_empty_vehicle));
1592 // Update the insertion entry to be on next_empty_vehicle instead of the
1593 // previous entry_vehicle.
1594 queue->Pop();
1595 const int64_t insert_after = model()->Start(next_fixed_cost_empty_vehicle);
1596 const int64_t insertion_cost = GetInsertionCostForNodeAtPosition(
1597 node_to_insert, insert_after, Value(insert_after),
1598 next_fixed_cost_empty_vehicle);
1599 const int64_t penalty_shift =
1600 absl::GetFlag(FLAGS_routing_shift_insertion_cost_by_penalty)
1601 ? GetUnperformedValue(node_to_insert)
1602 : 0;
1603 queue->PushInsertion(node_to_insert, insert_after,
1604 next_fixed_cost_empty_vehicle, bucket,
1605 CapSub(insertion_cost, penalty_shift));
1606 } else {
1607 queue->Pop();
1608 }
1609
1610 return true;
1611}
1612
1613bool GlobalCheapestInsertionFilteredHeuristic::SequentialInsertNodes(
1614 const std::map<int64_t, std::vector<int>>& nodes_by_bucket) {
1615 std::vector<bool> is_vehicle_used;
1616 absl::flat_hash_set<int> used_vehicles;
1617 std::vector<int> unused_vehicles;
1618
1619 DetectUsedVehicles(&is_vehicle_used, &unused_vehicles, &used_vehicles);
1620 if (!used_vehicles.empty() &&
1621 !InsertNodesOnRoutes(nodes_by_bucket, used_vehicles)) {
1622 return false;
1623 }
1624
1625 std::vector<std::vector<StartEndValue>> start_end_distances_per_node =
1626 ComputeStartEndDistanceForVehicles(unused_vehicles);
1627 SeedQueue first_node_queue(/*prioritize_farthest_nodes=*/false);
1628 InitializeSeedQueue(&start_end_distances_per_node, &first_node_queue);
1629
1630 int vehicle = InsertSeedNode(&start_end_distances_per_node, &first_node_queue,
1631 &is_vehicle_used);
1632
1633 while (vehicle >= 0) {
1634 if (!InsertNodesOnRoutes(nodes_by_bucket, {vehicle})) {
1635 return false;
1636 }
1637 vehicle = InsertSeedNode(&start_end_distances_per_node, &first_node_queue,
1638 &is_vehicle_used);
1639 }
1640 return true;
1641}
1642
1643void GlobalCheapestInsertionFilteredHeuristic::DetectUsedVehicles(
1644 std::vector<bool>* is_vehicle_used, std::vector<int>* unused_vehicles,
1645 absl::flat_hash_set<int>* used_vehicles) {
1646 is_vehicle_used->clear();
1647 is_vehicle_used->resize(model()->vehicles());
1648
1649 used_vehicles->clear();
1650 used_vehicles->reserve(model()->vehicles());
1651
1652 unused_vehicles->clear();
1653 unused_vehicles->reserve(model()->vehicles());
1654
1655 for (int vehicle = 0; vehicle < model()->vehicles(); vehicle++) {
1656 if (!VehicleIsEmpty(vehicle)) {
1657 (*is_vehicle_used)[vehicle] = true;
1658 used_vehicles->insert(vehicle);
1659 } else {
1660 (*is_vehicle_used)[vehicle] = false;
1661 unused_vehicles->push_back(vehicle);
1662 }
1663 }
1664}
1665
1666bool GlobalCheapestInsertionFilteredHeuristic::IsCheapestClassRepresentative(
1667 int vehicle) const {
1668 if (VehicleIsEmpty(vehicle)) {
1669 // We only consider the least expensive empty vehicle of each type for
1670 // entries of the same vehicle class.
1671 const int curator_vehicle =
1672 empty_vehicle_type_curator_->GetLowestFixedCostVehicleOfType(
1673 empty_vehicle_type_curator_->Type(vehicle));
1674 if (curator_vehicle != vehicle &&
1675 model()->GetVehicleClassIndexOfVehicle(curator_vehicle).value() ==
1676 model()->GetVehicleClassIndexOfVehicle(vehicle).value()) {
1677 return false;
1678 }
1679 }
1680 return true;
1681}
1682
1683void GlobalCheapestInsertionFilteredHeuristic::InsertFarthestNodesAsSeeds() {
1684 // TODO(user): consider checking search limits.
1685 if (gci_params_.farthest_seeds_ratio <= 0) return;
1686 // Insert at least 1 farthest Seed if the parameter is positive.
1687 const int num_seeds = static_cast<int>(
1688 std::ceil(gci_params_.farthest_seeds_ratio * model()->vehicles()));
1689
1690 std::vector<bool> is_vehicle_used;
1691 absl::flat_hash_set<int> used_vehicles;
1692 std::vector<int> unused_vehicles;
1693 DetectUsedVehicles(&is_vehicle_used, &unused_vehicles, &used_vehicles);
1694 std::vector<std::vector<StartEndValue>> start_end_distances_per_node =
1695 ComputeStartEndDistanceForVehicles(unused_vehicles);
1696
1697 // Priority queue where the Seeds with a larger distance are given higher
1698 // priority.
1699 SeedQueue farthest_node_queue(/*prioritize_farthest_nodes=*/true);
1700 InitializeSeedQueue(&start_end_distances_per_node, &farthest_node_queue);
1701
1702 int inserted_seeds = 0;
1703 while (inserted_seeds++ < num_seeds) {
1704 if (InsertSeedNode(&start_end_distances_per_node, &farthest_node_queue,
1705 &is_vehicle_used) < 0) {
1706 break;
1707 }
1708 }
1709
1710 // NOTE: As we don't use the empty_vehicle_type_curator_ when inserting seed
1711 // nodes on routes, some previously empty vehicles may now be used, so we
1712 // update the curator accordingly to ensure it still only stores empty
1713 // vehicles.
1714 DCHECK(empty_vehicle_type_curator_ != nullptr);
1715 empty_vehicle_type_curator_->Update(
1716 [this](int vehicle) { return !VehicleIsEmpty(vehicle); });
1717}
1718
1719int GlobalCheapestInsertionFilteredHeuristic::InsertSeedNode(
1720 std::vector<std::vector<StartEndValue>>* start_end_distances_per_node,
1721 SeedQueue* sq, std::vector<bool>* is_vehicle_used) {
1722 auto& priority_queue = sq->priority_queue;
1723 while (!priority_queue.empty()) {
1724 if (StopSearch()) return -1;
1725 const Seed& seed = priority_queue.top();
1726 const int seed_node = seed.index;
1727 DCHECK(seed.is_node_index);
1728 const int seed_vehicle = seed.vehicle;
1729 priority_queue.pop();
1730
1731 std::vector<StartEndValue>& other_start_end_values =
1732 (*start_end_distances_per_node)[seed_node];
1733
1734 if (Contains(seed_node)) {
1735 // The node is already inserted, it is therefore no longer considered as
1736 // a potential seed.
1737 other_start_end_values.clear();
1738 continue;
1739 }
1740 if (!(*is_vehicle_used)[seed_vehicle]) {
1741 // Try to insert this seed_node on this vehicle's route.
1742 const int64_t start = model()->Start(seed_vehicle);
1743 const int64_t end = model()->End(seed_vehicle);
1744 DCHECK_EQ(Value(start), end);
1745 InsertBetween(seed_node, start, end, seed_vehicle);
1746 if (Evaluate(/*commit=*/true).has_value()) {
1747 (*is_vehicle_used)[seed_vehicle] = true;
1748 other_start_end_values.clear();
1749 SetVehicleIndex(seed_node, seed_vehicle);
1750 return seed_vehicle;
1751 }
1752 }
1753 // Either the vehicle is already used, or the Commit() wasn't successful.
1754 // In both cases, we insert the next StartEndValue from
1755 // start_end_distances_per_node[seed_node] in the priority queue.
1756 AddSeedNodeToQueue(seed_node, &other_start_end_values, sq);
1757 }
1758 // No seed node was inserted.
1759 return -1;
1760}
1761
1762bool GlobalCheapestInsertionFilteredHeuristic::InitializePairPositions(
1763 const absl::flat_hash_set<int>& pair_indices,
1764 AdjustablePriorityQueue<
1765 GlobalCheapestInsertionFilteredHeuristic::PairEntry>* priority_queue,
1766 std::vector<GlobalCheapestInsertionFilteredHeuristic::PairEntries>*
1767 pickup_to_entries,
1768 std::vector<GlobalCheapestInsertionFilteredHeuristic::PairEntries>*
1769 delivery_to_entries) {
1770 priority_queue->Clear();
1771 pickup_to_entries->clear();
1772 pickup_to_entries->resize(model()->Size());
1773 delivery_to_entries->clear();
1774 delivery_to_entries->resize(model()->Size());
1775 const std::vector<PickupDeliveryPair>& pickup_delivery_pairs =
1776 model()->GetPickupAndDeliveryPairs();
1777 for (int index : pair_indices) {
1778 const auto& [pickups, deliveries] = pickup_delivery_pairs[index];
1779 for (int64_t pickup : pickups) {
1780 if (Contains(pickup)) continue;
1781 for (int64_t delivery : deliveries) {
1782 if (Contains(delivery)) continue;
1783 if (StopSearchAndCleanup(priority_queue)) return false;
1784 // Add insertion entry making pair unperformed. When the pair is part
1785 // of a disjunction we do not try to make any of its pairs unperformed
1786 // as it requires having an entry with all pairs being unperformed.
1787 // TODO(user): Adapt the code to make pair disjunctions unperformed.
1788 if (gci_params_.add_unperformed_entries && pickups.size() == 1 &&
1789 deliveries.size() == 1 &&
1790 GetUnperformedValue(pickup) !=
1791 std::numeric_limits<int64_t>::max() &&
1792 GetUnperformedValue(delivery) !=
1793 std::numeric_limits<int64_t>::max()) {
1794 AddPairEntry(pickup, -1, delivery, -1, -1, priority_queue, nullptr,
1795 nullptr);
1796 }
1797 // Add all other insertion entries with pair performed.
1798 InitializeInsertionEntriesPerformingPair(
1799 pickup, delivery, priority_queue, pickup_to_entries,
1800 delivery_to_entries);
1801 }
1802 }
1803 }
1804 return true;
1805}
1806
1807void GlobalCheapestInsertionFilteredHeuristic::
1808 InitializeInsertionEntriesPerformingPair(
1809 int64_t pickup, int64_t delivery,
1810 AdjustablePriorityQueue<
1811 GlobalCheapestInsertionFilteredHeuristic::PairEntry>*
1812 priority_queue,
1813 std::vector<GlobalCheapestInsertionFilteredHeuristic::PairEntries>*
1814 pickup_to_entries,
1815 std::vector<GlobalCheapestInsertionFilteredHeuristic::PairEntries>*
1816 delivery_to_entries) {
1817 if (!gci_params_.use_neighbors_ratio_for_initialization) {
1818 struct PairInsertion {
1819 int64_t insert_pickup_after;
1820 int64_t insert_delivery_after;
1821 int vehicle;
1822 };
1823 std::vector<PairInsertion> pair_insertions;
1824 std::vector<NodeInsertion> pickup_insertions;
1825 std::vector<NodeInsertion> delivery_insertions;
1826 for (int vehicle = 0; vehicle < model()->vehicles(); ++vehicle) {
1827 if (!IsCheapestClassRepresentative(vehicle)) continue;
1828 const int64_t start = model()->Start(vehicle);
1829 pickup_insertions.clear();
1830 AppendInsertionPositionsAfter(pickup, start, Value(start), vehicle,
1831 /*ignore_cost=*/true, &pickup_insertions);
1832 for (const NodeInsertion& pickup_insertion : pickup_insertions) {
1833 DCHECK(!model()->IsEnd(pickup_insertion.insert_after));
1834 delivery_insertions.clear();
1836 delivery, pickup, Value(pickup_insertion.insert_after), vehicle,
1837 /*ignore_cost=*/true, &delivery_insertions);
1838 for (const NodeInsertion& delivery_insertion : delivery_insertions) {
1839 pair_insertions.push_back({pickup_insertion.insert_after,
1840 delivery_insertion.insert_after, vehicle});
1841 }
1842 }
1843 }
1844 for (const auto& [insert_pickup_after, insert_delivery_after, vehicle] :
1845 pair_insertions) {
1846 DCHECK_NE(insert_pickup_after, insert_delivery_after);
1847 AddPairEntry(pickup, insert_pickup_after, delivery, insert_delivery_after,
1848 vehicle, priority_queue, pickup_to_entries,
1849 delivery_to_entries);
1850 }
1851 return;
1852 }
1853
1854 // We're only considering the closest neighbors as insertion positions for
1855 // the pickup/delivery pair.
1856 for (int cost_class = 0; cost_class < model()->GetCostClassesCount();
1857 cost_class++) {
1858 absl::flat_hash_set<std::pair<int64_t, int64_t>>
1859 existing_insertion_positions;
1860 // Explore the neighborhood of the pickup.
1861 for (const int64_t pickup_insert_after :
1862 node_index_to_neighbors_by_cost_class_
1863 ->GetIncomingNeighborsOfNodeForCostClass(cost_class, pickup)) {
1864 if (!Contains(pickup_insert_after)) {
1865 continue;
1866 }
1867 const int vehicle = node_index_to_vehicle_[pickup_insert_after];
1868 if (vehicle < 0 ||
1869 model()->GetCostClassIndexOfVehicle(vehicle).value() != cost_class) {
1870 continue;
1871 }
1872
1873 if (!IsCheapestClassRepresentative(vehicle)) continue;
1874
1875 int64_t delivery_insert_after = pickup;
1876 while (!model()->IsEnd(delivery_insert_after)) {
1877 const std::pair<int64_t, int64_t> insertion_position = {
1878 pickup_insert_after, delivery_insert_after};
1879 DCHECK(!existing_insertion_positions.contains(insertion_position));
1880 existing_insertion_positions.insert(insertion_position);
1881
1882 AddPairEntry(pickup, pickup_insert_after, delivery,
1883 delivery_insert_after, vehicle, priority_queue,
1884 pickup_to_entries, delivery_to_entries);
1885 delivery_insert_after = (delivery_insert_after == pickup)
1886 ? Value(pickup_insert_after)
1887 : Value(delivery_insert_after);
1888 }
1889 }
1890
1891 // Explore the neighborhood of the delivery.
1892 for (const int64_t delivery_insert_after :
1893 node_index_to_neighbors_by_cost_class_
1894 ->GetIncomingNeighborsOfNodeForCostClass(cost_class, delivery)) {
1895 if (!Contains(delivery_insert_after)) {
1896 continue;
1897 }
1898 const int vehicle = node_index_to_vehicle_[delivery_insert_after];
1899 if (vehicle < 0 ||
1900 model()->GetCostClassIndexOfVehicle(vehicle).value() != cost_class) {
1901 continue;
1902 }
1903
1904 if (VehicleIsEmpty(vehicle)) {
1905 // Vehicle is empty.
1906 DCHECK_EQ(delivery_insert_after, model()->Start(vehicle));
1907 }
1908
1909 int64_t pickup_insert_after = model()->Start(vehicle);
1910 while (pickup_insert_after != delivery_insert_after) {
1911 if (!existing_insertion_positions.contains(
1912 std::make_pair(pickup_insert_after, delivery_insert_after))) {
1913 AddPairEntry(pickup, pickup_insert_after, delivery,
1914 delivery_insert_after, vehicle, priority_queue,
1915 pickup_to_entries, delivery_to_entries);
1916 }
1917 pickup_insert_after = Value(pickup_insert_after);
1918 }
1919 }
1920 }
1921}
1922
1923bool GlobalCheapestInsertionFilteredHeuristic::UpdateAfterPairInsertion(
1924 const absl::flat_hash_set<int>& pair_indices, int vehicle, int64_t pickup,
1925 int64_t pickup_position, int64_t delivery, int64_t delivery_position,
1926 AdjustablePriorityQueue<PairEntry>* priority_queue,
1927 std::vector<PairEntries>* pickup_to_entries,
1928 std::vector<PairEntries>* delivery_to_entries) {
1929 // Clearing any entries created after the pickup; these entries are the ones
1930 // where the delivery is to be inserted immediately after the pickup.
1931 const std::vector<PairEntry*> to_remove(
1932 delivery_to_entries->at(pickup).begin(),
1933 delivery_to_entries->at(pickup).end());
1934 for (PairEntry* pair_entry : to_remove) {
1935 DeletePairEntry(pair_entry, priority_queue, pickup_to_entries,
1936 delivery_to_entries);
1937 }
1938 DCHECK(pickup_to_entries->at(pickup).empty());
1939 DCHECK(pickup_to_entries->at(delivery).empty());
1940 DCHECK(delivery_to_entries->at(pickup).empty());
1941 DCHECK(delivery_to_entries->at(delivery).empty());
1942 // Update cost of existing entries after nodes which have new nexts
1943 // (pickup_position and delivery_position).
1944 if (!UpdateExistingPairEntriesOnChain(pickup_position, Value(pickup_position),
1945 priority_queue, pickup_to_entries,
1946 delivery_to_entries) ||
1947 !UpdateExistingPairEntriesOnChain(
1948 delivery_position, Value(delivery_position), priority_queue,
1949 pickup_to_entries, delivery_to_entries)) {
1950 return false;
1951 }
1952 // Add new entries after nodes which have been inserted (pickup and delivery).
1953 // We skip inserting deliveries after 'delivery' in the first call to make
1954 // sure each pair is only inserted after ('pickup', 'delivery') once.
1955 if (!AddPairEntriesAfter(pair_indices, vehicle, pickup,
1956 /*skip_entries_inserting_delivery_after=*/delivery,
1957 priority_queue, pickup_to_entries,
1958 delivery_to_entries) ||
1959 !AddPairEntriesAfter(pair_indices, vehicle, delivery,
1960 /*skip_entries_inserting_delivery_after=*/-1,
1961 priority_queue, pickup_to_entries,
1962 delivery_to_entries)) {
1963 return false;
1964 }
1965 SetVehicleIndex(pickup, vehicle);
1966 SetVehicleIndex(delivery, vehicle);
1967 return true;
1968}
1969
1970bool GlobalCheapestInsertionFilteredHeuristic::UpdateExistingPairEntriesOnChain(
1971 int64_t insert_after_start, int64_t insert_after_end,
1972 AdjustablePriorityQueue<
1973 GlobalCheapestInsertionFilteredHeuristic::PairEntry>* priority_queue,
1974 std::vector<GlobalCheapestInsertionFilteredHeuristic::PairEntries>*
1975 pickup_to_entries,
1976 std::vector<GlobalCheapestInsertionFilteredHeuristic::PairEntries>*
1977 delivery_to_entries) {
1978 int64_t insert_after = insert_after_start;
1979 while (insert_after != insert_after_end) {
1980 DCHECK(!model()->IsEnd(insert_after));
1981 // Remove entries at 'insert_after' with nodes which have already been
1982 // inserted and update remaining entries.
1983 std::vector<PairEntry*> to_remove;
1984 for (const PairEntries* pair_entries :
1985 {&pickup_to_entries->at(insert_after),
1986 &delivery_to_entries->at(insert_after)}) {
1987 if (StopSearchAndCleanup(priority_queue)) return false;
1988 for (PairEntry* const pair_entry : *pair_entries) {
1989 DCHECK(priority_queue->Contains(pair_entry));
1990 if (Contains(pair_entry->pickup_to_insert()) ||
1991 Contains(pair_entry->delivery_to_insert())) {
1992 to_remove.push_back(pair_entry);
1993 } else {
1994 DCHECK(pickup_to_entries->at(pair_entry->pickup_insert_after())
1995 .contains(pair_entry));
1996 DCHECK(delivery_to_entries->at(pair_entry->delivery_insert_after())
1997 .contains(pair_entry));
1998 UpdatePairEntry(pair_entry, priority_queue);
1999 }
2000 }
2001 }
2002 for (PairEntry* const pair_entry : to_remove) {
2003 DeletePairEntry(pair_entry, priority_queue, pickup_to_entries,
2004 delivery_to_entries);
2005 }
2006 insert_after = Value(insert_after);
2007 }
2008 return true;
2009}
2010
2011bool GlobalCheapestInsertionFilteredHeuristic::AddPairEntriesWithPickupAfter(
2012 const absl::flat_hash_set<int>& pair_indices, int vehicle,
2013 int64_t insert_after, int64_t skip_entries_inserting_delivery_after,
2014 AdjustablePriorityQueue<
2015 GlobalCheapestInsertionFilteredHeuristic::PairEntry>* priority_queue,
2016 std::vector<GlobalCheapestInsertionFilteredHeuristic::PairEntries>*
2017 pickup_to_entries,
2018 std::vector<GlobalCheapestInsertionFilteredHeuristic::PairEntries>*
2019 delivery_to_entries) {
2020 const int cost_class = model()->GetCostClassIndexOfVehicle(vehicle).value();
2021 const int64_t pickup_insert_before = Value(insert_after);
2022 const std::vector<PickupDeliveryPair>& pickup_delivery_pairs =
2023 model()->GetPickupAndDeliveryPairs();
2024 DCHECK(pickup_to_entries->at(insert_after).empty());
2025 for (const int64_t pickup :
2026 node_index_to_neighbors_by_cost_class_
2027 ->GetOutgoingNeighborsOfNodeForCostClass(cost_class, insert_after)) {
2028 if (StopSearchAndCleanup(priority_queue)) return false;
2029 if (Contains(pickup) || !model()->VehicleVar(pickup)->Contains(vehicle)) {
2030 continue;
2031 }
2032 if (const std::optional<RoutingModel::PickupDeliveryPosition> pickup_pos =
2033 model()->GetPickupPosition(pickup);
2034 pickup_pos.has_value()) {
2035 const int pair_index = pickup_pos->pd_pair_index;
2036 if (!pair_indices.contains(pair_index)) continue;
2037 for (const int64_t delivery :
2038 pickup_delivery_pairs[pair_index].delivery_alternatives) {
2039 if (Contains(delivery) ||
2040 !model()->VehicleVar(delivery)->Contains(vehicle)) {
2041 continue;
2042 }
2043 int64_t delivery_insert_after = pickup;
2044 while (!model()->IsEnd(delivery_insert_after)) {
2045 if (delivery_insert_after != skip_entries_inserting_delivery_after) {
2046 AddPairEntry(pickup, insert_after, delivery, delivery_insert_after,
2047 vehicle, priority_queue, pickup_to_entries,
2048 delivery_to_entries);
2049 }
2050 if (delivery_insert_after == pickup) {
2051 delivery_insert_after = pickup_insert_before;
2052 } else {
2053 delivery_insert_after = Value(delivery_insert_after);
2054 }
2055 }
2056 }
2057 }
2058 }
2059 return true;
2060}
2061
2062bool GlobalCheapestInsertionFilteredHeuristic::AddPairEntriesWithDeliveryAfter(
2063 const absl::flat_hash_set<int>& pair_indices, int vehicle,
2064 int64_t insert_after,
2065 AdjustablePriorityQueue<
2066 GlobalCheapestInsertionFilteredHeuristic::PairEntry>* priority_queue,
2067 std::vector<GlobalCheapestInsertionFilteredHeuristic::PairEntries>*
2068 pickup_to_entries,
2069 std::vector<GlobalCheapestInsertionFilteredHeuristic::PairEntries>*
2070 delivery_to_entries) {
2071 const int cost_class = model()->GetCostClassIndexOfVehicle(vehicle).value();
2072 const std::vector<PickupDeliveryPair>& pickup_delivery_pairs =
2073 model()->GetPickupAndDeliveryPairs();
2074 for (const int64_t delivery :
2075 node_index_to_neighbors_by_cost_class_
2076 ->GetOutgoingNeighborsOfNodeForCostClass(cost_class, insert_after)) {
2077 if (StopSearchAndCleanup(priority_queue)) return false;
2078 if (Contains(delivery) ||
2079 !model()->VehicleVar(delivery)->Contains(vehicle)) {
2080 continue;
2081 }
2082 if (const std::optional<RoutingModel::PickupDeliveryPosition> delivery_pos =
2083 model()->GetDeliveryPosition(delivery);
2084 delivery_pos.has_value()) {
2085 const int pair_index = delivery_pos->pd_pair_index;
2086 if (!pair_indices.contains(pair_index)) continue;
2087 for (const int64_t pickup :
2088 pickup_delivery_pairs[pair_index].pickup_alternatives) {
2089 if (Contains(pickup) ||
2090 !model()->VehicleVar(pickup)->Contains(vehicle)) {
2091 continue;
2092 }
2093 int64_t pickup_insert_after = model()->Start(vehicle);
2094 while (pickup_insert_after != insert_after) {
2095 AddPairEntry(pickup, pickup_insert_after, delivery, insert_after,
2096 vehicle, priority_queue, pickup_to_entries,
2097 delivery_to_entries);
2098 pickup_insert_after = Value(pickup_insert_after);
2099 }
2100 }
2101 }
2102 }
2103 return true;
2104}
2105
2106void GlobalCheapestInsertionFilteredHeuristic::DeletePairEntry(
2107 GlobalCheapestInsertionFilteredHeuristic::PairEntry* entry,
2108 AdjustablePriorityQueue<
2109 GlobalCheapestInsertionFilteredHeuristic::PairEntry>* priority_queue,
2110 std::vector<PairEntries>* pickup_to_entries,
2111 std::vector<PairEntries>* delivery_to_entries) {
2112 priority_queue->Remove(entry);
2113 if (entry->pickup_insert_after() != -1) {
2114 pickup_to_entries->at(entry->pickup_insert_after()).erase(entry);
2115 }
2116 if (entry->delivery_insert_after() != -1) {
2117 delivery_to_entries->at(entry->delivery_insert_after()).erase(entry);
2118 }
2119 pair_entry_allocator_.FreeEntry(entry);
2120}
2121
2122void GlobalCheapestInsertionFilteredHeuristic::AddPairEntry(
2123 int64_t pickup, int64_t pickup_insert_after, int64_t delivery,
2124 int64_t delivery_insert_after, int vehicle,
2125 AdjustablePriorityQueue<
2126 GlobalCheapestInsertionFilteredHeuristic::PairEntry>* priority_queue,
2127 std::vector<GlobalCheapestInsertionFilteredHeuristic::PairEntries>*
2128 pickup_entries,
2129 std::vector<GlobalCheapestInsertionFilteredHeuristic::PairEntries>*
2130 delivery_entries) const {
2131 const IntVar* pickup_vehicle_var = model()->VehicleVar(pickup);
2132 const IntVar* delivery_vehicle_var = model()->VehicleVar(delivery);
2133 if (!pickup_vehicle_var->Contains(vehicle) ||
2134 !delivery_vehicle_var->Contains(vehicle)) {
2135 if (vehicle == -1 || !VehicleIsEmpty(vehicle)) return;
2136 // We need to check there is not an equivalent empty vehicle the pair
2137 // could fit on.
2138 const auto vehicle_is_compatible = [pickup_vehicle_var,
2139 delivery_vehicle_var](int vehicle) {
2140 return pickup_vehicle_var->Contains(vehicle) &&
2141 delivery_vehicle_var->Contains(vehicle);
2142 };
2143 if (!empty_vehicle_type_curator_->HasCompatibleVehicleOfType(
2144 empty_vehicle_type_curator_->Type(vehicle),
2145 vehicle_is_compatible)) {
2146 return;
2147 }
2148 }
2149 const int num_allowed_vehicles =
2150 std::min(pickup_vehicle_var->Size(), delivery_vehicle_var->Size());
2151 if (pickup_insert_after == -1) {
2152 DCHECK_EQ(delivery_insert_after, -1);
2153 DCHECK_EQ(vehicle, -1);
2154 PairEntry* pair_entry = pair_entry_allocator_.NewEntry(
2155 pickup, -1, delivery, -1, -1, num_allowed_vehicles);
2156 pair_entry->set_value(
2157 absl::GetFlag(FLAGS_routing_shift_insertion_cost_by_penalty)
2158 ? 0
2159 : CapAdd(GetUnperformedValue(pickup),
2160 GetUnperformedValue(delivery)));
2161 priority_queue->Add(pair_entry);
2162 return;
2163 }
2164
2165 PairEntry* const pair_entry = pair_entry_allocator_.NewEntry(
2166 pickup, pickup_insert_after, delivery, delivery_insert_after, vehicle,
2167 num_allowed_vehicles);
2168 pair_entry->set_value(GetInsertionValueForPairAtPositions(
2169 pickup, pickup_insert_after, delivery, delivery_insert_after, vehicle));
2170
2171 // Add entry to priority_queue and pickup_/delivery_entries.
2172 DCHECK(!priority_queue->Contains(pair_entry));
2173 pickup_entries->at(pickup_insert_after).insert(pair_entry);
2174 delivery_entries->at(delivery_insert_after).insert(pair_entry);
2175 priority_queue->Add(pair_entry);
2176}
2177
2178void GlobalCheapestInsertionFilteredHeuristic::UpdatePairEntry(
2179 GlobalCheapestInsertionFilteredHeuristic::PairEntry* const pair_entry,
2180 AdjustablePriorityQueue<
2181 GlobalCheapestInsertionFilteredHeuristic::PairEntry>* priority_queue)
2182 const {
2183 pair_entry->set_value(GetInsertionValueForPairAtPositions(
2184 pair_entry->pickup_to_insert(), pair_entry->pickup_insert_after(),
2185 pair_entry->delivery_to_insert(), pair_entry->delivery_insert_after(),
2186 pair_entry->vehicle()));
2187
2188 // Update the priority_queue.
2189 DCHECK(priority_queue->Contains(pair_entry));
2190 priority_queue->NoteChangedPriority(pair_entry);
2191}
2192
2193int64_t
2194GlobalCheapestInsertionFilteredHeuristic::GetInsertionValueForPairAtPositions(
2195 int64_t pickup, int64_t pickup_insert_after, int64_t delivery,
2196 int64_t delivery_insert_after, int vehicle) const {
2197 DCHECK_GE(pickup_insert_after, 0);
2198 const int64_t pickup_insert_before = Value(pickup_insert_after);
2199 const int64_t pickup_value = GetInsertionCostForNodeAtPosition(
2200 pickup, pickup_insert_after, pickup_insert_before, vehicle);
2201
2202 DCHECK_GE(delivery_insert_after, 0);
2203 const int64_t delivery_insert_before = (delivery_insert_after == pickup)
2204 ? pickup_insert_before
2205 : Value(delivery_insert_after);
2206 const int64_t delivery_value = GetInsertionCostForNodeAtPosition(
2207 delivery, delivery_insert_after, delivery_insert_before, vehicle);
2208
2209 const int64_t penalty_shift =
2210 absl::GetFlag(FLAGS_routing_shift_insertion_cost_by_penalty)
2211 ? CapAdd(GetUnperformedValue(pickup), GetUnperformedValue(delivery))
2212 : 0;
2213 return CapSub(CapAdd(pickup_value, delivery_value), penalty_shift);
2214}
2215
2216bool GlobalCheapestInsertionFilteredHeuristic::InitializePositions(
2217 const SparseBitset<int>& nodes, const absl::flat_hash_set<int>& vehicles,
2218 NodeEntryQueue* queue) {
2219 queue->Clear();
2220
2221 const int num_vehicles =
2222 vehicles.empty() ? model()->vehicles() : vehicles.size();
2223 const bool all_vehicles = (num_vehicles == model()->vehicles());
2224
2225 for (int node : nodes.PositionsSetAtLeastOnce()) {
2226 if (Contains(node)) continue;
2227
2228 if (StopSearch()) return false;
2229 // Add insertion entry making node unperformed.
2230 if (gci_params_.add_unperformed_entries &&
2231 GetUnperformedValue(node) != std::numeric_limits<int64_t>::max()) {
2232 AddNodeEntry(node, node, -1, all_vehicles, queue);
2233 }
2234 // Add all insertion entries making node performed.
2235 InitializeInsertionEntriesPerformingNode(node, vehicles, queue);
2236 }
2237 return true;
2238}
2239
2240void GlobalCheapestInsertionFilteredHeuristic::
2241 InitializeInsertionEntriesPerformingNode(
2242 int64_t node, const absl::flat_hash_set<int>& vehicles,
2243 NodeEntryQueue* queue) {
2244 const int num_vehicles =
2245 vehicles.empty() ? model()->vehicles() : vehicles.size();
2246 const bool all_vehicles = (num_vehicles == model()->vehicles());
2247
2248 if (!gci_params_.use_neighbors_ratio_for_initialization) {
2249 auto vehicles_it = vehicles.begin();
2250 std::vector<NodeInsertion> insertions;
2251 // TODO(user): Ideally we'd want to iterate on vehicle var values:
2252 // std::unique_ptr<IntVarIterator>
2253 // it(model()->VehicleVar(node)->MakeDomainIterator(false));
2254 // for (const int64_t v : InitAndGetValues(it)) {
2255 // Requires taking vehicles into account.
2256 for (int v = 0; v < num_vehicles; v++) {
2257 const int vehicle = vehicles.empty() ? v : *vehicles_it++;
2258
2259 const int64_t start = model()->Start(vehicle);
2260 if (all_vehicles && !IsCheapestClassRepresentative(vehicle)) continue;
2261 insertions.clear();
2262 AppendInsertionPositionsAfter(node, start, Value(start), vehicle,
2263 /*ignore_cost=*/true, &insertions);
2264 for (const NodeInsertion& insertion : insertions) {
2265 DCHECK_EQ(insertion.vehicle, vehicle);
2266 AddNodeEntry(node, insertion.insert_after, vehicle, all_vehicles,
2267 queue);
2268 }
2269 }
2270 return;
2271 }
2272
2273 // We're only considering the closest incoming neighbors as insertion
2274 // positions for the node.
2275 const auto insert_on_vehicle_for_cost_class = [this, &vehicles, all_vehicles](
2276 int v, int cost_class) {
2277 return (model()->GetCostClassIndexOfVehicle(v).value() == cost_class) &&
2278 (all_vehicles || vehicles.contains(v));
2279 };
2280 for (int cost_class = 0; cost_class < model()->GetCostClassesCount();
2281 cost_class++) {
2282 for (const int64_t insert_after :
2283 node_index_to_neighbors_by_cost_class_
2284 ->GetIncomingNeighborsOfNodeForCostClass(cost_class, node)) {
2285 if (!Contains(insert_after)) {
2286 continue;
2287 }
2288 const int vehicle = node_index_to_vehicle_[insert_after];
2289 if (vehicle == -1 ||
2290 !insert_on_vehicle_for_cost_class(vehicle, cost_class)) {
2291 continue;
2292 }
2293 if (all_vehicles && !IsCheapestClassRepresentative(vehicle)) continue;
2294 AddNodeEntry(node, insert_after, vehicle, all_vehicles, queue);
2295 }
2296 }
2297}
2298
2299bool GlobalCheapestInsertionFilteredHeuristic::UpdateAfterNodeInsertion(
2300 const SparseBitset<int>& nodes, int vehicle, int64_t node,
2301 int64_t insert_after, bool all_vehicles, NodeEntryQueue* queue) {
2302 // Update cost of existing entries after "insert_after" which now have new
2303 // nexts.
2304 if (!UpdateExistingNodeEntriesOnChain(nodes, vehicle, insert_after,
2305 Value(insert_after), all_vehicles,
2306 queue)) {
2307 return false;
2308 }
2309 // Add new entries after "node" which has just been inserted.
2310 // TODO(user): Also add node entries *before* the newly inserted node
2311 // in the case where we have non-full neighborhoods. This will leverage the
2312 // incoming neighbors of the newly inserted node, in case they're not also
2313 // outgoing neighbors of 'insert_after'.
2314 // NOTE: UpdateExistingNodeEntriesOnChain() could return the set of node
2315 // indices that already have entries between insert_after and node, to avoid
2316 // adding entries again for them when looking at incoming neighbors of node.
2317 if (!AddNodeEntriesAfter(nodes, vehicle, node, all_vehicles, queue)) {
2318 return false;
2319 }
2320 SetVehicleIndex(node, vehicle);
2321 return true;
2322}
2323
2324bool GlobalCheapestInsertionFilteredHeuristic::UpdateExistingNodeEntriesOnChain(
2325 const SparseBitset<int>& nodes, int vehicle, int64_t insert_after_start,
2326 int64_t insert_after_end, bool all_vehicles, NodeEntryQueue* queue) {
2327 int64_t insert_after = insert_after_start;
2328 while (insert_after != insert_after_end) {
2329 DCHECK(!model()->IsEnd(insert_after));
2330 AddNodeEntriesAfter(nodes, vehicle, insert_after, all_vehicles, queue);
2331 insert_after = Value(insert_after);
2332 }
2333 return true;
2334}
2335
2336bool GlobalCheapestInsertionFilteredHeuristic::AddNodeEntriesAfter(
2337 const SparseBitset<int>& nodes, int vehicle, int64_t insert_after,
2338 bool all_vehicles, NodeEntryQueue* queue) {
2339 const int cost_class = model()->GetCostClassIndexOfVehicle(vehicle).value();
2340 // Remove existing entries at 'insert_after', needed either when updating
2341 // entries or if unperformed node insertions were present.
2342 queue->ClearInsertions(insert_after);
2343 const std::vector<int>& neighbors =
2344 node_index_to_neighbors_by_cost_class_
2345 ->GetOutgoingNeighborsOfNodeForCostClass(cost_class, insert_after);
2346 if (neighbors.size() < nodes.NumberOfSetCallsWithDifferentArguments()) {
2347 // Iterate on the neighbors.
2348 for (int node : neighbors) {
2349 if (StopSearch()) return false;
2350 if (!Contains(node) && nodes[node]) {
2351 AddNodeEntry(node, insert_after, vehicle, all_vehicles, queue);
2352 }
2353 }
2354 } else {
2355 // Iterate on the nodes to insert.
2356 for (int node : nodes.PositionsSetAtLeastOnce()) {
2357 if (StopSearch()) return false;
2358 if (!Contains(node) &&
2359 node_index_to_neighbors_by_cost_class_->IsNeighborhoodArcForCostClass(
2360 cost_class, insert_after, node)) {
2361 AddNodeEntry(node, insert_after, vehicle, all_vehicles, queue);
2362 }
2363 }
2364 }
2365 return true;
2366}
2367
2368void GlobalCheapestInsertionFilteredHeuristic::AddNodeEntry(
2369 int64_t node, int64_t insert_after, int vehicle, bool all_vehicles,
2370 NodeEntryQueue* queue) const {
2371 const int64_t node_penalty = GetUnperformedValue(node);
2372 const int64_t penalty_shift =
2373 absl::GetFlag(FLAGS_routing_shift_insertion_cost_by_penalty)
2374 ? node_penalty
2375 : 0;
2376 const IntVar* const vehicle_var = model()->VehicleVar(node);
2377 if (!vehicle_var->Contains(vehicle)) {
2378 if (vehicle == -1 || !VehicleIsEmpty(vehicle)) return;
2379 // We need to check there is not an equivalent empty vehicle the node
2380 // could fit on.
2381 const auto vehicle_is_compatible = [vehicle_var](int vehicle) {
2382 return vehicle_var->Contains(vehicle);
2383 };
2384 if (!empty_vehicle_type_curator_->HasCompatibleVehicleOfType(
2385 empty_vehicle_type_curator_->Type(vehicle),
2386 vehicle_is_compatible)) {
2387 return;
2388 }
2389 }
2390 const int num_allowed_vehicles = vehicle_var->Size();
2391 if (vehicle == -1) {
2392 DCHECK_EQ(node, insert_after);
2393 if (!all_vehicles) {
2394 // NOTE: In the case where we're not considering all routes
2395 // simultaneously, we don't add insertion entries making nodes
2396 // unperformed.
2397 return;
2398 }
2399 queue->PushInsertion(node, node, -1, num_allowed_vehicles,
2400 CapSub(node_penalty, penalty_shift));
2401 return;
2402 }
2403
2404 const int64_t insertion_cost = GetInsertionCostForNodeAtPosition(
2405 node, insert_after, Value(insert_after), vehicle);
2406 if (!all_vehicles && insertion_cost > node_penalty) {
2407 // NOTE: When all vehicles aren't considered for insertion, we don't
2408 // add entries making nodes unperformed, so we don't add insertions
2409 // which cost more than the node penalty either.
2410 return;
2411 }
2412
2413 queue->PushInsertion(node, insert_after, vehicle, num_allowed_vehicles,
2414 CapSub(insertion_cost, penalty_shift));
2415}
2416
2418 int pickup, int delivery, int vehicle, const std::vector<int>& path,
2419 const std::vector<bool>& path_node_is_pickup,
2420 const std::vector<bool>& path_node_is_delivery,
2421 InsertionSequenceContainer& insertions) {
2422 const int num_nodes = path.size();
2423 DCHECK_GE(num_nodes, 2);
2424 const int kNoPrevIncrease = -1;
2425 const int kNoNextDecrease = num_nodes;
2426 {
2427 prev_decrease_.resize(num_nodes - 1);
2428 prev_increase_.resize(num_nodes - 1);
2429 int prev_decrease = 0;
2430 int prev_increase = kNoPrevIncrease;
2431 for (int pos = 0; pos < num_nodes - 1; ++pos) {
2432 if (path_node_is_delivery[pos]) prev_decrease = pos;
2433 prev_decrease_[pos] = prev_decrease;
2434 if (path_node_is_pickup[pos]) prev_increase = pos;
2435 prev_increase_[pos] = prev_increase;
2436 }
2437 }
2438 {
2439 next_decrease_.resize(num_nodes - 1);
2440 next_increase_.resize(num_nodes - 1);
2441 int next_increase = num_nodes - 1;
2442 int next_decrease = kNoNextDecrease;
2443 for (int pos = num_nodes - 2; pos >= 0; --pos) {
2444 next_decrease_[pos] = next_decrease;
2445 if (path_node_is_delivery[pos]) next_decrease = pos;
2446 next_increase_[pos] = next_increase;
2447 if (path_node_is_pickup[pos]) next_increase = pos;
2448 }
2449 }
2450
2451 auto append = [pickup, delivery, vehicle, num_nodes, &path, &insertions](
2452 int pickup_pos, int delivery_pos) {
2453 if (pickup_pos < 0 || num_nodes - 1 <= pickup_pos) return;
2454 if (delivery_pos < 0 || num_nodes - 1 <= delivery_pos) return;
2455 const int delivery_pred =
2456 pickup_pos == delivery_pos ? pickup : path[delivery_pos];
2457 insertions.AddInsertionSequence(
2458 vehicle, {{.pred = path[pickup_pos], .node = pickup},
2459 {.pred = delivery_pred, .node = delivery}});
2460 };
2461
2462 // Find insertion positions for the input pair, pickup P and delivery D.
2463 for (int pos = 0; pos < num_nodes - 1; ++pos) {
2464 const bool is_after_decrease = prev_increase_[pos] < prev_decrease_[pos];
2465 const bool is_before_increase = next_increase_[pos] < next_decrease_[pos];
2466 if (is_after_decrease) {
2467 append(prev_increase_[pos], pos);
2468 if (is_before_increase) { // Upwards inflexion: vehicle is empty.
2469 append(pos, next_increase_[pos] - 1);
2470 append(pos, next_decrease_[pos] - 1);
2471 // Avoids duplicate insertions. If next_increase_[pos] - 1 == pos:
2472 // - append(pos, pos) is append(pos, next_increase_[pos] - 1)
2473 // - because is_after_decrease,
2474 // with pos' = prev_decrease_[pos],
2475 // next_increase_[pos'] == next_increase_[pos], so that
2476 // append(prev_decrease_[pos], pos) is
2477 // append(pos', next_increase_[pos'] - 1).
2478 if (next_increase_[pos] - 1 != pos) {
2479 append(pos, pos);
2480 if (prev_decrease_[pos] != pos) append(prev_decrease_[pos], pos);
2481 }
2482 }
2483 } else {
2484 append(pos, next_decrease_[pos] - 1);
2485 if (!is_before_increase && next_decrease_[pos] - 1 != pos) {
2486 // Downwards inflexion: vehicle is at its max.
2487 // Avoids duplicate insertions, when next_decrease_[pos] - 1 == pos:
2488 // - append(pos, pos) is append(pos, next_decrease_[pos] - 1)
2489 // - because is_before_increase, with pos' = prev_increase_[pos],
2490 // next_decrease_[pos'] == next_decrease_[pos], so that
2491 // append(prev_increase_[pos], pos) is
2492 // append(pos', next_decrease_[pos'] - 1).
2493 append(pos, pos);
2494 if (prev_increase_[pos] != pos) append(prev_increase_[pos], pos);
2495 }
2496 }
2497 }
2498}
2499
2500// LocalCheapestInsertionFilteredHeuristic
2501// TODO(user): Add support for penalty costs.
2504 RoutingModel* model, std::function<bool()> stop_search,
2505 std::function<int64_t(int64_t, int64_t, int64_t)> evaluator,
2506 RoutingSearchParameters::PairInsertionStrategy pair_insertion_strategy,
2507 std::vector<RoutingSearchParameters::InsertionSortingProperty>
2508 insertion_sorting_properties,
2509 LocalSearchFilterManager* filter_manager, bool use_first_solution_hint,
2510 BinCapacities* bin_capacities,
2511 std::function<bool(const std::vector<RoutingModel::VariableValuePair>&,
2512 std::vector<RoutingModel::VariableValuePair>*)>
2513 optimize_on_insertion)
2514 : CheapestInsertionFilteredHeuristic(model, std::move(stop_search),
2515 std::move(evaluator), nullptr,
2516 filter_manager),
2517 pair_insertion_strategy_(pair_insertion_strategy),
2518 insertion_sorting_properties_(std::move(insertion_sorting_properties)),
2519 use_first_solution_hint_(use_first_solution_hint),
2520 bin_capacities_(bin_capacities),
2521 optimize_on_insertion_(std::move(optimize_on_insertion)) {
2522 DCHECK(!insertion_sorting_properties_.empty());
2523}
2524
2526 // NOTE(user): Keeping the code in a separate function as opposed to
2527 // inlining here, to allow for future additions to this function.
2528 synchronize_insertion_optimizer_ = true;
2529 hint_next_values_.assign(model()->Size(), -1);
2530 hint_prev_values_.assign(model()->Size() + model()->vehicles(), -1);
2531 const Assignment* hint = model()->GetFirstSolutionHint();
2532 if (hint != nullptr && use_first_solution_hint_) {
2533 const Assignment::IntContainer& container = hint->IntVarContainer();
2534 for (int i = 0; i < model()->Nexts().size(); ++i) {
2535 const IntVarElement* element =
2536 container.ElementPtrOrNull(model()->NextVar(i));
2537 if (element != nullptr && element->Bound()) {
2538 hint_next_values_[i] = element->Value();
2539 hint_prev_values_[element->Value()] = i;
2540 }
2541 }
2542 }
2543 ComputeInsertionOrder();
2544}
2545
2546bool LocalCheapestInsertionFilteredHeuristic::OptimizeOnInsertion(
2547 std::vector<int> delta_indices) {
2548 if (optimize_on_insertion_ == nullptr) return false;
2549 std::vector<RoutingModel::VariableValuePair> in_state;
2550 if (synchronize_insertion_optimizer_) {
2551 for (int i = 0; i < model()->Nexts().size(); ++i) {
2552 if (Contains(i)) {
2553 in_state.push_back({i, Value(i)});
2554 }
2555 }
2556 synchronize_insertion_optimizer_ = false;
2557 } else {
2558 for (int index : delta_indices) {
2559 in_state.push_back({index, Value(index)});
2560 }
2561 }
2562 std::vector<RoutingModel::VariableValuePair> out_state;
2563 optimize_on_insertion_(in_state, &out_state);
2564 if (out_state.empty()) return false;
2565 for (const auto& [var, value] : out_state) {
2566 if (Contains(var)) {
2567 SetValue(var, value);
2568 }
2569 }
2570 return Evaluate(/*commit=*/true).has_value();
2571}
2572
2573namespace {
2574// Computes the cost from vehicle starts to pickups.
2575std::vector<std::vector<int64_t>> ComputeStartToPickupCosts(
2576 const RoutingModel& model, absl::Span<const int64_t> pickups,
2577 const Bitset64<int>& vehicle_set) {
2578 std::vector<std::vector<int64_t>> pickup_costs(model.Size());
2579 for (int64_t pickup : pickups) {
2580 std::vector<int64_t>& cost_from_start = pickup_costs[pickup];
2581 cost_from_start.resize(model.vehicles(), -1);
2582
2583 ProcessVehicleStartEndCosts(
2584 model, pickup,
2585 [&cost_from_start](int64_t cost, int v) { cost_from_start[v] = cost; },
2586 vehicle_set, /*ignore_start=*/false, /*ignore_end=*/true);
2587 }
2588 return pickup_costs;
2589}
2590
2591// Computes the cost from deliveries to vehicle ends.
2592std::vector<std::vector<int64_t>> ComputeDeliveryToEndCosts(
2593 const RoutingModel& model, absl::Span<const int64_t> deliveries,
2594 const Bitset64<int>& vehicle_set) {
2595 std::vector<std::vector<int64_t>> delivery_costs(model.Size());
2596 for (int64_t delivery : deliveries) {
2597 std::vector<int64_t>& cost_to_end = delivery_costs[delivery];
2598 cost_to_end.resize(model.vehicles(), -1);
2599
2600 ProcessVehicleStartEndCosts(
2601 model, delivery,
2602 [&cost_to_end](int64_t cost, int v) { cost_to_end[v] = cost; },
2603 vehicle_set, /*ignore_start=*/true, /*ignore_end=*/false);
2604 }
2605 return delivery_costs;
2606}
2607
2608// Returns the opposite of the maximum cost between all pickup/delivery nodes of
2609// the given pair from their "closest" vehicle.
2610// For a given pickup/delivery, the cost from the closest vehicle is defined as
2611// min_{v in vehicles}(ArcCost(start[v]->pickup) + ArcCost(delivery->end[v])).
2612int64_t GetNegMaxDistanceFromVehicles(const RoutingModel& model,
2613 int pair_index) {
2614 const auto& [pickups, deliveries] =
2615 model.GetPickupAndDeliveryPairs()[pair_index];
2616
2617 Bitset64<int> vehicle_set(model.vehicles());
2618 for (int v = 0; v < model.vehicles(); ++v) vehicle_set.Set(v);
2619
2620 // Precompute the cost from vehicle starts to every pickup in the pair.
2621 const std::vector<std::vector<int64_t>> pickup_costs =
2622 ComputeStartToPickupCosts(model, pickups, vehicle_set);
2623
2624 // Precompute the cost from every delivery in the pair to vehicle ends.
2625 const std::vector<std::vector<int64_t>> delivery_costs =
2626 ComputeDeliveryToEndCosts(model, deliveries, vehicle_set);
2627
2628 int64_t max_pair_distance = 0;
2629 for (int64_t pickup : pickups) {
2630 const std::vector<int64_t>& cost_from_start = pickup_costs[pickup];
2631 for (int64_t delivery : deliveries) {
2632 const std::vector<int64_t>& cost_to_end = delivery_costs[delivery];
2633 int64_t closest_vehicle_distance = std::numeric_limits<int64_t>::max();
2634 for (int v = 0; v < model.vehicles(); v++) {
2635 if (cost_from_start[v] < 0 || cost_to_end[v] < 0) {
2636 // Vehicle not in the pickup and/or delivery's vehicle var domain.
2637 continue;
2638 }
2639 closest_vehicle_distance =
2640 std::min(closest_vehicle_distance,
2641 CapAdd(cost_from_start[v], cost_to_end[v]));
2642 }
2643 max_pair_distance = std::max(max_pair_distance, closest_vehicle_distance);
2644 }
2645 }
2646 return CapOpp(max_pair_distance);
2647}
2648
2649// Returns the average distance between all pickup/delivery nodes of the given
2650// pair from all vehicles where they can be serverd. Returns an empty optional
2651// if no vehicle can serve the pair. For a given pickup/delivery, the distance
2652// from a vehicle v is defined as ArcCost(start[v]->pickup) +
2653// ArcCost(delivery->end[v]).
2654std::optional<int64_t> GetAvgPickupDeliveryPairDistanceFromVehicles(
2655 const RoutingModel& model, int pair_index,
2656 const Bitset64<int>& vehicle_set) {
2657 const auto& [pickups, deliveries] =
2658 model.GetPickupAndDeliveryPairs()[pair_index];
2659
2660 // Precompute the cost from vehicle starts to every pickup in the pair.
2661 const std::vector<std::vector<int64_t>> pickup_costs =
2662 ComputeStartToPickupCosts(model, pickups, vehicle_set);
2663
2664 // Precompute the cost from every delivery in the pair to vehicle ends.
2665 const std::vector<std::vector<int64_t>> delivery_costs =
2666 ComputeDeliveryToEndCosts(model, deliveries, vehicle_set);
2667
2668 int64_t avg_pair_distance_sum = 0;
2669 int valid_pairs = 0;
2670 for (int64_t pickup : pickups) {
2671 const std::vector<int64_t>& cost_from_start = pickup_costs[pickup];
2672 for (int64_t delivery : deliveries) {
2673 const std::vector<int64_t>& cost_to_end = delivery_costs[delivery];
2674 int64_t distance_from_vehicle_sum = 0;
2675 int valid_vehicles = 0;
2676 for (int v = 0; v < model.vehicles(); v++) {
2677 if (cost_from_start[v] < 0 || cost_to_end[v] < 0) {
2678 // Vehicle not in the pickup and/or delivery's vehicle var domain.
2679 continue;
2680 }
2681 distance_from_vehicle_sum =
2682 CapAdd(distance_from_vehicle_sum,
2683 CapAdd(cost_from_start[v], cost_to_end[v]));
2684 ++valid_vehicles;
2685 }
2686 if (valid_vehicles > 0) {
2687 avg_pair_distance_sum = CapAdd(
2688 distance_from_vehicle_sum / valid_vehicles, avg_pair_distance_sum);
2689 ++valid_pairs;
2690 }
2691 }
2692 }
2693 return valid_pairs > 0
2694 ? std::make_optional<int64_t>(avg_pair_distance_sum / valid_pairs)
2695 : std::nullopt;
2696}
2697
2698// Returns the maximum vehicle capacity for the given dimensions. Vehicles with
2699// infinite capacity are ignored.
2700int64_t GetMaxFiniteDimensionCapacity(
2701 const RoutingModel& model,
2702 const std::vector<RoutingDimension*>& dimensions) {
2703 int64_t max_capacity = 0;
2704 for (const RoutingDimension* dimension : dimensions) {
2705 for (int vehicle = 0; vehicle < model.vehicles(); ++vehicle) {
2706 const int64_t capacity = dimension->vehicle_capacities()[vehicle];
2707 if (capacity == kint64max) continue;
2708 max_capacity = std::max(max_capacity, capacity);
2709 }
2710 }
2711 return max_capacity;
2712}
2713
2714// Returns the average dimension usage of the given node scaled according to the
2715// given max capacity.
2716int64_t GetAvgNodeUnaryDimensionUsage(
2717 const RoutingModel& model, const std::vector<RoutingDimension*>& dimensions,
2718 int64_t max_vehicle_capacity, int64_t node) {
2719 if (dimensions.empty() || max_vehicle_capacity == 0) {
2720 return 0;
2721 }
2722
2723 double dimension_usage_sum = 0;
2724 for (const RoutingDimension* dimension : dimensions) {
2725 // TODO(user): extend to non unary dimensions.
2726 DCHECK(dimension->IsUnary());
2727 double dimension_usage_per_vehicle_sum = 0;
2728 int valid_vehicles = 0;
2729
2730 // TODO(user): limit computations to vehicles allowed for this node.
2731 for (int vehicle = 0; vehicle < model.vehicles(); ++vehicle) {
2732 const int64_t capacity = dimension->vehicle_capacities()[vehicle];
2733 if (capacity == 0) continue;
2734 DCHECK_GE(capacity, 0);
2735 valid_vehicles++;
2736 // If the capacity is infinite, bypass the "noise" added to the dimension
2737 // usage.
2738 if (capacity == kint64max) continue;
2739 const RoutingModel::TransitCallback1& transit_evaluator =
2740 dimension->GetUnaryTransitEvaluator(vehicle);
2741 dimension_usage_per_vehicle_sum +=
2742 1.0 * std::abs(transit_evaluator(node)) / capacity;
2743 }
2744
2745 if (valid_vehicles > 0) {
2746 // Cap the dimension usage to 1.0 in case some node demand is greater than
2747 // the vehicle capacity.
2748 dimension_usage_sum +=
2749 std::min(1.0, dimension_usage_per_vehicle_sum / valid_vehicles);
2750 }
2751 }
2752
2753 // We multiply the computed dimension_usage_sum by the max_vehicle_capacity to
2754 // have a larger scale for rounding to int64_t.
2755 return MathUtil::SafeRound<int64_t>(max_vehicle_capacity *
2756 dimension_usage_sum);
2757}
2758
2759// Returns the average dimension usage of a pickup and delivery pair scaled
2760// according to the given max capacity.
2761int64_t GetAvgPickupDeliveryPairUnaryDimensionUsage(
2762 const RoutingModel& model, const std::vector<RoutingDimension*>& dimensions,
2763 int64_t max_vehicle_capacity, int pair_index) {
2764 if (dimensions.empty()) {
2765 return 0;
2766 }
2767
2768 const auto& [pickups, deliveries] =
2769 model.GetPickupAndDeliveryPairs()[pair_index];
2770 if (pickups.empty() || deliveries.empty()) {
2771 return 0;
2772 }
2773
2774 double dimension_usage_sum = 0;
2775 for (const RoutingDimension* dimension : dimensions) {
2776 double dimension_usage_per_vehicle_sum = 0;
2777 int valid_vehicles = 0;
2778 // TODO(user): limit computations to nodes allowed for this vehicle.
2779 for (int vehicle = 0; vehicle < model.vehicles(); ++vehicle) {
2780 const int64_t capacity = dimension->vehicle_capacities()[vehicle];
2781 if (capacity == 0) continue;
2782 valid_vehicles++;
2783 // If the capacity is infinite, bypass the "noise" added to the dimension
2784 // usage.
2785 if (capacity == kint64max) continue;
2786 const RoutingModel::TransitCallback1& transit_evaluator =
2787 dimension->GetUnaryTransitEvaluator(vehicle);
2788
2789 double pickup_sum = 0;
2790 for (int64_t pickup : pickups) {
2791 pickup_sum += 1.0 * std::abs(transit_evaluator(pickup)) / capacity;
2792 }
2793 const double avg_pickup_usage = pickup_sum / pickups.size();
2794 double delivery_sum = 0;
2795 for (int64_t delivery : deliveries) {
2796 delivery_sum += 1.0 * std::abs(transit_evaluator(delivery)) / capacity;
2797 }
2798 const double avg_delivery_usage = delivery_sum / deliveries.size();
2799 dimension_usage_per_vehicle_sum +=
2800 std::max(avg_pickup_usage, avg_delivery_usage);
2801 }
2802
2803 if (valid_vehicles > 0) {
2804 // Cap the dimension usage to 1.0 in case some node demand is greater than
2805 // the vehicle capacity.
2806 dimension_usage_sum +=
2807 std::min(1.0, dimension_usage_per_vehicle_sum / valid_vehicles);
2808 }
2809 }
2810
2811 // We multiply the computed dimension_usage_sum by the max_vehicle_capacity to
2812 // have a larger scale for rounding to int64_t.
2813 return MathUtil::SafeRound<int64_t>(max_vehicle_capacity *
2814 dimension_usage_sum);
2815}
2816
2817} // namespace
2818
2819void LocalCheapestInsertionFilteredHeuristic::ComputeInsertionOrder() {
2820 if (!insertion_order_.empty()) return;
2821
2822 // We consider pairs and single nodes simultaneously, to make sure the most
2823 // critical ones (fewer allowed vehicles and high penalties) get inserted
2824 // first.
2825 // TODO(user): Explore other metrics to evaluate criticality, more directly
2826 // mixing penalties and allowed vehicles (such as a ratio between the two).
2827
2828 const RoutingModel& model = *this->model();
2829 insertion_order_.reserve(model.Size() +
2830 model.GetPickupAndDeliveryPairs().size());
2831
2832 auto get_insertion_properties = [this](int64_t penalty,
2833 int64_t num_allowed_vehicles,
2834 int64_t avg_distance_to_vehicle,
2835 int64_t neg_min_distance_to_vehicles,
2836 int hint_weight,
2837 int reversed_hint_weight,
2838 int64_t avg_dimension_usage) {
2839 DCHECK_NE(0, num_allowed_vehicles);
2840 absl::InlinedVector<int64_t, 8> properties;
2841 properties.reserve(insertion_sorting_properties_.size());
2842
2843 // Always consider hints first. We favor nodes with hints over nodes with
2844 // reversed hints.
2845 // TODO(user): Figure out a way to insert hinted nodes in a logical
2846 // order. We could try toposorting hinted nodes.
2847 properties.push_back(-hint_weight);
2848 properties.push_back(-reversed_hint_weight);
2849
2850 bool neg_min_distance_to_vehicles_appended = false;
2851 for (const int property : insertion_sorting_properties_) {
2852 switch (property) {
2853 case RoutingSearchParameters::SORTING_PROPERTY_ALLOWED_VEHICLES:
2854 properties.push_back(num_allowed_vehicles);
2855 break;
2856 case RoutingSearchParameters::SORTING_PROPERTY_PENALTY:
2857 properties.push_back(CapOpp(penalty));
2858 break;
2859 case RoutingSearchParameters::
2860 SORTING_PROPERTY_PENALTY_OVER_ALLOWED_VEHICLES_RATIO:
2861 properties.push_back(CapOpp(penalty / num_allowed_vehicles));
2862 break;
2863 case RoutingSearchParameters::
2864 SORTING_PROPERTY_HIGHEST_AVG_ARC_COST_TO_VEHICLE_START_ENDS:
2865 properties.push_back(CapOpp(avg_distance_to_vehicle));
2866 break;
2867 case RoutingSearchParameters::
2868 SORTING_PROPERTY_LOWEST_AVG_ARC_COST_TO_VEHICLE_START_ENDS:
2869 properties.push_back(avg_distance_to_vehicle);
2870 break;
2871 case RoutingSearchParameters::
2872 SORTING_PROPERTY_LOWEST_MIN_ARC_COST_TO_VEHICLE_START_ENDS:
2873 properties.push_back(neg_min_distance_to_vehicles);
2874 neg_min_distance_to_vehicles_appended = true;
2875 break;
2876 case RoutingSearchParameters::SORTING_PROPERTY_HIGHEST_DIMENSION_USAGE:
2877 properties.push_back(CapOpp(avg_dimension_usage));
2878 break;
2879 default:
2880 LOG(DFATAL)
2881 << "Unknown RoutingSearchParameter::InsertionSortingProperty "
2882 "used!";
2883 break;
2884 }
2885 }
2886
2887 // Historically the negative max distance to vehicles has always been
2888 // considered to be the last property in the hierarchy defining how nodes
2889 // are sorted for the LCI heuristic, so we add it here iff it wasn't added
2890 // before
2891 if (!neg_min_distance_to_vehicles_appended) {
2892 properties.push_back(neg_min_distance_to_vehicles);
2893 }
2894
2895 return properties;
2896 };
2897
2898 // Identify whether the selected properties require a more expensive
2899 // preprocessing.
2900 bool compute_avg_pickup_delivery_pair_distance_from_vehicles = false;
2901 bool compute_avg_dimension_usage = false;
2902 for (const RoutingSearchParameters::InsertionSortingProperty property :
2903 insertion_sorting_properties_) {
2904 if (property ==
2905 RoutingSearchParameters::
2906 SORTING_PROPERTY_HIGHEST_AVG_ARC_COST_TO_VEHICLE_START_ENDS ||
2907 property ==
2908 RoutingSearchParameters::
2909 SORTING_PROPERTY_LOWEST_AVG_ARC_COST_TO_VEHICLE_START_ENDS) {
2910 compute_avg_pickup_delivery_pair_distance_from_vehicles = true;
2911 }
2912 if (property ==
2913 RoutingSearchParameters::SORTING_PROPERTY_HIGHEST_DIMENSION_USAGE) {
2914 compute_avg_dimension_usage = true;
2915 }
2916 }
2917
2918 Bitset64<int> vehicle_set(model.vehicles());
2919 for (int v = 0; v < model.vehicles(); ++v) vehicle_set.Set(v);
2920
2921 const std::vector<RoutingDimension*> unary_dimensions =
2922 compute_avg_dimension_usage ? model.GetUnaryDimensions()
2923 : std::vector<RoutingDimension*>();
2924
2925 const int64_t max_dimension_capacity =
2926 compute_avg_dimension_usage
2927 ? GetMaxFiniteDimensionCapacity(model, unary_dimensions)
2928 : 0;
2929
2930 // Iterating on pickup and delivery pairs.
2931 const std::vector<PickupDeliveryPair>& pairs =
2932 model.GetPickupAndDeliveryPairs();
2933
2934 for (int pair_index = 0; pair_index < pairs.size(); ++pair_index) {
2935 const auto& [pickups, deliveries] = pairs[pair_index];
2936 int64_t num_allowed_vehicles = std::numeric_limits<int64_t>::max();
2937 int64_t pickup_penalty = 0;
2938 int hint_weight = 0;
2939 int reversed_hint_weight = 0;
2940 for (int64_t pickup : pickups) {
2941 num_allowed_vehicles =
2942 std::min(num_allowed_vehicles,
2943 static_cast<int64_t>(model.VehicleVar(pickup)->Size()));
2944 pickup_penalty =
2945 std::max(pickup_penalty, model.UnperformedPenalty(pickup));
2946 hint_weight += HasHintedNext(pickup);
2947 reversed_hint_weight += HasHintedPrev(pickup);
2948 }
2949 int64_t delivery_penalty = 0;
2950 for (int64_t delivery : deliveries) {
2951 num_allowed_vehicles =
2952 std::min(num_allowed_vehicles,
2953 static_cast<int64_t>(model.VehicleVar(delivery)->Size()));
2954 delivery_penalty =
2955 std::max(delivery_penalty, model.UnperformedPenalty(delivery));
2956 hint_weight += HasHintedNext(delivery);
2957 reversed_hint_weight += HasHintedPrev(delivery);
2958 }
2959
2960 const std::optional<int64_t> maybe_avg_pair_to_vehicle_cost =
2961 compute_avg_pickup_delivery_pair_distance_from_vehicles
2962 ? GetAvgPickupDeliveryPairDistanceFromVehicles(model, pair_index,
2963 vehicle_set)
2964 : std::make_optional<int64_t>(0);
2965
2966 if (!maybe_avg_pair_to_vehicle_cost.has_value()) {
2967 // There is no vehicle that can serve the pickup/delivery nodes in the
2968 // current pair index.
2969 continue;
2970 }
2971
2972 const int64_t avg_pair_dimension_usage =
2973 compute_avg_dimension_usage
2974 ? GetAvgPickupDeliveryPairUnaryDimensionUsage(
2975 model, unary_dimensions, max_dimension_capacity, pair_index)
2976 : 0;
2977
2978 absl::InlinedVector<int64_t, 8> properties = get_insertion_properties(
2979 CapAdd(pickup_penalty, delivery_penalty), num_allowed_vehicles,
2980 maybe_avg_pair_to_vehicle_cost.value(),
2981 GetNegMaxDistanceFromVehicles(model, pair_index), hint_weight,
2982 reversed_hint_weight, avg_pair_dimension_usage);
2983
2984 insertion_order_.push_back({.properties = std::move(properties),
2985 .vehicle = 0,
2986 .is_node_index = false,
2987 .index = pair_index});
2988 }
2989
2990 for (int node = 0; node < model.Size(); ++node) {
2991 if (model.IsStart(node) || model.IsEnd(node)) continue;
2992
2993 int64_t min_distance = std::numeric_limits<int64_t>::max();
2994 int64_t sum_distance = 0;
2995 ProcessVehicleStartEndCosts(
2996 model, node,
2997 [&min_distance, &sum_distance](int64_t dist, int) {
2998 min_distance = std::min(min_distance, dist);
2999 sum_distance = CapAdd(dist, sum_distance);
3000 },
3001 vehicle_set);
3002 DCHECK_GT(vehicle_set.size(), 0);
3003
3004 const int64_t avg_dimension_usage =
3005 compute_avg_dimension_usage
3006 ? GetAvgNodeUnaryDimensionUsage(model, unary_dimensions,
3007 max_dimension_capacity, node)
3008 : 0;
3009
3010 absl::InlinedVector<int64_t, 8> properties = get_insertion_properties(
3011 model.UnperformedPenalty(node), model.VehicleVar(node)->Size(),
3012 sum_distance / vehicle_set.size(), CapOpp(min_distance),
3013 HasHintedNext(node), HasHintedPrev(node), avg_dimension_usage);
3014 insertion_order_.push_back({.properties = std::move(properties),
3015 .vehicle = 0,
3016 .is_node_index = true,
3017 .index = node});
3018 }
3019
3020 absl::c_sort(insertion_order_, std::greater<Seed>());
3021 absl::c_reverse(insertion_order_);
3022}
3023
3024bool LocalCheapestInsertionFilteredHeuristic::InsertPair(
3025 int64_t pickup, int64_t insert_pickup_after, int64_t delivery,
3026 int64_t insert_delivery_after, int vehicle) {
3027 const int64_t insert_pickup_before = Value(insert_pickup_after);
3028 InsertBetween(pickup, insert_pickup_after, insert_pickup_before, vehicle);
3029 DCHECK_NE(insert_delivery_after, insert_pickup_after);
3030 const int64_t insert_delivery_before = (insert_delivery_after == pickup)
3031 ? insert_pickup_before
3032 : Value(insert_delivery_after);
3033 InsertBetween(delivery, insert_delivery_after, insert_delivery_before,
3034 vehicle);
3035 // Capturing the state of the delta before it gets wiped by Evaluate.
3036 std::vector<int> indices = delta_indices();
3037 if (Evaluate(/*commit=*/true, /*ignore_upper_bound=*/true).has_value()) {
3038 OptimizeOnInsertion(std::move(indices));
3039 return true;
3040 }
3041 return false;
3042}
3043
3044void LocalCheapestInsertionFilteredHeuristic::InsertBestPickupThenDelivery(
3045 const PickupDeliveryPair& index_pair) {
3046 for (int pickup : index_pair.pickup_alternatives) {
3047 std::vector<NodeInsertion> pickup_insertions =
3048 ComputeEvaluatorSortedPositions(pickup);
3049 for (int delivery : index_pair.delivery_alternatives) {
3050 if (StopSearch()) return;
3051 for (const NodeInsertion& pickup_insertion : pickup_insertions) {
3052 const int vehicle = pickup_insertion.vehicle;
3053 if (!model()->VehicleVar(delivery)->Contains(vehicle)) continue;
3054 if (MustUpdateBinCapacities() &&
3055 !bin_capacities_->CheckAdditionsFeasibility({pickup, delivery},
3056 vehicle)) {
3057 continue;
3058 }
3059 for (const NodeInsertion& delivery_insertion :
3060 ComputeEvaluatorSortedPositionsOnRouteAfter(
3061 delivery, pickup, Value(pickup_insertion.insert_after),
3062 vehicle)) {
3063 if (InsertPair(pickup, pickup_insertion.insert_after, delivery,
3064 delivery_insertion.insert_after, vehicle)) {
3065 if (MustUpdateBinCapacities()) {
3066 bin_capacities_->AddItemToBin(pickup, vehicle);
3067 bin_capacities_->AddItemToBin(delivery, vehicle);
3068 }
3069 return;
3070 }
3071 }
3072 if (StopSearch()) return;
3073 }
3074 }
3075 }
3076}
3077
3078void LocalCheapestInsertionFilteredHeuristic::InsertBestPair(
3079 const PickupDeliveryPair& pair) {
3080 for (int pickup : pair.pickup_alternatives) {
3081 for (int delivery : pair.delivery_alternatives) {
3082 if (StopSearch()) return;
3083 std::vector<PickupDeliveryInsertion> sorted_pair_positions =
3084 ComputeEvaluatorSortedPairPositions(pickup, delivery);
3085 if (sorted_pair_positions.empty()) continue;
3086 for (const auto [insert_pickup_after, insert_delivery_after,
3087 unused_hint_weight, unused_value, vehicle] :
3088 sorted_pair_positions) {
3089 if (InsertPair(pickup, insert_pickup_after, delivery,
3090 insert_delivery_after, vehicle)) {
3091 if (MustUpdateBinCapacities()) {
3092 bin_capacities_->AddItemToBin(pickup, vehicle);
3093 bin_capacities_->AddItemToBin(delivery, vehicle);
3094 }
3095 return;
3096 }
3097 if (StopSearch()) return;
3098 }
3099 }
3100 }
3101}
3102
3103void LocalCheapestInsertionFilteredHeuristic::InsertBestPairMultitour(
3104 const PickupDeliveryPair& pair) {
3105 using InsertionSequence = InsertionSequenceContainer::InsertionSequence;
3106 using Insertion = InsertionSequenceContainer::Insertion;
3107
3108 std::vector<int> path;
3109 std::vector<bool> path_node_is_pickup;
3110 std::vector<bool> path_node_is_delivery;
3111 // Fills path with all nodes visited by vehicle, including start/end.
3112 auto fill_path = [&path, &path_node_is_pickup, &path_node_is_delivery,
3113 this](int vehicle) {
3114 path.clear();
3115 path_node_is_pickup.clear();
3116 path_node_is_delivery.clear();
3117 const int start = model()->Start(vehicle);
3118 const int end = model()->End(vehicle);
3119 for (int node = start; node != end; node = Value(node)) {
3120 path.push_back(node);
3121 path_node_is_pickup.push_back(model()->IsPickup(node));
3122 path_node_is_delivery.push_back(model()->IsDelivery(node));
3123 }
3124 path.push_back(end);
3125 };
3126
3127 // Fills value field of all insertions, kint64max if unevaluable.
3128 auto price_insertion_sequences_evaluator =
3129 [this](BinCapacities* bin_capacities) {
3130 for (InsertionSequence sequence : insertion_container_) {
3131 int64_t sequence_cost = 0;
3132 int previous_node = -1;
3133 int previous_succ = -1;
3134 int hint_weight = 0;
3135 for (const Insertion& insertion : sequence) {
3136 const int succ = previous_node == insertion.pred
3137 ? previous_succ
3138 : Value(insertion.pred);
3139 hint_weight += IsHint(insertion.pred, insertion.node);
3140 hint_weight += IsHint(insertion.node, succ);
3141 const int64_t cost = GetInsertionCostForNodeAtPosition(
3142 insertion.node, insertion.pred, succ, sequence.Vehicle());
3143 CapAddTo(cost, &sequence_cost);
3144 previous_node = insertion.node;
3145 previous_succ = succ;
3146 }
3147 sequence.Cost() = sequence_cost;
3148 sequence.SetHintWeight(hint_weight);
3149 }
3150 if (bin_capacities == nullptr) return;
3151 for (InsertionSequence sequence : insertion_container_) {
3152 const int64_t old_cost = bin_capacities->TotalCost();
3153 for (const Insertion& insertion : sequence) {
3154 bin_capacities->AddItemToBin(insertion.node, sequence.Vehicle());
3155 }
3156 const int64_t new_cost = bin_capacities->TotalCost();
3157 const int64_t delta_cost = CapSub(new_cost, old_cost);
3158 CapAddTo(delta_cost, &sequence.Cost());
3159 for (const Insertion& insertion : sequence) {
3160 bin_capacities->RemoveItemFromBin(insertion.node,
3161 sequence.Vehicle());
3162 }
3163 }
3164 };
3165
3166 auto price_insertion_sequences_no_evaluator = [this]() {
3167 for (InsertionSequence sequence : insertion_container_) {
3168 int previous_node = -1;
3169 int previous_succ = -1;
3170 int hint_weight = 0;
3171 for (const Insertion& insertion : sequence) {
3172 const int succ = previous_node == insertion.pred
3173 ? previous_succ
3174 : Value(insertion.pred);
3175 hint_weight += IsHint(insertion.pred, insertion.node);
3176 hint_weight += IsHint(insertion.node, succ);
3177 InsertBetween(insertion.node, insertion.pred, succ, sequence.Vehicle());
3178 previous_node = insertion.node;
3179 previous_succ = succ;
3180 }
3181 sequence.Cost() =
3182 Evaluate(/*commit=*/false, /*ignore_upper_bound=*/hint_weight > 0)
3183 .value_or(kint64max);
3184 sequence.SetHintWeight(hint_weight);
3185 }
3186 };
3187
3188 for (int pickup : pair.pickup_alternatives) {
3189 const IntVar* pickup_vehicle_var = model()->VehicleVar(pickup);
3190 if (StopSearch()) return;
3191 for (int delivery : pair.delivery_alternatives) {
3192 const IntVar* delivery_vehicle_var = model()->VehicleVar(delivery);
3193 insertion_container_.Clear();
3194 std::unique_ptr<IntVarIterator> pickup_vehicles(
3195 pickup_vehicle_var->MakeDomainIterator(false));
3196 for (const int vehicle : InitAndGetValues(pickup_vehicles.get())) {
3197 if (vehicle == -1) continue;
3198 if (!delivery_vehicle_var->Contains(vehicle)) continue;
3199 if (MustUpdateBinCapacities() &&
3200 !bin_capacities_->CheckAdditionsFeasibility({pickup, delivery},
3201 vehicle)) {
3202 continue;
3203 }
3204 fill_path(vehicle);
3205 insertion_generator_.AppendPickupDeliveryMultitourInsertions(
3206 pickup, delivery, vehicle, path, path_node_is_pickup,
3207 path_node_is_delivery, insertion_container_);
3208 }
3209 if (StopSearch()) return;
3210 if (evaluator_ == nullptr) {
3211 price_insertion_sequences_no_evaluator();
3212 } else {
3213 price_insertion_sequences_evaluator(bin_capacities_);
3214 }
3215 if (StopSearch()) return;
3216 insertion_container_.RemoveIf(
3217 [](const InsertionSequence& sequence) -> bool {
3218 return sequence.Cost() == kint64max;
3219 });
3220 insertion_container_.Sort();
3221 for (InsertionSequence sequence : insertion_container_) {
3222 if (StopSearch()) return;
3223 int previous_node = -1;
3224 int previous_succ = -1;
3225 const int vehicle = sequence.Vehicle();
3226 for (const Insertion& insertion : sequence) {
3227 const int succ = previous_node == insertion.pred
3228 ? previous_succ
3229 : Value(insertion.pred);
3230 InsertBetween(insertion.node, insertion.pred, succ, vehicle);
3231 previous_node = insertion.node;
3232 previous_succ = succ;
3233 }
3234 if (Evaluate(/*commit=*/true, /*ignore_upper_bound=*/true)
3235 .has_value()) {
3236 // Insertion succeeded.
3237 if (MustUpdateBinCapacities()) {
3238 bin_capacities_->AddItemToBin(pickup, vehicle);
3239 bin_capacities_->AddItemToBin(delivery, vehicle);
3240 }
3241 return;
3242 }
3243 }
3244 }
3245 }
3246}
3247
3248namespace {
3249void SetFalseForAllAlternatives(const PickupDeliveryPair& pair,
3250 std::vector<bool>* data) {
3251 for (const int64_t pickup : pair.pickup_alternatives) {
3252 data->at(pickup) = false;
3253 }
3254 for (const int64_t delivery : pair.delivery_alternatives) {
3255 data->at(delivery) = false;
3256 }
3257}
3258} // namespace
3259
3261 const RoutingModel& model = *this->model();
3262
3263 // Fill vehicle bins with nodes that are already inserted.
3264 if (MustUpdateBinCapacities()) {
3265 bin_capacities_->ClearItems();
3266 for (int vehicle = 0; vehicle < model.vehicles(); ++vehicle) {
3267 const int start = Value(model.Start(vehicle));
3268 for (int node = start; !model.IsEnd(node); node = Value(node)) {
3269 bin_capacities_->AddItemToBin(node, vehicle);
3270 }
3271 }
3272 }
3273
3274 const std::vector<PickupDeliveryPair>& pairs =
3275 model.GetPickupAndDeliveryPairs();
3276 std::vector<bool> ignore_pair_index(pairs.size(), false);
3277 std::vector<bool> insert_as_single_node(model.Size(), true);
3278 for (int pair_index = 0; pair_index < pairs.size(); ++pair_index) {
3279 const auto& [pickups, deliveries] = pairs[pair_index];
3280 bool pickup_contained = false;
3281 for (int64_t pickup : pickups) {
3282 if (Contains(pickup)) {
3283 pickup_contained = true;
3284 break;
3285 }
3286 }
3287 bool delivery_contained = false;
3288 for (int64_t delivery : deliveries) {
3289 if (Contains(delivery)) {
3290 delivery_contained = true;
3291 break;
3292 }
3293 }
3294 ignore_pair_index[pair_index] = pickup_contained || delivery_contained;
3295 if (pickup_contained == delivery_contained) {
3296 // Either both pickup and delivery are already inserted for this pair, or
3297 // neither are inserted and should be considered as pair.
3298 // In both cases, the nodes in the pickup/delivery alternatives shouldn't
3299 // be considered for insertion as single nodes.
3300 SetFalseForAllAlternatives(pairs[pair_index], &insert_as_single_node);
3301 }
3302 }
3303
3304 for (const Seed& seed : insertion_order_) {
3305 const int index = seed.index;
3306 if (!seed.is_node_index) {
3307 if (ignore_pair_index[index]) continue;
3308
3309 const auto& pair = pairs[index];
3310 switch (pair_insertion_strategy_) {
3311 case RoutingSearchParameters::AUTOMATIC:
3312 case RoutingSearchParameters::BEST_PICKUP_DELIVERY_PAIR:
3313 InsertBestPair(pair);
3314 break;
3315 case RoutingSearchParameters::BEST_PICKUP_THEN_BEST_DELIVERY:
3316 InsertBestPickupThenDelivery(pair);
3317 break;
3318 case RoutingSearchParameters::BEST_PICKUP_DELIVERY_PAIR_MULTITOUR:
3319 InsertBestPairMultitour(pair);
3320 break;
3321 default:
3322 LOG(ERROR) << "Unknown pair insertion strategy value.";
3323 break;
3324 }
3325 if (StopSearch()) {
3326 return MakeUnassignedNodesUnperformed() && Evaluate(true).has_value();
3327 }
3328 } else {
3329 if (Contains(index) || !insert_as_single_node[index]) {
3330 continue;
3331 }
3332 for (const NodeInsertion& insertion :
3333 ComputeEvaluatorSortedPositions(index)) {
3334 if (StopSearch()) {
3335 return MakeUnassignedNodesUnperformed() && Evaluate(true).has_value();
3336 }
3337 InsertBetween(index, insertion.insert_after,
3338 Value(insertion.insert_after), insertion.vehicle);
3339 // Capturing the state of the delta before it gets wiped by Evaluate.
3340 std::vector<int> indices = delta_indices();
3341 if (Evaluate(/*commit=*/true, /*ignore_upper_bound=*/true)
3342 .has_value()) {
3343 if (MustUpdateBinCapacities()) {
3344 bin_capacities_->AddItemToBin(index, insertion.vehicle);
3345 }
3346 OptimizeOnInsertion(std::move(indices));
3347 break;
3348 }
3349 }
3350 }
3351 }
3352 return MakeUnassignedNodesUnperformed() && Evaluate(true).has_value();
3353}
3354
3355std::vector<LocalCheapestInsertionFilteredHeuristic::NodeInsertion>
3356LocalCheapestInsertionFilteredHeuristic::ComputeEvaluatorSortedPositions(
3357 int64_t node) {
3358 DCHECK(!Contains(node));
3359 const int size = model()->Size();
3360 if (node >= size) return {};
3361 std::vector<NodeInsertion> sorted_insertions;
3362 const IntVar* vehicle_var = model()->VehicleVar(node);
3363 std::unique_ptr<IntVarIterator> node_vehicles(
3364 vehicle_var->MakeDomainIterator(false));
3365 for (const int vehicle : InitAndGetValues(node_vehicles.get())) {
3366 if (vehicle == -1) continue;
3367 if (MustUpdateBinCapacities() &&
3368 !bin_capacities_->CheckAdditionFeasibility(node, vehicle)) {
3369 continue;
3370 }
3371 const int64_t start = model()->Start(vehicle);
3372 const size_t old_num_insertions = sorted_insertions.size();
3373 AppendInsertionPositionsAfter(node, start, Value(start), vehicle,
3374 /*ignore_cost=*/false, &sorted_insertions);
3375 if (MustUpdateBinCapacities() && evaluator_) {
3376 // Compute cost incurred from soft capacities.
3377 const int64_t old_cost = bin_capacities_->TotalCost();
3378 bin_capacities_->AddItemToBin(node, vehicle);
3379 const int64_t new_cost = bin_capacities_->TotalCost();
3380 bin_capacities_->RemoveItemFromBin(node, vehicle);
3381 const int64_t delta_cost = CapSub(new_cost, old_cost);
3382 // Add soft cost to new insertions.
3383 for (size_t i = old_num_insertions; i < sorted_insertions.size(); ++i) {
3384 CapAddTo(delta_cost, &sorted_insertions[i].value);
3385 }
3386 }
3387 }
3388 absl::c_sort(sorted_insertions);
3389 return sorted_insertions;
3390}
3391
3392std::vector<LocalCheapestInsertionFilteredHeuristic::NodeInsertion>
3393LocalCheapestInsertionFilteredHeuristic::
3394 ComputeEvaluatorSortedPositionsOnRouteAfter(int64_t node, int64_t start,
3395 int64_t next_after_start,
3396 int vehicle) {
3397 DCHECK(!Contains(node));
3398 const int size = model()->Size();
3399 if (node >= size) return {};
3400 std::vector<NodeInsertion> sorted_insertions;
3401 AppendInsertionPositionsAfter(node, start, next_after_start, vehicle,
3402 /*ignore_cost=*/false, &sorted_insertions);
3403 absl::c_sort(sorted_insertions);
3404 return sorted_insertions;
3405}
3406
3407std::vector<PickupDeliveryInsertion>
3408LocalCheapestInsertionFilteredHeuristic::ComputeEvaluatorSortedPairPositions(
3409 int pickup, int delivery) {
3410 std::vector<PickupDeliveryInsertion> sorted_pickup_delivery_insertions;
3411 const int size = model()->Size();
3412 DCHECK_LT(pickup, size);
3413 DCHECK_LT(delivery, size);
3414 const IntVar* pickup_vehicle_var = model()->VehicleVar(pickup);
3415 const IntVar* delivery_vehicle_var = model()->VehicleVar(delivery);
3416 std::unique_ptr<IntVarIterator> pickup_vehicles(
3417 pickup_vehicle_var->MakeDomainIterator(false));
3418 for (const int vehicle : InitAndGetValues(pickup_vehicles.get())) {
3419 if (vehicle == -1) continue;
3420 if (!delivery_vehicle_var->Contains(vehicle)) continue;
3421 if (MustUpdateBinCapacities() &&
3422 !bin_capacities_->CheckAdditionsFeasibility({pickup, delivery},
3423 vehicle)) {
3424 continue;
3425 }
3426 int64_t insert_pickup_after = model()->Start(vehicle);
3427 while (!model()->IsEnd(insert_pickup_after)) {
3428 const int64_t insert_pickup_before = Value(insert_pickup_after);
3429 int64_t insert_delivery_after = pickup;
3430 while (!model()->IsEnd(insert_delivery_after)) {
3431 if (StopSearch()) return {};
3432 const int64_t insert_delivery_before =
3433 insert_delivery_after == pickup ? insert_pickup_before
3434 : Value(insert_delivery_after);
3435 const int hint_weight = IsHint(insert_pickup_after, pickup) +
3436 IsHint(insert_delivery_after, delivery) +
3437 IsHint(pickup, insert_pickup_before) +
3438 IsHint(delivery, insert_delivery_before);
3439 if (evaluator_ == nullptr) {
3440 InsertBetween(pickup, insert_pickup_after, insert_pickup_before,
3441 vehicle);
3442 InsertBetween(delivery, insert_delivery_after, insert_delivery_before,
3443 vehicle);
3444 std::optional<int64_t> insertion_cost = Evaluate(
3445 /*commit=*/false, /*ignore_upper_bound=*/hint_weight > 0);
3446 if (insertion_cost.has_value()) {
3447 sorted_pickup_delivery_insertions.push_back(
3448 {insert_pickup_after, insert_delivery_after, -hint_weight,
3449 *insertion_cost, vehicle});
3450 }
3451 } else {
3452 const int64_t pickup_cost = GetInsertionCostForNodeAtPosition(
3453 pickup, insert_pickup_after, insert_pickup_before, vehicle);
3454 const int64_t delivery_cost = GetInsertionCostForNodeAtPosition(
3455 delivery, insert_delivery_after, insert_delivery_before, vehicle);
3456 int64_t total_cost = CapAdd(pickup_cost, delivery_cost);
3457 if (MustUpdateBinCapacities()) {
3458 const int64_t old_cost = bin_capacities_->TotalCost();
3459 bin_capacities_->AddItemToBin(pickup, vehicle);
3460 bin_capacities_->AddItemToBin(delivery, vehicle);
3461 const int64_t new_cost = bin_capacities_->TotalCost();
3462 CapAddTo(CapSub(new_cost, old_cost), &total_cost);
3463 bin_capacities_->RemoveItemFromBin(pickup, vehicle);
3464 bin_capacities_->RemoveItemFromBin(delivery, vehicle);
3465 }
3466 sorted_pickup_delivery_insertions.push_back(
3467 {insert_pickup_after, insert_delivery_after, -hint_weight,
3468 total_cost, vehicle});
3469 }
3470 insert_delivery_after = insert_delivery_before;
3471 }
3472 insert_pickup_after = insert_pickup_before;
3473 }
3474 }
3475 absl::c_sort(sorted_pickup_delivery_insertions);
3476 return sorted_pickup_delivery_insertions;
3477}
3478
3479// CheapestAdditionFilteredHeuristic
3480
3482 RoutingModel* model, std::function<bool()> stop_search,
3483 LocalSearchFilterManager* filter_manager)
3484 : RoutingFilteredHeuristic(model, std::move(stop_search), filter_manager) {}
3485
3487 const int kUnassigned = -1;
3488 const int num_nexts = model()->Nexts().size();
3489 std::vector<std::vector<int64_t>> deliveries(num_nexts);
3490 std::vector<std::vector<int64_t>> pickups(num_nexts);
3491 for (const auto& [pickup_alternatives, delivery_alternatives] :
3492 model()->GetPickupAndDeliveryPairs()) {
3493 for (int pickup : pickup_alternatives) {
3494 for (int delivery : delivery_alternatives) {
3495 deliveries[pickup].push_back(delivery);
3496 pickups[delivery].push_back(pickup);
3497 }
3498 }
3499 }
3500 // To mimic the behavior of PathSelector (cf. search.cc), iterating on
3501 // routes with partial route at their start first then on routes with largest
3502 // index.
3503 std::vector<int> sorted_vehicles(model()->vehicles(), 0);
3504 for (int vehicle = 0; vehicle < model()->vehicles(); ++vehicle) {
3505 sorted_vehicles[vehicle] = vehicle;
3506 }
3507 absl::c_sort(sorted_vehicles,
3508 PartialRoutesAndLargeVehicleIndicesFirst(*this));
3509 // Neighbors of the node currently being extended.
3510 for (const int vehicle : sorted_vehicles) {
3511 int64_t last_node = GetStartChainEnd(vehicle);
3512 bool extend_route = true;
3513 // Extend the route of the current vehicle while it's possible. We can
3514 // iterate more than once if pickup and delivery pairs have been inserted
3515 // in the last iteration (see comment below); the new iteration will try to
3516 // extend the route after the last delivery on the route.
3517 while (extend_route) {
3518 extend_route = false;
3519 bool found = true;
3520 int64_t index = last_node;
3521 int64_t end = GetEndChainStart(vehicle);
3522 // Extend the route until either the end node of the vehicle is reached
3523 // or no node or node pair can be added. Deliveries in pickup and
3524 // delivery pairs are added at the same time as pickups, at the end of the
3525 // route, in reverse order of the pickups. Deliveries are never added
3526 // alone.
3527 while (found && !model()->IsEnd(index)) {
3528 found = false;
3529 std::vector<int64_t> neighbors;
3530 if (index < model()->Nexts().size()) {
3531 std::unique_ptr<IntVarIterator> it(
3532 model()->Nexts()[index]->MakeDomainIterator(false));
3533 auto next_values = InitAndGetValues(it.get());
3534 neighbors = GetPossibleNextsFromIterator(index, next_values.begin(),
3535 next_values.end());
3536 }
3537 for (int i = 0; !found && i < neighbors.size(); ++i) {
3538 int64_t next = -1;
3539 switch (i) {
3540 case 0:
3541 next = FindTopSuccessor(index, neighbors);
3542 break;
3543 case 1:
3544 SortSuccessors(index, &neighbors);
3545 ABSL_FALLTHROUGH_INTENDED;
3546 default:
3547 next = neighbors[i];
3548 }
3549 if (model()->IsEnd(next) && next != end) {
3550 continue;
3551 }
3552 // Only add a delivery if one of its pickups has been added already.
3553 if (!model()->IsEnd(next) && !pickups[next].empty()) {
3554 bool contains_pickups = false;
3555 for (int64_t pickup : pickups[next]) {
3556 if (Contains(pickup)) {
3557 contains_pickups = true;
3558 break;
3559 }
3560 }
3561 if (!contains_pickups) {
3562 continue;
3563 }
3564 }
3565 std::vector<int64_t> next_deliveries;
3566 if (next < deliveries.size()) {
3567 next_deliveries = GetPossibleNextsFromIterator(
3568 next, deliveries[next].begin(), deliveries[next].end());
3569 }
3570 if (next_deliveries.empty()) next_deliveries = {kUnassigned};
3571 for (int j = 0; !found && j < next_deliveries.size(); ++j) {
3572 if (StopSearch()) return false;
3573 int delivery = -1;
3574 switch (j) {
3575 case 0:
3576 delivery = FindTopSuccessor(next, next_deliveries);
3577 break;
3578 case 1:
3579 SortSuccessors(next, &next_deliveries);
3580 ABSL_FALLTHROUGH_INTENDED;
3581 default:
3582 delivery = next_deliveries[j];
3583 }
3584 // Insert "next" after "index", and before "end" if it is not the
3585 // end already.
3586 SetNext(index, next, vehicle);
3587 if (!model()->IsEnd(next)) {
3588 SetNext(next, end, vehicle);
3590 if (delivery != kUnassigned) {
3591 SetNext(next, delivery, vehicle);
3592 SetNext(delivery, end, vehicle);
3594 }
3595 }
3596 if (Evaluate(/*commit=*/true).has_value()) {
3597 index = next;
3598 found = true;
3599 if (delivery != kUnassigned) {
3600 if (model()->IsEnd(end) && last_node != delivery) {
3601 last_node = delivery;
3602 extend_route = true;
3603 }
3604 end = delivery;
3605 }
3606 break;
3607 }
3608 }
3609 }
3610 }
3611 }
3612 }
3614 return Evaluate(/*commit=*/true).has_value();
3615}
3616
3617bool CheapestAdditionFilteredHeuristic::
3618 PartialRoutesAndLargeVehicleIndicesFirst::operator()(int vehicle1,
3619 int vehicle2) const {
3620 const bool has_partial_route1 = (builder_.model()->Start(vehicle1) !=
3621 builder_.GetStartChainEnd(vehicle1));
3622 const bool has_partial_route2 = (builder_.model()->Start(vehicle2) !=
3623 builder_.GetStartChainEnd(vehicle2));
3624 if (has_partial_route1 == has_partial_route2) {
3625 return vehicle2 < vehicle1;
3626 } else {
3627 return has_partial_route2 < has_partial_route1;
3628 }
3629}
3630
3631// EvaluatorCheapestAdditionFilteredHeuristic
3632
3635 RoutingModel* model, std::function<bool()> stop_search,
3636 std::function<int64_t(int64_t, int64_t)> evaluator,
3637 LocalSearchFilterManager* filter_manager)
3638 : CheapestAdditionFilteredHeuristic(model, std::move(stop_search),
3639 filter_manager),
3640 evaluator_(std::move(evaluator)) {}
3641
3642int64_t EvaluatorCheapestAdditionFilteredHeuristic::FindTopSuccessor(
3643 int64_t node, const std::vector<int64_t>& successors) {
3644 int64_t best_evaluation = std::numeric_limits<int64_t>::max();
3645 int64_t best_successor = -1;
3646 for (int64_t successor : successors) {
3647 const int64_t evaluation = (successor >= 0)
3648 ? evaluator_(node, successor)
3649 : std::numeric_limits<int64_t>::max();
3650 if (evaluation < best_evaluation ||
3651 (evaluation == best_evaluation && successor > best_successor)) {
3652 best_evaluation = evaluation;
3653 best_successor = successor;
3654 }
3655 }
3656 return best_successor;
3657}
3658
3659void EvaluatorCheapestAdditionFilteredHeuristic::SortSuccessors(
3660 int64_t node, std::vector<int64_t>* successors) {
3661 std::vector<std::pair<int64_t, int64_t>> values;
3662 values.reserve(successors->size());
3663 for (int64_t successor : *successors) {
3664 // Tie-breaking on largest node index to mimic the behavior of
3665 // CheapestValueSelector (search.cc).
3666 values.push_back({evaluator_(node, successor), successor});
3667 }
3668 absl::c_sort(values, [](const std::pair<int64_t, int64_t>& s1,
3669 const std::pair<int64_t, int64_t>& s2) {
3670 return s1.first < s2.first ||
3671 (s1.first == s2.first && s1.second > s2.second);
3672 });
3673 successors->clear();
3674 for (auto value : values) {
3675 successors->push_back(value.second);
3676 }
3677}
3678
3679// ComparatorCheapestAdditionFilteredHeuristic
3680
3683 RoutingModel* model, std::function<bool()> stop_search,
3685 LocalSearchFilterManager* filter_manager)
3686 : CheapestAdditionFilteredHeuristic(model, std::move(stop_search),
3687 filter_manager),
3688 comparator_(std::move(comparator)) {}
3689
3690int64_t ComparatorCheapestAdditionFilteredHeuristic::FindTopSuccessor(
3691 int64_t node, const std::vector<int64_t>& successors) {
3692 return *absl::c_min_element(
3693 successors, [this, node](int successor1, int successor2) {
3694 return comparator_(node, successor1, successor2);
3695 });
3696}
3697
3698void ComparatorCheapestAdditionFilteredHeuristic::SortSuccessors(
3699 int64_t node, std::vector<int64_t>* successors) {
3700 absl::c_sort(*successors, [this, node](int successor1, int successor2) {
3701 return comparator_(node, successor1, successor2);
3702 });
3703}
3704
3705// Class storing and allowing access to the savings according to the number of
3706// vehicle types.
3707// The savings are stored and sorted in sorted_savings_per_vehicle_type_.
3708// Furthermore, when there is more than one vehicle type, the savings for a same
3709// before-->after arc are sorted in costs_and_savings_per_arc_[arc] by
3710// increasing cost(s-->before-->after-->e), where s and e are the start and end
3711// of the route, in order to make sure the arc is served by the route with the
3712// closest depot (start/end) possible.
3713// When there is only one vehicle "type" (i.e. all vehicles have the same
3714// start/end and cost class), each arc has a single saving value associated to
3715// it, so we ignore this last step to avoid unnecessary computations, and only
3716// work with sorted_savings_per_vehicle_type_[0].
3717// In case of multiple vehicle types, the best savings for each arc, i.e. the
3718// savings corresponding to the closest vehicle type, are inserted and sorted in
3719// sorted_savings_.
3720//
3721// This class also handles skipped Savings:
3722// The vectors skipped_savings_starting/ending_at_ contain all the Savings that
3723// weren't added to the model, which we want to consider for later:
3724// 1) When a Saving before-->after with both nodes uncontained cannot be used to
3725// start a new route (no more available vehicles or could not commit on any
3726// of those available).
3727// 2) When only one of the nodes of the Saving is contained but on a different
3728// vehicle type.
3729// In these cases, the Update() method is called with update_best_saving = true,
3730// which in turn calls SkipSavingForArc() (within
3731// UpdateNextAndSkippedSavingsForArcWithType()) to mark the Saving for this arc
3732// (with the correct type in the second case) as "skipped", by storing it in
3733// skipped_savings_starting_at_[before] and skipped_savings_ending_at_[after].
3734//
3735// UpdateNextAndSkippedSavingsForArcWithType() also updates the next_savings_
3736// vector, which stores the savings to go through once we've iterated through
3737// all sorted_savings_.
3738// In the first case above, where neither nodes are contained, we skip the
3739// current Saving (current_saving_), and add the next best Saving for this arc
3740// to next_savings_ (in case this skipped Saving is never considered).
3741// In the second case with a specific type, we search for the Saving with the
3742// correct type for this arc, and add it to both next_savings_ and the skipped
3743// Savings.
3744//
3745// The skipped Savings are then re-considered when one of their ends gets
3746// inserted:
3747// When another Saving other_node-->before (or after-->other_node) gets
3748// inserted, all skipped Savings in skipped_savings_starting_at_[before] (or
3749// skipped_savings_ending_at_[after]) are once again considered by calling
3750// ReinjectSkippedSavingsStartingAt() (or ReinjectSkippedSavingsEndingAt()).
3751// Then, when calling GetSaving(), we iterate through the reinjected Savings in
3752// order of insertion in the vectors while there are reinjected savings.
3753template <typename Saving>
3755 public:
3756 explicit SavingsContainer(const SavingsFilteredHeuristic* savings_db,
3757 int vehicle_types)
3758 : savings_db_(savings_db),
3759 index_in_sorted_savings_(0),
3760 vehicle_types_(vehicle_types),
3761 single_vehicle_type_(vehicle_types == 1),
3762 using_incoming_reinjected_saving_(false),
3763 sorted_(false),
3764 to_update_(true) {}
3765
3766 void InitializeContainer(int64_t size, int64_t saving_neighbors) {
3767 sorted_savings_per_vehicle_type_.clear();
3768 sorted_savings_per_vehicle_type_.resize(vehicle_types_);
3769 for (std::vector<Saving>& savings : sorted_savings_per_vehicle_type_) {
3770 savings.reserve(size * saving_neighbors);
3771 }
3772
3773 sorted_savings_.clear();
3774 costs_and_savings_per_arc_.clear();
3775 arc_indices_per_before_node_.clear();
3776
3777 if (!single_vehicle_type_) {
3778 costs_and_savings_per_arc_.reserve(size * saving_neighbors);
3779 arc_indices_per_before_node_.resize(size);
3780 for (int before_node = 0; before_node < size; before_node++) {
3781 arc_indices_per_before_node_[before_node].reserve(saving_neighbors);
3782 }
3783 }
3784 skipped_savings_starting_at_.clear();
3785 skipped_savings_starting_at_.resize(size);
3786 skipped_savings_ending_at_.clear();
3787 skipped_savings_ending_at_.resize(size);
3788 incoming_reinjected_savings_ = nullptr;
3789 outgoing_reinjected_savings_ = nullptr;
3790 incoming_new_reinjected_savings_ = nullptr;
3791 outgoing_new_reinjected_savings_ = nullptr;
3792 }
3793
3794 void AddNewSaving(const Saving& saving, int64_t total_cost,
3795 int64_t before_node, int64_t after_node, int vehicle_type) {
3796 CHECK(!sorted_savings_per_vehicle_type_.empty())
3797 << "Container not initialized!";
3798 sorted_savings_per_vehicle_type_[vehicle_type].push_back(saving);
3799 UpdateArcIndicesCostsAndSavings(before_node, after_node,
3800 {total_cost, saving});
3801 }
3802
3803 void Sort() {
3804 CHECK(!sorted_) << "Container already sorted!";
3805
3806 for (std::vector<Saving>& savings : sorted_savings_per_vehicle_type_) {
3807 absl::c_sort(savings);
3808 }
3809
3810 if (single_vehicle_type_) {
3811 const auto& savings = sorted_savings_per_vehicle_type_[0];
3812 sorted_savings_.resize(savings.size());
3813 absl::c_transform(savings, sorted_savings_.begin(),
3814 [](const Saving& saving) {
3815 return SavingAndArc({saving, /*arc_index*/ -1});
3816 });
3817 } else {
3818 // For each arc, sort the savings by decreasing total cost
3819 // start-->a-->b-->end.
3820 // The best saving for each arc is therefore the last of its vector.
3821 sorted_savings_.reserve(vehicle_types_ *
3822 costs_and_savings_per_arc_.size());
3823
3824 for (int arc_index = 0; arc_index < costs_and_savings_per_arc_.size();
3825 arc_index++) {
3826 std::vector<std::pair<int64_t, Saving>>& costs_and_savings =
3827 costs_and_savings_per_arc_[arc_index];
3828 DCHECK(!costs_and_savings.empty());
3829
3830 absl::c_sort(
3831 costs_and_savings,
3832 [](const std::pair<int64_t, Saving>& cs1,
3833 const std::pair<int64_t, Saving>& cs2) { return cs1 > cs2; });
3834
3835 // Insert all Savings for this arc with the lowest cost into
3836 // sorted_savings_.
3837 // TODO(user): Also do this when reiterating on next_savings_.
3838 const int64_t cost = costs_and_savings.back().first;
3839 while (!costs_and_savings.empty() &&
3840 costs_and_savings.back().first == cost) {
3841 sorted_savings_.push_back(
3842 {costs_and_savings.back().second, arc_index});
3843 costs_and_savings.pop_back();
3844 }
3845 }
3846 absl::c_sort(sorted_savings_);
3847 next_saving_type_and_index_for_arc_.clear();
3848 next_saving_type_and_index_for_arc_.resize(
3849 costs_and_savings_per_arc_.size(), {-1, -1});
3850 }
3851 sorted_ = true;
3852 index_in_sorted_savings_ = 0;
3853 to_update_ = false;
3854 }
3855
3856 bool HasSaving() {
3857 return index_in_sorted_savings_ < sorted_savings_.size() ||
3858 HasReinjectedSavings();
3859 }
3860
3862 CHECK(sorted_) << "Calling GetSaving() before Sort() !";
3863 CHECK(!to_update_)
3864 << "Update() should be called between two calls to GetSaving() !";
3865
3866 to_update_ = true;
3867
3868 if (HasReinjectedSavings()) {
3869 if (incoming_reinjected_savings_ != nullptr &&
3870 outgoing_reinjected_savings_ != nullptr) {
3871 // Get the best Saving among the two.
3872 SavingAndArc& incoming_saving = incoming_reinjected_savings_->front();
3873 SavingAndArc& outgoing_saving = outgoing_reinjected_savings_->front();
3874 if (incoming_saving < outgoing_saving) {
3875 current_saving_ = incoming_saving;
3876 using_incoming_reinjected_saving_ = true;
3877 } else {
3878 current_saving_ = outgoing_saving;
3879 using_incoming_reinjected_saving_ = false;
3880 }
3881 } else {
3882 if (incoming_reinjected_savings_ != nullptr) {
3883 current_saving_ = incoming_reinjected_savings_->front();
3884 using_incoming_reinjected_saving_ = true;
3885 }
3886 if (outgoing_reinjected_savings_ != nullptr) {
3887 current_saving_ = outgoing_reinjected_savings_->front();
3888 using_incoming_reinjected_saving_ = false;
3889 }
3890 }
3891 } else {
3892 current_saving_ = sorted_savings_[index_in_sorted_savings_];
3893 }
3894 return current_saving_.saving;
3895 }
3896
3897 void Update(bool update_best_saving, int type = -1) {
3898 CHECK(to_update_) << "Container already up to date!";
3899 if (update_best_saving) {
3900 const int64_t arc_index = current_saving_.arc_index;
3901 UpdateNextAndSkippedSavingsForArcWithType(arc_index, type);
3902 }
3903 if (!HasReinjectedSavings()) {
3904 index_in_sorted_savings_++;
3905
3906 if (index_in_sorted_savings_ == sorted_savings_.size()) {
3907 sorted_savings_.swap(next_savings_);
3908 gtl::STLClearObject(&next_savings_);
3909 index_in_sorted_savings_ = 0;
3910
3911 absl::c_sort(sorted_savings_);
3912 next_saving_type_and_index_for_arc_.clear();
3913 next_saving_type_and_index_for_arc_.resize(
3914 costs_and_savings_per_arc_.size(), {-1, -1});
3915 }
3916 }
3917 UpdateReinjectedSavings();
3918 to_update_ = false;
3919 }
3920
3921 void UpdateWithType(int type) {
3922 CHECK(!single_vehicle_type_);
3923 Update(/*update_best_saving*/ true, type);
3924 }
3925
3926 const std::vector<Saving>& GetSortedSavingsForVehicleType(int type) {
3927 CHECK(sorted_) << "Savings not sorted yet!";
3928 CHECK_LT(type, vehicle_types_);
3929 return sorted_savings_per_vehicle_type_[type];
3930 }
3931
3933 CHECK(outgoing_new_reinjected_savings_ == nullptr);
3934 outgoing_new_reinjected_savings_ = &(skipped_savings_starting_at_[node]);
3935 }
3936
3938 CHECK(incoming_new_reinjected_savings_ == nullptr);
3939 incoming_new_reinjected_savings_ = &(skipped_savings_ending_at_[node]);
3940 }
3941
3942 private:
3943 struct SavingAndArc {
3944 Saving saving;
3945 int64_t arc_index;
3946
3947 bool operator<(const SavingAndArc& other) const {
3948 return std::tie(saving, arc_index) <
3949 std::tie(other.saving, other.arc_index);
3950 }
3951 };
3952
3953 // Skips the Saving for the arc before_node-->after_node, by adding it to the
3954 // skipped_savings_ vector of the nodes, if they're uncontained.
3955 void SkipSavingForArc(const SavingAndArc& saving_and_arc) {
3956 const Saving& saving = saving_and_arc.saving;
3957 const int64_t before_node = saving.before_node;
3958 const int64_t after_node = saving.after_node;
3959 if (!savings_db_->Contains(before_node)) {
3960 skipped_savings_starting_at_[before_node].push_back(saving_and_arc);
3961 }
3962 if (!savings_db_->Contains(after_node)) {
3963 skipped_savings_ending_at_[after_node].push_back(saving_and_arc);
3964 }
3965 }
3966
3967 // Called within Update() when update_best_saving is true, this method updates
3968 // the next_savings_ and skipped savings vectors for a given arc_index and
3969 // vehicle type.
3970 // When a Saving with the right type has already been added to next_savings_
3971 // for this arc, no action is needed on next_savings_.
3972 // Otherwise, if such a Saving exists, GetNextSavingForArcWithType() will find
3973 // and assign it to next_saving, which is then used to update next_savings_.
3974 // Finally, the right Saving is skipped for this arc: if looking for a
3975 // specific type (i.e. type != -1), next_saving (which has the correct type)
3976 // is skipped, otherwise the current_saving_ is.
3977 void UpdateNextAndSkippedSavingsForArcWithType(int64_t arc_index, int type) {
3978 if (single_vehicle_type_) {
3979 // No next Saving, skip the current Saving.
3980 CHECK_EQ(type, -1);
3981 SkipSavingForArc(current_saving_);
3982 return;
3983 }
3984 CHECK_GE(arc_index, 0);
3985 auto& type_and_index = next_saving_type_and_index_for_arc_[arc_index];
3986 const int previous_index = type_and_index.second;
3987 const int previous_type = type_and_index.first;
3988 bool next_saving_added = false;
3989 Saving next_saving;
3990
3991 if (previous_index >= 0) {
3992 // Next Saving already added for this arc.
3993 DCHECK_GE(previous_type, 0);
3994 if (type == -1 || previous_type == type) {
3995 // Not looking for a specific type, or correct type already in
3996 // next_savings_.
3997 next_saving_added = true;
3998 next_saving = next_savings_[previous_index].saving;
3999 }
4000 }
4001
4002 if (!next_saving_added &&
4003 GetNextSavingForArcWithType(arc_index, type, &next_saving)) {
4004 type_and_index.first = next_saving.vehicle_type;
4005 if (previous_index >= 0) {
4006 // Update the previous saving.
4007 next_savings_[previous_index] = {next_saving, arc_index};
4008 } else {
4009 // Insert the new next Saving for this arc.
4010 type_and_index.second = next_savings_.size();
4011 next_savings_.push_back({next_saving, arc_index});
4012 }
4013 next_saving_added = true;
4014 }
4015
4016 // Skip the Saving based on the vehicle type.
4017 if (type == -1) {
4018 // Skip the current Saving.
4019 SkipSavingForArc(current_saving_);
4020 } else {
4021 // Skip the Saving with the correct type, already added to next_savings_
4022 // if it was found.
4023 if (next_saving_added) {
4024 SkipSavingForArc({next_saving, arc_index});
4025 }
4026 }
4027 }
4028
4029 void UpdateReinjectedSavings() {
4030 UpdateGivenReinjectedSavings(incoming_new_reinjected_savings_,
4031 &incoming_reinjected_savings_,
4032 using_incoming_reinjected_saving_);
4033 UpdateGivenReinjectedSavings(outgoing_new_reinjected_savings_,
4034 &outgoing_reinjected_savings_,
4035 !using_incoming_reinjected_saving_);
4036 incoming_new_reinjected_savings_ = nullptr;
4037 outgoing_new_reinjected_savings_ = nullptr;
4038 }
4039
4040 void UpdateGivenReinjectedSavings(
4041 std::deque<SavingAndArc>* new_reinjected_savings,
4042 std::deque<SavingAndArc>** reinjected_savings,
4043 bool using_reinjected_savings) {
4044 if (new_reinjected_savings == nullptr) {
4045 // No new reinjected savings, update the previous ones if needed.
4046 if (*reinjected_savings != nullptr && using_reinjected_savings) {
4047 CHECK(!(*reinjected_savings)->empty());
4048 (*reinjected_savings)->pop_front();
4049 if ((*reinjected_savings)->empty()) {
4050 *reinjected_savings = nullptr;
4051 }
4052 }
4053 return;
4054 }
4055
4056 // New savings reinjected.
4057 // Forget about the previous reinjected savings and add the new ones if
4058 // there are any.
4059 if (*reinjected_savings != nullptr) {
4060 (*reinjected_savings)->clear();
4061 }
4062 *reinjected_savings = nullptr;
4063 if (!new_reinjected_savings->empty()) {
4064 *reinjected_savings = new_reinjected_savings;
4065 }
4066 }
4067
4068 bool HasReinjectedSavings() {
4069 return outgoing_reinjected_savings_ != nullptr ||
4070 incoming_reinjected_savings_ != nullptr;
4071 }
4072
4073 void UpdateArcIndicesCostsAndSavings(
4074 int64_t before_node, int64_t after_node,
4075 const std::pair<int64_t, Saving>& cost_and_saving) {
4076 if (single_vehicle_type_) {
4077 return;
4078 }
4079 absl::flat_hash_map<int, int>& arc_indices =
4080 arc_indices_per_before_node_[before_node];
4081 const auto& arc_inserted = arc_indices.insert(
4082 std::make_pair(after_node, costs_and_savings_per_arc_.size()));
4083 const int index = arc_inserted.first->second;
4084 if (arc_inserted.second) {
4085 costs_and_savings_per_arc_.push_back({cost_and_saving});
4086 } else {
4087 DCHECK_LT(index, costs_and_savings_per_arc_.size());
4088 costs_and_savings_per_arc_[index].push_back(cost_and_saving);
4089 }
4090 }
4091
4092 bool GetNextSavingForArcWithType(int64_t arc_index, int type,
4093 Saving* next_saving) {
4094 std::vector<std::pair<int64_t, Saving>>& costs_and_savings =
4095 costs_and_savings_per_arc_[arc_index];
4096
4097 bool found_saving = false;
4098 while (!costs_and_savings.empty() && !found_saving) {
4099 const Saving& saving = costs_and_savings.back().second;
4100 if (type == -1 || saving.vehicle_type == type) {
4101 *next_saving = saving;
4102 found_saving = true;
4103 }
4104 costs_and_savings.pop_back();
4105 }
4106 return found_saving;
4107 }
4108
4109 const SavingsFilteredHeuristic* const savings_db_;
4110 int64_t index_in_sorted_savings_;
4111 std::vector<std::vector<Saving>> sorted_savings_per_vehicle_type_;
4112 std::vector<SavingAndArc> sorted_savings_;
4113 std::vector<SavingAndArc> next_savings_;
4114 std::vector<std::pair</*type*/ int, /*index*/ int>>
4115 next_saving_type_and_index_for_arc_;
4116 SavingAndArc current_saving_;
4117 std::vector<std::vector<std::pair</*cost*/ int64_t, Saving>>>
4118 costs_and_savings_per_arc_;
4119 std::vector<absl::flat_hash_map</*after_node*/ int, /*arc_index*/ int>>
4120 arc_indices_per_before_node_;
4121 std::vector<std::deque<SavingAndArc>> skipped_savings_starting_at_;
4122 std::vector<std::deque<SavingAndArc>> skipped_savings_ending_at_;
4123 std::deque<SavingAndArc>* outgoing_reinjected_savings_;
4124 std::deque<SavingAndArc>* incoming_reinjected_savings_;
4125 std::deque<SavingAndArc>* outgoing_new_reinjected_savings_;
4126 std::deque<SavingAndArc>* incoming_new_reinjected_savings_;
4127 const int vehicle_types_;
4128 const bool single_vehicle_type_;
4129 bool using_incoming_reinjected_saving_;
4130 bool sorted_;
4131 bool to_update_;
4132};
4133
4134// SavingsFilteredHeuristic
4135
4137 RoutingModel* model, std::function<bool()> stop_search,
4138 SavingsParameters parameters, LocalSearchFilterManager* filter_manager)
4139 : RoutingFilteredHeuristic(model, std::move(stop_search), filter_manager),
4140 vehicle_type_curator_(nullptr),
4141 savings_params_(parameters) {
4142 DCHECK_GT(savings_params_.neighbors_ratio, 0);
4143 DCHECK_LE(savings_params_.neighbors_ratio, 1);
4144 DCHECK_GT(savings_params_.max_memory_usage_bytes, 0);
4145 DCHECK_GT(savings_params_.arc_coefficient, 0);
4146}
4147
4149
4151 if (vehicle_type_curator_ == nullptr) {
4152 vehicle_type_curator_ = std::make_unique<VehicleTypeCurator>(
4153 model()->GetVehicleTypeContainer());
4154 }
4155 // Only store empty vehicles in the vehicle_type_curator_.
4156 vehicle_type_curator_->Reset(
4157 [this](int vehicle) { return VehicleIsEmpty(vehicle); });
4158 if (!ComputeSavings()) return false;
4160 // Free all the space used to store the Savings in the container.
4161 savings_container_.reset();
4164 if (!Evaluate(/*commit=*/true).has_value()) return false;
4166 return Evaluate(/*commit=*/true).has_value();
4167}
4168
4170 int type, int64_t before_node, int64_t after_node) {
4171 auto vehicle_is_compatible = [this, before_node, after_node](int vehicle) {
4172 if (!model()->VehicleVar(before_node)->Contains(vehicle) ||
4173 !model()->VehicleVar(after_node)->Contains(vehicle)) {
4174 return false;
4175 }
4176 // Try to commit the arc on this vehicle.
4177 DCHECK(VehicleIsEmpty(vehicle));
4178 SetNext(model()->Start(vehicle), before_node, vehicle);
4179 SetNext(before_node, after_node, vehicle);
4180 SetNext(after_node, model()->End(vehicle), vehicle);
4181 return Evaluate(/*commit=*/true).has_value();
4182 };
4183
4185 ->GetCompatibleVehicleOfType(
4186 type, vehicle_is_compatible,
4187 /*stop_and_return_vehicle*/ [](int) { return false; })
4188 .first;
4189}
4190
4191void SavingsFilteredHeuristic::AddSymmetricArcsToAdjacencyLists(
4192 std::vector<std::vector<int64_t>>* adjacency_lists) {
4193 for (int64_t node = 0; node < adjacency_lists->size(); node++) {
4194 for (int64_t neighbor : (*adjacency_lists)[node]) {
4195 if (model()->IsStart(neighbor) || model()->IsEnd(neighbor)) {
4196 continue;
4197 }
4198 (*adjacency_lists)[neighbor].push_back(node);
4199 }
4200 }
4201 absl::c_transform(*adjacency_lists, adjacency_lists->begin(),
4202 [](std::vector<int64_t> vec) {
4203 absl::c_sort(vec);
4204 vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
4205 return vec;
4206 });
4207}
4208
4209// Computes the savings related to each pair of non-start and non-end nodes.
4210// The savings value for an arc a-->b for a vehicle starting at node s and
4211// ending at node e is:
4212// saving = cost(s-->a-->e) + cost(s-->b-->e) - cost(s-->a-->b-->e), i.e.
4213// saving = cost(a-->e) + cost(s-->b) - cost(a-->b)
4214// The saving value also considers a coefficient for the cost of the arc
4215// a-->b, which results in:
4216// saving = cost(a-->e) + cost(s-->b) - [arc_coefficient_ * cost(a-->b)]
4217// The higher this saving value, the better the arc.
4218// Here, the value stored for the savings is -saving, which are therefore
4219// considered in decreasing order.
4220bool SavingsFilteredHeuristic::ComputeSavings() {
4221 const int num_vehicle_types = vehicle_type_curator_->NumTypes();
4222 const int size = model()->Size();
4223
4224 std::vector<int64_t> uncontained_non_start_end_nodes;
4225 uncontained_non_start_end_nodes.reserve(size);
4226 for (int node = 0; node < size; node++) {
4227 if (!model()->IsStart(node) && !model()->IsEnd(node) && !Contains(node)) {
4228 uncontained_non_start_end_nodes.push_back(node);
4229 }
4230 }
4231
4232 const int64_t saving_neighbors =
4233 std::min(MaxNumNeighborsPerNode(num_vehicle_types),
4234 static_cast<int64_t>(uncontained_non_start_end_nodes.size()));
4235
4237 std::make_unique<SavingsContainer<Saving>>(this, num_vehicle_types);
4238 savings_container_->InitializeContainer(size, saving_neighbors);
4239 if (StopSearch()) return false;
4240 std::vector<std::vector<int64_t>> adjacency_lists(size);
4241
4242 for (int type = 0; type < num_vehicle_types; ++type) {
4243 const int vehicle =
4244 vehicle_type_curator_->GetLowestFixedCostVehicleOfType(type);
4245 if (vehicle < 0) continue;
4246
4247 const int64_t cost_class =
4248 model()->GetCostClassIndexOfVehicle(vehicle).value();
4249 const int64_t start = model()->Start(vehicle);
4250 const int64_t end = model()->End(vehicle);
4251 const int64_t fixed_cost = model()->GetFixedCostOfVehicle(vehicle);
4252
4253 // Compute the neighbors for each non-start/end node not already inserted in
4254 // the model.
4255 for (int before_node : uncontained_non_start_end_nodes) {
4256 std::vector<std::pair</*cost*/ int64_t, /*node*/ int64_t>>
4257 costed_after_nodes;
4258 costed_after_nodes.reserve(uncontained_non_start_end_nodes.size());
4259 if (StopSearch()) return false;
4260 for (int after_node : uncontained_non_start_end_nodes) {
4261 if (after_node != before_node) {
4262 costed_after_nodes.push_back(std::make_pair(
4263 model()->GetArcCostForClass(before_node, after_node, cost_class),
4264 after_node));
4265 }
4266 }
4267 if (saving_neighbors < costed_after_nodes.size()) {
4268 std::nth_element(costed_after_nodes.begin(),
4269 costed_after_nodes.begin() + saving_neighbors,
4270 costed_after_nodes.end());
4271 costed_after_nodes.resize(saving_neighbors);
4272 }
4273 adjacency_lists[before_node].resize(costed_after_nodes.size());
4274 std::transform(costed_after_nodes.begin(), costed_after_nodes.end(),
4275 adjacency_lists[before_node].begin(),
4276 [](std::pair<int64_t, int64_t> cost_and_node) {
4277 return cost_and_node.second;
4278 });
4279 }
4280 if (savings_params_.add_reverse_arcs) {
4281 AddSymmetricArcsToAdjacencyLists(&adjacency_lists);
4282 }
4283 if (StopSearch()) return false;
4284
4285 // Build the savings for this vehicle type given the adjacency_lists.
4286 for (int before_node : uncontained_non_start_end_nodes) {
4287 const int64_t before_to_end_cost =
4288 model()->GetArcCostForClass(before_node, end, cost_class);
4289 const int64_t start_to_before_cost =
4290 CapSub(model()->GetArcCostForClass(start, before_node, cost_class),
4291 fixed_cost);
4292 if (StopSearch()) return false;
4293 for (int64_t after_node : adjacency_lists[before_node]) {
4294 if (model()->IsStart(after_node) || model()->IsEnd(after_node) ||
4295 before_node == after_node || Contains(after_node)) {
4296 continue;
4297 }
4298 const int64_t arc_cost =
4299 model()->GetArcCostForClass(before_node, after_node, cost_class);
4300 const int64_t start_to_after_cost =
4301 CapSub(model()->GetArcCostForClass(start, after_node, cost_class),
4302 fixed_cost);
4303 const int64_t after_to_end_cost =
4304 model()->GetArcCostForClass(after_node, end, cost_class);
4305
4306 const double weighted_arc_cost_fp =
4307 savings_params_.arc_coefficient * arc_cost;
4308 const int64_t weighted_arc_cost =
4309 weighted_arc_cost_fp < std::numeric_limits<int64_t>::max()
4310 ? static_cast<int64_t>(weighted_arc_cost_fp)
4311 : std::numeric_limits<int64_t>::max();
4312 const int64_t saving_value = CapSub(
4313 CapAdd(before_to_end_cost, start_to_after_cost), weighted_arc_cost);
4314
4315 const Saving saving =
4316 BuildSaving(-saving_value, type, before_node, after_node);
4317
4318 const int64_t total_cost =
4319 CapAdd(CapAdd(start_to_before_cost, arc_cost), after_to_end_cost);
4320
4321 savings_container_->AddNewSaving(saving, total_cost, before_node,
4322 after_node, type);
4323 }
4324 }
4325 }
4326 savings_container_->Sort();
4327 return !StopSearch();
4328}
4329
4330int64_t SavingsFilteredHeuristic::MaxNumNeighborsPerNode(
4331 int num_vehicle_types) const {
4332 const int64_t size = model()->Size();
4333
4334 const int64_t num_neighbors_with_ratio =
4335 std::max(1.0, size * savings_params_.neighbors_ratio);
4336
4337 // A single Saving takes 2*8 bytes of memory.
4338 // max_memory_usage_in_savings_unit = num_savings * multiplicative_factor,
4339 // Where multiplicative_factor is the memory taken (in Savings unit) for each
4340 // computed Saving.
4341 const double max_memory_usage_in_savings_unit =
4342 savings_params_.max_memory_usage_bytes / 16;
4343
4344 // In the SavingsContainer, for each Saving, the Savings are stored:
4345 // - Once in "sorted_savings_per_vehicle_type", and (at most) once in
4346 // "sorted_savings_" --> factor 2
4347 // - If num_vehicle_types > 1, they're also stored by arc_index in
4348 // "costs_and_savings_per_arc", along with their int64_t cost --> factor 1.5
4349 //
4350 // On top of that,
4351 // - In the sequential version, the Saving* are also stored by in-coming and
4352 // outgoing node (in in/out_savings_ptr), adding another 2*8 bytes per
4353 // Saving --> factor 1.
4354 // - In the parallel version, skipped Savings are also stored in
4355 // skipped_savings_starting/ending_at_, resulting in a maximum added factor
4356 // of 2 for each Saving.
4357 // These extra factors are given by ExtraSavingsMemoryMultiplicativeFactor().
4358 double multiplicative_factor = 2.0 + ExtraSavingsMemoryMultiplicativeFactor();
4359 if (num_vehicle_types > 1) {
4360 multiplicative_factor += 1.5;
4361 }
4362 const double num_savings =
4363 max_memory_usage_in_savings_unit / multiplicative_factor;
4364 const int64_t num_neighbors_with_memory_restriction =
4365 std::max(1.0, num_savings / (num_vehicle_types * size));
4366
4367 return std::min(num_neighbors_with_ratio,
4368 num_neighbors_with_memory_restriction);
4369}
4370
4371// SequentialSavingsFilteredHeuristic
4372
4373void SequentialSavingsFilteredHeuristic::BuildRoutesFromSavings() {
4374 const int vehicle_types = vehicle_type_curator_->NumTypes();
4375 DCHECK_GT(vehicle_types, 0);
4376 const int size = model()->Size();
4377 // Store savings for each incoming and outgoing node and by vehicle type. This
4378 // is necessary to quickly extend partial chains without scanning all savings.
4379 std::vector<std::vector<const Saving*>> in_savings_ptr(size * vehicle_types);
4380 std::vector<std::vector<const Saving*>> out_savings_ptr(size * vehicle_types);
4381 for (int type = 0; type < vehicle_types; type++) {
4382 const int vehicle_type_offset = type * size;
4383 const std::vector<Saving>& sorted_savings_for_type =
4384 savings_container_->GetSortedSavingsForVehicleType(type);
4385 for (const Saving& saving : sorted_savings_for_type) {
4386 DCHECK_EQ(saving.vehicle_type, type);
4387 const int before_node = saving.before_node;
4388 in_savings_ptr[vehicle_type_offset + before_node].push_back(&saving);
4389 const int after_node = saving.after_node;
4390 out_savings_ptr[vehicle_type_offset + after_node].push_back(&saving);
4391 }
4392 }
4393
4394 // Build routes from savings.
4395 while (savings_container_->HasSaving()) {
4396 if (StopSearch()) return;
4397 // First find the best saving to start a new route.
4398 const Saving saving = savings_container_->GetSaving();
4399 int before_node = saving.before_node;
4400 int after_node = saving.after_node;
4401 const bool nodes_contained = Contains(before_node) || Contains(after_node);
4402
4403 if (nodes_contained) {
4404 savings_container_->Update(false);
4405 continue;
4406 }
4407
4408 // Find the right vehicle to start the route with this Saving.
4409 const int type = saving.vehicle_type;
4410 const int vehicle =
4411 StartNewRouteWithBestVehicleOfType(type, before_node, after_node);
4412 if (vehicle < 0) {
4413 savings_container_->Update(true);
4414 continue;
4415 }
4416
4417 const int64_t start = model()->Start(vehicle);
4418 const int64_t end = model()->End(vehicle);
4419 // Then extend the route from both ends of the partial route.
4420 int in_index = 0;
4421 int out_index = 0;
4422 const int saving_offset = type * size;
4423
4424 while (in_index < in_savings_ptr[saving_offset + after_node].size() ||
4425 out_index < out_savings_ptr[saving_offset + before_node].size()) {
4426 if (StopSearch()) return;
4427 // First determine how to extend the route.
4428 int before_before_node = -1;
4429 int after_after_node = -1;
4430 if (in_index < in_savings_ptr[saving_offset + after_node].size()) {
4431 const Saving& in_saving =
4432 *(in_savings_ptr[saving_offset + after_node][in_index]);
4433 if (out_index < out_savings_ptr[saving_offset + before_node].size()) {
4434 const Saving& out_saving =
4435 *(out_savings_ptr[saving_offset + before_node][out_index]);
4436 if (in_saving.saving < out_saving.saving) {
4437 after_after_node = in_saving.after_node;
4438 } else {
4439 before_before_node = out_saving.before_node;
4440 }
4441 } else {
4442 after_after_node = in_saving.after_node;
4443 }
4444 } else {
4445 before_before_node =
4446 out_savings_ptr[saving_offset + before_node][out_index]
4447 ->before_node;
4448 }
4449 // Extend the route
4450 if (after_after_node != -1) {
4451 DCHECK_EQ(before_before_node, -1);
4452 ++in_index;
4453 // Extending after after_node
4454 if (!Contains(after_after_node)) {
4455 SetNext(after_node, after_after_node, vehicle);
4456 SetNext(after_after_node, end, vehicle);
4457 if (Evaluate(/*commit=*/true).has_value()) {
4458 in_index = 0;
4459 after_node = after_after_node;
4460 }
4461 }
4462 } else {
4463 // Extending before before_node
4464 CHECK_GE(before_before_node, 0);
4465 ++out_index;
4466 if (!Contains(before_before_node)) {
4467 SetNext(start, before_before_node, vehicle);
4468 SetNext(before_before_node, before_node, vehicle);
4469 if (Evaluate(/*commit=*/true).has_value()) {
4470 out_index = 0;
4471 before_node = before_before_node;
4472 }
4473 }
4474 }
4475 }
4476 savings_container_->Update(false);
4477 }
4478}
4479
4480// ParallelSavingsFilteredHeuristic
4481
4482void ParallelSavingsFilteredHeuristic::BuildRoutesFromSavings() {
4483 // Initialize the vehicles of the first/last non start/end nodes served by
4484 // each route.
4485 const int64_t size = model()->Size();
4486 const int vehicles = model()->vehicles();
4487
4488 first_node_on_route_.resize(vehicles, -1);
4489 last_node_on_route_.resize(vehicles, -1);
4490 vehicle_of_first_or_last_node_.resize(size, -1);
4491
4492 for (int vehicle = 0; vehicle < vehicles; vehicle++) {
4493 const int64_t start = model()->Start(vehicle);
4494 const int64_t end = model()->End(vehicle);
4495 if (!Contains(start)) {
4496 continue;
4497 }
4498 int64_t node = Value(start);
4499 if (node != end) {
4500 vehicle_of_first_or_last_node_[node] = vehicle;
4501 first_node_on_route_[vehicle] = node;
4502
4503 int64_t next = Value(node);
4504 while (next != end) {
4505 node = next;
4506 next = Value(node);
4507 }
4508 vehicle_of_first_or_last_node_[node] = vehicle;
4509 last_node_on_route_[vehicle] = node;
4510 }
4511 }
4512
4513 while (savings_container_->HasSaving()) {
4514 if (StopSearch()) return;
4515 const Saving saving = savings_container_->GetSaving();
4516 const int64_t before_node = saving.before_node;
4517 const int64_t after_node = saving.after_node;
4518 const int type = saving.vehicle_type;
4519
4520 if (!Contains(before_node) && !Contains(after_node)) {
4521 // Neither nodes are contained, start a new route.
4522 bool committed = false;
4523
4524 const int vehicle =
4525 StartNewRouteWithBestVehicleOfType(type, before_node, after_node);
4526
4527 if (vehicle >= 0) {
4528 committed = true;
4529 // Store before_node and after_node as first and last nodes of the route
4530 vehicle_of_first_or_last_node_[before_node] = vehicle;
4531 vehicle_of_first_or_last_node_[after_node] = vehicle;
4532 first_node_on_route_[vehicle] = before_node;
4533 last_node_on_route_[vehicle] = after_node;
4534 savings_container_->ReinjectSkippedSavingsStartingAt(after_node);
4535 savings_container_->ReinjectSkippedSavingsEndingAt(before_node);
4536 }
4537 savings_container_->Update(!committed);
4538 continue;
4539 }
4540
4541 if (Contains(before_node) && Contains(after_node)) {
4542 // Merge the two routes if before_node is last and after_node first of its
4543 // route, the two nodes aren't already on the same route, and the vehicle
4544 // types are compatible.
4545 const int v1 = vehicle_of_first_or_last_node_[before_node];
4546 const int64_t last_node = v1 == -1 ? -1 : last_node_on_route_[v1];
4547
4548 const int v2 = vehicle_of_first_or_last_node_[after_node];
4549 const int64_t first_node = v2 == -1 ? -1 : first_node_on_route_[v2];
4550
4551 if (before_node == last_node && after_node == first_node && v1 != v2 &&
4552 vehicle_type_curator_->Type(v1) == vehicle_type_curator_->Type(v2)) {
4553 CHECK_EQ(Value(before_node), model()->End(v1));
4554 CHECK_EQ(Value(model()->Start(v2)), after_node);
4555
4556 // We try merging the two routes.
4557 // TODO(user): Try to use skipped savings to start new routes when
4558 // a vehicle becomes available after a merge (not trivial because it can
4559 // result in an infinite loop).
4560 MergeRoutes(v1, v2, before_node, after_node);
4561 }
4562 }
4563
4564 if (Contains(before_node) && !Contains(after_node)) {
4565 const int vehicle = vehicle_of_first_or_last_node_[before_node];
4566 const int64_t last_node =
4567 vehicle == -1 ? -1 : last_node_on_route_[vehicle];
4568
4569 if (before_node == last_node) {
4570 const int64_t end = model()->End(vehicle);
4571 CHECK_EQ(Value(before_node), end);
4572
4573 const int route_type = vehicle_type_curator_->Type(vehicle);
4574 if (type != route_type) {
4575 // The saving doesn't correspond to the type of the vehicle serving
4576 // before_node. We update the container with the correct type.
4577 savings_container_->UpdateWithType(route_type);
4578 continue;
4579 }
4580
4581 // Try adding after_node on route of before_node.
4582 SetNext(before_node, after_node, vehicle);
4583 SetNext(after_node, end, vehicle);
4584 if (Evaluate(/*commit=*/true).has_value()) {
4585 if (first_node_on_route_[vehicle] != before_node) {
4586 // before_node is no longer the start or end of its route
4587 DCHECK_NE(Value(model()->Start(vehicle)), before_node);
4588 vehicle_of_first_or_last_node_[before_node] = -1;
4589 }
4590 vehicle_of_first_or_last_node_[after_node] = vehicle;
4591 last_node_on_route_[vehicle] = after_node;
4592 savings_container_->ReinjectSkippedSavingsStartingAt(after_node);
4593 }
4594 }
4595 }
4596
4597 if (!Contains(before_node) && Contains(after_node)) {
4598 const int vehicle = vehicle_of_first_or_last_node_[after_node];
4599 const int64_t first_node =
4600 vehicle == -1 ? -1 : first_node_on_route_[vehicle];
4601
4602 if (after_node == first_node) {
4603 const int64_t start = model()->Start(vehicle);
4604 CHECK_EQ(Value(start), after_node);
4605
4606 const int route_type = vehicle_type_curator_->Type(vehicle);
4607 if (type != route_type) {
4608 // The saving doesn't correspond to the type of the vehicle serving
4609 // after_node. We update the container with the correct type.
4610 savings_container_->UpdateWithType(route_type);
4611 continue;
4612 }
4613
4614 // Try adding before_node on route of after_node.
4615 SetNext(before_node, after_node, vehicle);
4616 SetNext(start, before_node, vehicle);
4617 if (Evaluate(/*commit=*/true).has_value()) {
4618 if (last_node_on_route_[vehicle] != after_node) {
4619 // after_node is no longer the start or end of its route
4620 DCHECK_NE(Value(after_node), model()->End(vehicle));
4621 vehicle_of_first_or_last_node_[after_node] = -1;
4622 }
4623 vehicle_of_first_or_last_node_[before_node] = vehicle;
4624 first_node_on_route_[vehicle] = before_node;
4625 savings_container_->ReinjectSkippedSavingsEndingAt(before_node);
4626 }
4627 }
4628 }
4629 savings_container_->Update(/*update_best_saving*/ false);
4630 }
4631}
4632
4633void ParallelSavingsFilteredHeuristic::MergeRoutes(int first_vehicle,
4634 int second_vehicle,
4635 int64_t before_node,
4636 int64_t after_node) {
4637 if (StopSearch()) return;
4638 const int64_t new_first_node = first_node_on_route_[first_vehicle];
4639 DCHECK_EQ(vehicle_of_first_or_last_node_[new_first_node], first_vehicle);
4640 CHECK_EQ(Value(model()->Start(first_vehicle)), new_first_node);
4641 const int64_t new_last_node = last_node_on_route_[second_vehicle];
4642 DCHECK_EQ(vehicle_of_first_or_last_node_[new_last_node], second_vehicle);
4643 CHECK_EQ(Value(new_last_node), model()->End(second_vehicle));
4644
4645 // Select the vehicle with lower fixed cost to merge the routes.
4646 int used_vehicle = first_vehicle;
4647 int unused_vehicle = second_vehicle;
4648 if (model()->GetFixedCostOfVehicle(first_vehicle) >
4649 model()->GetFixedCostOfVehicle(second_vehicle)) {
4650 used_vehicle = second_vehicle;
4651 unused_vehicle = first_vehicle;
4652 }
4653
4654 SetNext(before_node, after_node, used_vehicle);
4655 SetNext(model()->Start(unused_vehicle), model()->End(unused_vehicle),
4656 unused_vehicle);
4657 if (used_vehicle == first_vehicle) {
4658 SetNext(new_last_node, model()->End(used_vehicle), used_vehicle);
4659 } else {
4660 SetNext(model()->Start(used_vehicle), new_first_node, used_vehicle);
4661 }
4662 bool committed = Evaluate(/*commit=*/true).has_value();
4663 if (!committed &&
4664 model()->GetVehicleClassIndexOfVehicle(first_vehicle).value() !=
4665 model()->GetVehicleClassIndexOfVehicle(second_vehicle).value()) {
4666 // Try committing on other vehicle instead.
4667 std::swap(used_vehicle, unused_vehicle);
4668 SetNext(before_node, after_node, used_vehicle);
4669 SetNext(model()->Start(unused_vehicle), model()->End(unused_vehicle),
4670 unused_vehicle);
4671 if (used_vehicle == first_vehicle) {
4672 SetNext(new_last_node, model()->End(used_vehicle), used_vehicle);
4673 } else {
4674 SetNext(model()->Start(used_vehicle), new_first_node, used_vehicle);
4675 }
4676 committed = Evaluate(/*commit=*/true).has_value();
4677 }
4678 if (committed) {
4679 // Make unused_vehicle available
4680 vehicle_type_curator_->ReinjectVehicleOfClass(
4681 unused_vehicle,
4682 model()->GetVehicleClassIndexOfVehicle(unused_vehicle).value(),
4683 model()->GetFixedCostOfVehicle(unused_vehicle));
4684
4685 // Update the first and last nodes on vehicles.
4686 first_node_on_route_[unused_vehicle] = -1;
4687 last_node_on_route_[unused_vehicle] = -1;
4688 vehicle_of_first_or_last_node_[before_node] = -1;
4689 vehicle_of_first_or_last_node_[after_node] = -1;
4690 first_node_on_route_[used_vehicle] = new_first_node;
4691 last_node_on_route_[used_vehicle] = new_last_node;
4692 vehicle_of_first_or_last_node_[new_last_node] = used_vehicle;
4693 vehicle_of_first_or_last_node_[new_first_node] = used_vehicle;
4694 }
4695}
4696
4697// ChristofidesFilteredHeuristic
4698
4700 RoutingModel* model, std::function<bool()> stop_search,
4701 LocalSearchFilterManager* filter_manager, bool use_minimum_matching)
4702 : RoutingFilteredHeuristic(model, std::move(stop_search), filter_manager),
4703 use_minimum_matching_(use_minimum_matching) {}
4704
4705// TODO(user): Support pickup & delivery.
4707 const int size = model()->Size() - model()->vehicles() + 1;
4708 // Node indices for Christofides solver.
4709 // 0: start/end node
4710 // >0: non start/end nodes
4711 // TODO(user): Add robustness to fixed arcs by collapsing them into meta-
4712 // nodes.
4713 std::vector<int> indices(1, 0);
4714 for (int i = 1; i < size; ++i) {
4715 if (!model()->IsStart(i) && !model()->IsEnd(i)) {
4716 indices.push_back(i);
4717 }
4718 }
4719 const int num_cost_classes = model()->GetCostClassesCount();
4720 std::vector<std::vector<int>> path_per_cost_class(num_cost_classes);
4721 std::vector<bool> class_covered(num_cost_classes, false);
4722 for (int vehicle = 0; vehicle < model()->vehicles(); ++vehicle) {
4723 const int64_t cost_class =
4724 model()->GetCostClassIndexOfVehicle(vehicle).value();
4725 if (!class_covered[cost_class]) {
4726 class_covered[cost_class] = true;
4727 const int64_t start = model()->Start(vehicle);
4728 const int64_t end = model()->End(vehicle);
4729 auto cost = [this, &indices, start, end, cost_class](int from, int to) {
4730 DCHECK_LT(from, indices.size());
4731 DCHECK_LT(to, indices.size());
4732 const int from_index = (from == 0) ? start : indices[from];
4733 const int to_index = (to == 0) ? end : indices[to];
4734 const int64_t cost =
4735 model()->GetArcCostForClass(from_index, to_index, cost_class);
4736 // To avoid overflow issues, capping costs at kint64max/2, the maximum
4737 // value supported by MinCostPerfectMatching.
4738 // TODO(user): Investigate if ChristofidesPathSolver should not
4739 // return a status to bail out fast in case of problem.
4740 return std::min(cost, std::numeric_limits<int64_t>::max() / 2);
4741 };
4742 using Cost = decltype(cost);
4744 indices.size(), cost);
4745 if (use_minimum_matching_) {
4746 christofides_solver.SetMatchingAlgorithm(
4748 MatchingAlgorithm::MINIMUM_WEIGHT_MATCHING);
4749 }
4750 if (christofides_solver.Solve()) {
4751 path_per_cost_class[cost_class] =
4752 christofides_solver.TravelingSalesmanPath();
4753 }
4754 }
4755 }
4756 // TODO(user): Investigate if sorting paths per cost improves solutions.
4757 for (int vehicle = 0; vehicle < model()->vehicles(); ++vehicle) {
4758 const int64_t cost_class =
4759 model()->GetCostClassIndexOfVehicle(vehicle).value();
4760 const std::vector<int>& path = path_per_cost_class[cost_class];
4761 if (path.empty()) continue;
4762 DCHECK_EQ(0, path[0]);
4763 DCHECK_EQ(0, path.back());
4764 // Extend route from start.
4765 int prev = GetStartChainEnd(vehicle);
4766 const int end = model()->End(vehicle);
4767 for (int i = 1; i < path.size() - 1 && prev != end; ++i) {
4768 if (StopSearch()) return false;
4769 int next = indices[path[i]];
4770 if (!Contains(next)) {
4771 SetNext(prev, next, vehicle);
4772 SetNext(next, end, vehicle);
4773 if (Evaluate(/*commit=*/true).has_value()) {
4774 prev = next;
4775 }
4776 }
4777 }
4778 }
4780 return Evaluate(/*commit=*/true).has_value();
4781}
4782
4783// Sweep heuristic
4784// TODO(user): Clean up to match other first solution strategies.
4785
4786namespace {
4787struct SweepIndex {
4788 SweepIndex(const int64_t index, const double angle, const double distance)
4789 : index(index), angle(angle), distance(distance) {}
4790 ~SweepIndex() = default;
4791
4792 int64_t index;
4793 double angle;
4794 double distance;
4795};
4796
4797struct SweepIndexSortAngle {
4798 bool operator()(const SweepIndex& node1, const SweepIndex& node2) const {
4799 return (node1.angle < node2.angle);
4800 }
4801} SweepIndexAngleComparator;
4802
4803struct SweepIndexSortDistance {
4804 bool operator()(const SweepIndex& node1, const SweepIndex& node2) const {
4805 return (node1.distance < node2.distance);
4806 }
4807} SweepIndexDistanceComparator;
4808} // namespace
4809
4811 absl::Span<const std::pair<int64_t, int64_t>> points)
4812 : coordinates_(2 * points.size(), 0), sectors_(1) {
4813 for (int64_t i = 0; i < points.size(); ++i) {
4814 coordinates_[2 * i] = points[i].first;
4815 coordinates_[2 * i + 1] = points[i].second;
4816 }
4817}
4818
4819// Splits the space of the indices into sectors and sorts the indices of each
4820// sector with ascending angle from the depot.
4821void SweepArranger::ArrangeIndices(std::vector<int64_t>* indices) {
4822 const double pi_rad = 3.14159265;
4823 // Suppose that the center is at x0, y0.
4824 const int x0 = coordinates_[0];
4825 const int y0 = coordinates_[1];
4826
4827 std::vector<SweepIndex> sweep_indices;
4828 for (int64_t index = 0; index < static_cast<int>(coordinates_.size()) / 2;
4829 ++index) {
4830 const int x = coordinates_[2 * index];
4831 const int y = coordinates_[2 * index + 1];
4832 const double x_delta = x - x0;
4833 const double y_delta = y - y0;
4834 double square_distance = x_delta * x_delta + y_delta * y_delta;
4835 double angle = square_distance == 0 ? 0 : std::atan2(y_delta, x_delta);
4836 angle = angle >= 0 ? angle : 2 * pi_rad + angle;
4837 sweep_indices.push_back(SweepIndex(index, angle, square_distance));
4838 }
4839 absl::c_sort(sweep_indices, SweepIndexDistanceComparator);
4840
4841 const int size = static_cast<int>(sweep_indices.size()) / sectors_;
4842 for (int sector = 0; sector < sectors_; ++sector) {
4843 std::vector<SweepIndex> cluster;
4844 std::vector<SweepIndex>::iterator begin =
4845 sweep_indices.begin() + sector * size;
4846 std::vector<SweepIndex>::iterator end =
4847 sector == sectors_ - 1 ? sweep_indices.end()
4848 : sweep_indices.begin() + (sector + 1) * size;
4849 std::sort(begin, end, SweepIndexAngleComparator);
4850 }
4851 for (const SweepIndex& sweep_index : sweep_indices) {
4852 indices->push_back(sweep_index.index);
4853 }
4854}
4855
4856namespace {
4857
4858struct Link {
4859 Link(std::pair<int, int> link, double value, int vehicle_class,
4860 int64_t start_depot, int64_t end_depot)
4861 : link(link),
4862 value(value),
4863 vehicle_class(vehicle_class),
4864 start_depot(start_depot),
4865 end_depot(end_depot) {}
4866 ~Link() = default;
4867
4868 std::pair<int, int> link;
4869 int64_t value;
4870 int vehicle_class;
4871 int64_t start_depot;
4872 int64_t end_depot;
4873};
4874
4875// The RouteConstructor creates the routes of a VRP instance subject to its
4876// constraints by iterating on a list of arcs appearing in descending order
4877// of priority.
4878// TODO(user): Use the dimension class in this class.
4879// TODO(user): Add support for vehicle-dependent dimension transits.
4880class RouteConstructor {
4881 public:
4882 RouteConstructor(Assignment* const assignment, RoutingModel* const model,
4883 bool check_assignment, int64_t num_indices,
4884 const std::vector<Link>& links_list)
4885 : assignment_(assignment),
4886 model_(model),
4887 check_assignment_(check_assignment),
4888 solver_(model_->solver()),
4889 num_indices_(num_indices),
4890 links_list_(links_list),
4891 nexts_(model_->Nexts()),
4892 in_route_(num_indices_, -1),
4893 final_routes_(),
4894 index_to_chain_index_(num_indices, -1),
4895 index_to_vehicle_class_index_(num_indices, -1) {
4896 {
4897 const std::vector<std::string> dimension_names =
4898 model_->GetAllDimensionNames();
4899 dimensions_.assign(dimension_names.size(), nullptr);
4900 for (int i = 0; i < dimension_names.size(); ++i) {
4901 dimensions_[i] = &model_->GetDimensionOrDie(dimension_names[i]);
4902 }
4903 }
4904 cumuls_.resize(dimensions_.size());
4905 for (std::vector<int64_t>& cumuls : cumuls_) {
4906 cumuls.resize(num_indices_);
4907 }
4908 new_possible_cumuls_.resize(dimensions_.size());
4909 }
4910
4911 ~RouteConstructor() = default;
4912
4913 void Construct() {
4914 model_->solver()->TopPeriodicCheck();
4915 // Initial State: Each order is served by its own vehicle.
4916 for (int index = 0; index < num_indices_; ++index) {
4917 if (!model_->IsStart(index) && !model_->IsEnd(index)) {
4918 routes_.push_back({index});
4919 in_route_[index] = routes_.size() - 1;
4920 }
4921 }
4922
4923 for (const Link& link : links_list_) {
4924 model_->solver()->TopPeriodicCheck();
4925 const int index1 = link.link.first;
4926 const int index2 = link.link.second;
4927 const int vehicle_class = link.vehicle_class;
4928 const int64_t start_depot = link.start_depot;
4929 const int64_t end_depot = link.end_depot;
4930
4931 // Initialisation of cumuls_ if the indices are encountered for first time
4932 if (index_to_vehicle_class_index_[index1] < 0) {
4933 for (int dimension_index = 0; dimension_index < dimensions_.size();
4934 ++dimension_index) {
4935 cumuls_[dimension_index][index1] =
4936 std::max(dimensions_[dimension_index]->GetTransitValue(
4937 start_depot, index1, 0),
4938 dimensions_[dimension_index]->CumulVar(index1)->Min());
4939 }
4940 }
4941 if (index_to_vehicle_class_index_[index2] < 0) {
4942 for (int dimension_index = 0; dimension_index < dimensions_.size();
4943 ++dimension_index) {
4944 cumuls_[dimension_index][index2] =
4945 std::max(dimensions_[dimension_index]->GetTransitValue(
4946 start_depot, index2, 0),
4947 dimensions_[dimension_index]->CumulVar(index2)->Min());
4948 }
4949 }
4950
4951 const int route_index1 = in_route_[index1];
4952 const int route_index2 = in_route_[index2];
4953 const bool merge =
4954 route_index1 >= 0 && route_index2 >= 0 &&
4955 FeasibleMerge(routes_[route_index1], routes_[route_index2], index1,
4956 index2, route_index1, route_index2, vehicle_class,
4957 start_depot, end_depot);
4958 if (Merge(merge, route_index1, route_index2)) {
4959 index_to_vehicle_class_index_[index1] = vehicle_class;
4960 index_to_vehicle_class_index_[index2] = vehicle_class;
4961 }
4962 }
4963
4964 model_->solver()->TopPeriodicCheck();
4965 // Beyond this point not checking limits anymore as the rest of the code is
4966 // linear and that given we managed to build a solution would be ludicrous
4967 // to drop it now.
4968 for (int chain_index = 0; chain_index < chains_.size(); ++chain_index) {
4969 if (!deleted_chains_.contains(chain_index)) {
4970 final_chains_.push_back(chains_[chain_index]);
4971 }
4972 }
4973 absl::c_sort(final_chains_, ChainComparator);
4974 for (int route_index = 0; route_index < routes_.size(); ++route_index) {
4975 if (!deleted_routes_.contains(route_index)) {
4976 final_routes_.push_back(routes_[route_index]);
4977 }
4978 }
4979 absl::c_sort(final_routes_, RouteComparator);
4980
4981 const int extra_vehicles = std::max(
4982 0, static_cast<int>(final_chains_.size()) - model_->vehicles());
4983 // Bind the Start and End of each chain
4984 int chain_index = 0;
4985 for (chain_index = extra_vehicles; chain_index < final_chains_.size();
4986 ++chain_index) {
4987 if (chain_index - extra_vehicles >= model_->vehicles()) {
4988 break;
4989 }
4990 const int start = final_chains_[chain_index].head;
4991 const int end = final_chains_[chain_index].tail;
4992 assignment_->Add(
4993 model_->NextVar(model_->Start(chain_index - extra_vehicles)));
4994 assignment_->SetValue(
4995 model_->NextVar(model_->Start(chain_index - extra_vehicles)), start);
4996 assignment_->Add(nexts_[end]);
4997 assignment_->SetValue(nexts_[end],
4998 model_->End(chain_index - extra_vehicles));
4999 }
5000
5001 // Create the single order routes
5002 for (int route_index = 0; route_index < final_routes_.size();
5003 ++route_index) {
5004 if (chain_index - extra_vehicles >= model_->vehicles()) {
5005 break;
5006 }
5007 DCHECK_LT(route_index, final_routes_.size());
5008 const int head = final_routes_[route_index].front();
5009 const int tail = final_routes_[route_index].back();
5010 if (head == tail && head < model_->Size()) {
5011 assignment_->Add(
5012 model_->NextVar(model_->Start(chain_index - extra_vehicles)));
5013 assignment_->SetValue(
5014 model_->NextVar(model_->Start(chain_index - extra_vehicles)), head);
5015 assignment_->Add(nexts_[tail]);
5016 assignment_->SetValue(nexts_[tail],
5017 model_->End(chain_index - extra_vehicles));
5018 ++chain_index;
5019 }
5020 }
5021
5022 // Unperformed
5023 for (int index = 0; index < model_->Size(); ++index) {
5024 IntVar* const next = nexts_[index];
5025 if (!assignment_->Contains(next)) {
5026 assignment_->Add(next);
5027 if (next->Contains(index)) {
5028 assignment_->SetValue(next, index);
5029 }
5030 }
5031 }
5032 }
5033
5034 private:
5035 enum MergeStatus { FIRST_SECOND, SECOND_FIRST, NO_MERGE };
5036
5037 struct RouteSort {
5038 bool operator()(absl::Span<const int> route1,
5039 absl::Span<const int> route2) const {
5040 return (route1.size() < route2.size());
5041 }
5042 } RouteComparator;
5043
5044 struct Chain {
5045 int head;
5046 int tail;
5047 int nodes;
5048 };
5049
5050 struct ChainSort {
5051 bool operator()(const Chain& chain1, const Chain& chain2) const {
5052 return (chain1.nodes < chain2.nodes);
5053 }
5054 } ChainComparator;
5055
5056 bool Head(int node) const {
5057 return (node == routes_[in_route_[node]].front());
5058 }
5059
5060 bool Tail(int node) const {
5061 return (node == routes_[in_route_[node]].back());
5062 }
5063
5064 bool FeasibleRoute(const std::vector<int>& route, int64_t route_cumul,
5065 int dimension_index) {
5066 const RoutingDimension& dimension = *dimensions_[dimension_index];
5067 std::vector<int>::const_iterator it = route.begin();
5068 int64_t cumul = route_cumul;
5069 while (it != route.end()) {
5070 const int previous = *it;
5071 const int64_t cumul_previous = cumul;
5072 gtl::InsertOrDie(&(new_possible_cumuls_[dimension_index]), previous,
5073 cumul_previous);
5074 ++it;
5075 if (it == route.end()) {
5076 return true;
5077 }
5078 const int next = *it;
5079 int64_t available_from_previous =
5080 cumul_previous + dimension.GetTransitValue(previous, next, 0);
5081 int64_t available_cumul_next =
5082 std::max(cumuls_[dimension_index][next], available_from_previous);
5083
5084 const int64_t slack = available_cumul_next - available_from_previous;
5085 if (slack > dimension.SlackVar(previous)->Max()) {
5086 available_cumul_next =
5087 available_from_previous + dimension.SlackVar(previous)->Max();
5088 }
5089
5090 if (available_cumul_next > dimension.CumulVar(next)->Max()) {
5091 return false;
5092 }
5093 if (available_cumul_next <= cumuls_[dimension_index][next]) {
5094 return true;
5095 }
5096 cumul = available_cumul_next;
5097 }
5098 return true;
5099 }
5100
5101 bool CheckRouteConnection(absl::Span<const int> route1,
5102 const std::vector<int>& route2, int dimension_index,
5103 int64_t /*start_depot*/, int64_t end_depot) {
5104 const int tail1 = route1.back();
5105 const int head2 = route2.front();
5106 const int tail2 = route2.back();
5107 const RoutingDimension& dimension = *dimensions_[dimension_index];
5108 int non_depot_node = -1;
5109 for (int node = 0; node < num_indices_; ++node) {
5110 if (!model_->IsStart(node) && !model_->IsEnd(node)) {
5111 non_depot_node = node;
5112 break;
5113 }
5114 }
5115 CHECK_GE(non_depot_node, 0);
5116 const int64_t depot_threshold =
5117 std::max(dimension.SlackVar(non_depot_node)->Max(),
5118 dimension.CumulVar(non_depot_node)->Max());
5119
5120 int64_t available_from_tail1 = cumuls_[dimension_index][tail1] +
5121 dimension.GetTransitValue(tail1, head2, 0);
5122 int64_t new_available_cumul_head2 =
5123 std::max(cumuls_[dimension_index][head2], available_from_tail1);
5124
5125 const int64_t slack = new_available_cumul_head2 - available_from_tail1;
5126 if (slack > dimension.SlackVar(tail1)->Max()) {
5127 new_available_cumul_head2 =
5128 available_from_tail1 + dimension.SlackVar(tail1)->Max();
5129 }
5130
5131 bool feasible_route = true;
5132 if (new_available_cumul_head2 > dimension.CumulVar(head2)->Max()) {
5133 return false;
5134 }
5135 if (new_available_cumul_head2 <= cumuls_[dimension_index][head2]) {
5136 return true;
5137 }
5138
5139 feasible_route =
5140 FeasibleRoute(route2, new_available_cumul_head2, dimension_index);
5141 const int64_t new_possible_cumul_tail2 =
5142 new_possible_cumuls_[dimension_index].contains(tail2)
5143 ? new_possible_cumuls_[dimension_index][tail2]
5144 : cumuls_[dimension_index][tail2];
5145
5146 if (!feasible_route || (new_possible_cumul_tail2 +
5147 dimension.GetTransitValue(tail2, end_depot, 0) >
5148 depot_threshold)) {
5149 return false;
5150 }
5151 return true;
5152 }
5153
5154 bool FeasibleMerge(const std::vector<int>& route1,
5155 const std::vector<int>& route2, int node1, int node2,
5156 int route_index1, int route_index2, int vehicle_class,
5157 int64_t start_depot, int64_t end_depot) {
5158 if ((route_index1 == route_index2) || !(Tail(node1) && Head(node2))) {
5159 return false;
5160 }
5161
5162 // Vehicle Class Check
5163 if (!((index_to_vehicle_class_index_[node1] == -1 &&
5164 index_to_vehicle_class_index_[node2] == -1) ||
5165 (index_to_vehicle_class_index_[node1] == vehicle_class &&
5166 index_to_vehicle_class_index_[node2] == -1) ||
5167 (index_to_vehicle_class_index_[node1] == -1 &&
5168 index_to_vehicle_class_index_[node2] == vehicle_class) ||
5169 (index_to_vehicle_class_index_[node1] == vehicle_class &&
5170 index_to_vehicle_class_index_[node2] == vehicle_class))) {
5171 return false;
5172 }
5173
5174 // Check Route1 -> Route2 connection for every dimension
5175 bool merge = true;
5176 for (int dimension_index = 0; dimension_index < dimensions_.size();
5177 ++dimension_index) {
5178 new_possible_cumuls_[dimension_index].clear();
5179 merge = merge && CheckRouteConnection(route1, route2, dimension_index,
5180 start_depot, end_depot);
5181 if (!merge) {
5182 return false;
5183 }
5184 }
5185 return true;
5186 }
5187
5188 bool CheckTempAssignment(Assignment* const temp_assignment,
5189 int new_chain_index, int old_chain_index, int head1,
5190 int tail1, int head2, int tail2) {
5191 // TODO(user): If the chain index is greater than the number of vehicles,
5192 // use another vehicle instead.
5193 if (new_chain_index >= model_->vehicles()) return false;
5194 const int start = head1;
5195 temp_assignment->Add(model_->NextVar(model_->Start(new_chain_index)));
5196 temp_assignment->SetValue(model_->NextVar(model_->Start(new_chain_index)),
5197 start);
5198 temp_assignment->Add(nexts_[tail1]);
5199 temp_assignment->SetValue(nexts_[tail1], head2);
5200 temp_assignment->Add(nexts_[tail2]);
5201 temp_assignment->SetValue(nexts_[tail2], model_->End(new_chain_index));
5202 for (int chain_index = 0; chain_index < chains_.size(); ++chain_index) {
5203 if ((chain_index != new_chain_index) &&
5204 (chain_index != old_chain_index) &&
5205 (!deleted_chains_.contains(chain_index))) {
5206 const int start = chains_[chain_index].head;
5207 const int end = chains_[chain_index].tail;
5208 temp_assignment->Add(model_->NextVar(model_->Start(chain_index)));
5209 temp_assignment->SetValue(model_->NextVar(model_->Start(chain_index)),
5210 start);
5211 temp_assignment->Add(nexts_[end]);
5212 temp_assignment->SetValue(nexts_[end], model_->End(chain_index));
5213 }
5214 }
5215 return solver_->Solve(solver_->MakeRestoreAssignment(temp_assignment));
5216 }
5217
5218 bool UpdateAssignment(absl::Span<const int> route1,
5219 absl::Span<const int> route2) {
5220 bool feasible = true;
5221 const int head1 = route1.front();
5222 const int tail1 = route1.back();
5223 const int head2 = route2.front();
5224 const int tail2 = route2.back();
5225 const int chain_index1 = index_to_chain_index_[head1];
5226 const int chain_index2 = index_to_chain_index_[head2];
5227 if (chain_index1 < 0 && chain_index2 < 0) {
5228 const int chain_index = chains_.size();
5229 if (check_assignment_) {
5230 Assignment* const temp_assignment =
5231 solver_->MakeAssignment(assignment_);
5232 feasible = CheckTempAssignment(temp_assignment, chain_index, -1, head1,
5233 tail1, head2, tail2);
5234 }
5235 if (feasible) {
5236 Chain chain;
5237 chain.head = head1;
5238 chain.tail = tail2;
5239 chain.nodes = 2;
5240 index_to_chain_index_[head1] = chain_index;
5241 index_to_chain_index_[tail2] = chain_index;
5242 chains_.push_back(chain);
5243 }
5244 } else if (chain_index1 >= 0 && chain_index2 < 0) {
5245 if (check_assignment_) {
5246 Assignment* const temp_assignment =
5247 solver_->MakeAssignment(assignment_);
5248 feasible =
5249 CheckTempAssignment(temp_assignment, chain_index1, chain_index2,
5250 head1, tail1, head2, tail2);
5251 }
5252 if (feasible) {
5253 index_to_chain_index_[tail2] = chain_index1;
5254 chains_[chain_index1].head = head1;
5255 chains_[chain_index1].tail = tail2;
5256 ++chains_[chain_index1].nodes;
5257 }
5258 } else if (chain_index1 < 0 && chain_index2 >= 0) {
5259 if (check_assignment_) {
5260 Assignment* const temp_assignment =
5261 solver_->MakeAssignment(assignment_);
5262 feasible =
5263 CheckTempAssignment(temp_assignment, chain_index2, chain_index1,
5264 head1, tail1, head2, tail2);
5265 }
5266 if (feasible) {
5267 index_to_chain_index_[head1] = chain_index2;
5268 chains_[chain_index2].head = head1;
5269 chains_[chain_index2].tail = tail2;
5270 ++chains_[chain_index2].nodes;
5271 }
5272 } else {
5273 if (check_assignment_) {
5274 Assignment* const temp_assignment =
5275 solver_->MakeAssignment(assignment_);
5276 feasible =
5277 CheckTempAssignment(temp_assignment, chain_index1, chain_index2,
5278 head1, tail1, head2, tail2);
5279 }
5280 if (feasible) {
5281 index_to_chain_index_[tail2] = chain_index1;
5282 chains_[chain_index1].head = head1;
5283 chains_[chain_index1].tail = tail2;
5284 chains_[chain_index1].nodes += chains_[chain_index2].nodes;
5285 deleted_chains_.insert(chain_index2);
5286 }
5287 }
5288 if (feasible) {
5289 assignment_->Add(nexts_[tail1]);
5290 assignment_->SetValue(nexts_[tail1], head2);
5291 }
5292 return feasible;
5293 }
5294
5295 bool Merge(bool merge, int index1, int index2) {
5296 if (merge) {
5297 if (UpdateAssignment(routes_[index1], routes_[index2])) {
5298 // Connection Route1 -> Route2
5299 for (const int node : routes_[index2]) {
5300 in_route_[node] = index1;
5301 routes_[index1].push_back(node);
5302 }
5303 for (int dimension_index = 0; dimension_index < dimensions_.size();
5304 ++dimension_index) {
5305 for (const std::pair<int, int64_t> new_possible_cumul :
5306 new_possible_cumuls_[dimension_index]) {
5307 cumuls_[dimension_index][new_possible_cumul.first] =
5308 new_possible_cumul.second;
5309 }
5310 }
5311 deleted_routes_.insert(index2);
5312 return true;
5313 }
5314 }
5315 return false;
5316 }
5317
5318 Assignment* const assignment_;
5319 RoutingModel* const model_;
5320 const bool check_assignment_;
5321 Solver* const solver_;
5322 const int64_t num_indices_;
5323 const std::vector<Link> links_list_;
5324 std::vector<IntVar*> nexts_;
5325 std::vector<const RoutingDimension*> dimensions_; // Not owned.
5326 std::vector<std::vector<int64_t>> cumuls_;
5327 std::vector<absl::flat_hash_map<int, int64_t>> new_possible_cumuls_;
5328 std::vector<std::vector<int>> routes_;
5329 std::vector<int> in_route_;
5330 absl::flat_hash_set<int> deleted_routes_;
5331 std::vector<std::vector<int>> final_routes_;
5332 std::vector<Chain> chains_;
5333 absl::flat_hash_set<int> deleted_chains_;
5334 std::vector<Chain> final_chains_;
5335 std::vector<int> index_to_chain_index_;
5336 std::vector<int> index_to_vehicle_class_index_;
5337};
5338
5339// Decision Builder building a first solution based on Sweep heuristic for
5340// Vehicle Routing Problem.
5341// Suitable only when distance is considered as the cost.
5342class SweepBuilder : public DecisionBuilder {
5343 public:
5344 SweepBuilder(RoutingModel* const model, bool check_assignment)
5345 : model_(model), check_assignment_(check_assignment) {}
5346 ~SweepBuilder() override = default;
5347
5348 Decision* Next(Solver* const solver) override {
5349 // Setup the model of the instance for the Sweep Algorithm
5350 ModelSetup();
5351
5352 // Build the assignment routes for the model
5353 Assignment* const assignment = solver->MakeAssignment();
5354 route_constructor_ = std::make_unique<RouteConstructor>(
5355 assignment, model_, check_assignment_, num_indices_, links_);
5356 // This call might cause backtracking if the search limit is reached.
5357 route_constructor_->Construct();
5358 route_constructor_.reset(nullptr);
5359 // This call might cause backtracking if the solution is not feasible.
5360 assignment->Restore();
5361
5362 return nullptr;
5363 }
5364
5365 private:
5366 void ModelSetup() {
5367 const int depot = model_->GetDepot();
5368 num_indices_ = model_->Size() + model_->vehicles();
5369 if (absl::GetFlag(FLAGS_sweep_sectors) > 0 &&
5370 absl::GetFlag(FLAGS_sweep_sectors) < num_indices_) {
5371 model_->sweep_arranger()->SetSectors(absl::GetFlag(FLAGS_sweep_sectors));
5372 }
5373 std::vector<int64_t> indices;
5374 model_->sweep_arranger()->ArrangeIndices(&indices);
5375 for (int i = 0; i < indices.size() - 1; ++i) {
5376 const int64_t first = indices[i];
5377 const int64_t second = indices[i + 1];
5378 if ((model_->IsStart(first) || !model_->IsEnd(first)) &&
5379 (model_->IsStart(second) || !model_->IsEnd(second))) {
5380 if (first != depot && second != depot) {
5381 links_.push_back(
5382 Link(std::make_pair(first, second), 0, 0, depot, depot));
5383 }
5384 }
5385 }
5386 }
5387
5388 RoutingModel* const model_;
5389 std::unique_ptr<RouteConstructor> route_constructor_;
5390 const bool check_assignment_;
5391 int64_t num_indices_;
5392 std::vector<Link> links_;
5393};
5394} // namespace
5395
5397 bool check_assignment) {
5398 return model->solver()->RevAlloc(new SweepBuilder(model, check_assignment));
5399}
5400
5401// AllUnperformed
5402
5403namespace {
5404// Decision builder to build a solution with all nodes inactive. It does no
5405// branching and may fail if some nodes cannot be made inactive.
5406
5407class AllUnperformed : public DecisionBuilder {
5408 public:
5409 // Does not take ownership of model.
5410 explicit AllUnperformed(RoutingModel* const model) : model_(model) {}
5411 ~AllUnperformed() override = default;
5412 Decision* Next(Solver* const /*solver*/) override {
5413 // Solver::(Un)FreezeQueue is private, passing through the public API
5414 // on PropagationBaseObject.
5415 model_->CostVar()->FreezeQueue();
5416 for (int i = 0; i < model_->Size(); ++i) {
5417 if (!model_->IsStart(i)) {
5418 model_->ActiveVar(i)->SetValue(0);
5419 }
5420 }
5421 model_->CostVar()->UnfreezeQueue();
5422 return nullptr;
5423 }
5424
5425 private:
5426 RoutingModel* const model_;
5427};
5428} // namespace
5429
5431 return model->solver()->RevAlloc(new AllUnperformed(model));
5432}
5433
5434namespace {
5435// The description is in routing.h:MakeGuidedSlackFinalizer
5436class GuidedSlackFinalizer : public DecisionBuilder {
5437 public:
5438 GuidedSlackFinalizer(const RoutingDimension* dimension, RoutingModel* model,
5439 std::function<int64_t(int64_t)> initializer);
5440
5441 // This type is neither copyable nor movable.
5442 GuidedSlackFinalizer(const GuidedSlackFinalizer&) = delete;
5443 GuidedSlackFinalizer& operator=(const GuidedSlackFinalizer&) = delete;
5444
5445 Decision* Next(Solver* solver) override;
5446
5447 private:
5448 int64_t SelectValue(int64_t index);
5449 int64_t ChooseVariable();
5450
5451 const RoutingDimension* const dimension_;
5452 RoutingModel* const model_;
5453 const std::function<int64_t(int64_t)> initializer_;
5454 RevArray<bool> is_initialized_;
5455 std::vector<int64_t> initial_values_;
5456 Rev<int64_t> current_index_;
5457 Rev<int64_t> current_route_;
5458 RevArray<int64_t> last_delta_used_;
5459};
5460
5461GuidedSlackFinalizer::GuidedSlackFinalizer(
5462 const RoutingDimension* dimension, RoutingModel* model,
5463 std::function<int64_t(int64_t)> initializer)
5464 : dimension_(ABSL_DIE_IF_NULL(dimension)),
5465 model_(ABSL_DIE_IF_NULL(model)),
5466 initializer_(std::move(initializer)),
5467 is_initialized_(dimension->slacks().size(), false),
5468 initial_values_(dimension->slacks().size(),
5469 std::numeric_limits<int64_t>::min()),
5470 current_index_(model_->Start(0)),
5471 current_route_(0),
5472 last_delta_used_(dimension->slacks().size(), 0) {}
5473
5474Decision* GuidedSlackFinalizer::Next(Solver* solver) {
5475 CHECK_EQ(solver, model_->solver());
5476 const int node_idx = ChooseVariable();
5477 CHECK(node_idx == -1 ||
5478 (node_idx >= 0 && node_idx < dimension_->slacks().size()));
5479 if (node_idx != -1) {
5480 if (!is_initialized_[node_idx]) {
5481 initial_values_[node_idx] = initializer_(node_idx);
5482 is_initialized_.SetValue(solver, node_idx, true);
5483 }
5484 const int64_t value = SelectValue(node_idx);
5485 IntVar* const slack_variable = dimension_->SlackVar(node_idx);
5486 return solver->MakeAssignVariableValue(slack_variable, value);
5487 }
5488 return nullptr;
5489}
5490
5491int64_t GuidedSlackFinalizer::SelectValue(int64_t index) {
5492 const IntVar* const slack_variable = dimension_->SlackVar(index);
5493 const int64_t center = initial_values_[index];
5494 const int64_t max_delta =
5495 std::max(center - slack_variable->Min(), slack_variable->Max() - center) +
5496 1;
5497 int64_t delta = last_delta_used_[index];
5498
5499 // The sequence of deltas is 0, 1, -1, 2, -2 ...
5500 // Only the values inside the domain of variable are returned.
5501 while (std::abs(delta) < max_delta &&
5502 !slack_variable->Contains(center + delta)) {
5503 if (delta > 0) {
5504 delta = -delta;
5505 } else {
5506 delta = -delta + 1;
5507 }
5508 }
5509 last_delta_used_.SetValue(model_->solver(), index, delta);
5510 return center + delta;
5511}
5512
5513int64_t GuidedSlackFinalizer::ChooseVariable() {
5514 int64_t int_current_node = current_index_.Value();
5515 int64_t int_current_route = current_route_.Value();
5516
5517 while (int_current_route < model_->vehicles()) {
5518 while (!model_->IsEnd(int_current_node) &&
5519 dimension_->SlackVar(int_current_node)->Bound()) {
5520 int_current_node = model_->NextVar(int_current_node)->Value();
5521 }
5522 if (!model_->IsEnd(int_current_node)) {
5523 break;
5524 }
5525 int_current_route += 1;
5526 if (int_current_route < model_->vehicles()) {
5527 int_current_node = model_->Start(int_current_route);
5528 }
5529 }
5530
5531 CHECK(int_current_route == model_->vehicles() ||
5532 !dimension_->SlackVar(int_current_node)->Bound());
5533 current_index_.SetValue(model_->solver(), int_current_node);
5534 current_route_.SetValue(model_->solver(), int_current_route);
5535 if (int_current_route < model_->vehicles()) {
5536 return int_current_node;
5537 } else {
5538 return -1;
5539 }
5540}
5541} // namespace
5542
5543DecisionBuilder* RoutingModel::MakeGuidedSlackFinalizer(
5544 const RoutingDimension* dimension,
5545 std::function<int64_t(int64_t)> initializer) {
5546 return solver_->RevAlloc(
5547 new GuidedSlackFinalizer(dimension, this, std::move(initializer)));
5548}
5549
5550int64_t RoutingDimension::ShortestTransitionSlack(int64_t node) const {
5551 CHECK_EQ(base_dimension_, this);
5552 CHECK(!model_->IsEnd(node));
5553 // Recall that the model is cumul[i+1] = cumul[i] + transit[i] + slack[i]. Our
5554 // aim is to find a value for slack[i] such that cumul[i+1] + transit[i+1] is
5555 // minimized.
5556 const int64_t next = model_->NextVar(node)->Value();
5557 if (model_->IsEnd(next)) {
5558 return SlackVar(node)->Min();
5559 }
5560 const int64_t next_next = model_->NextVar(next)->Value();
5561 const int64_t serving_vehicle = model_->VehicleVar(node)->Value();
5562 CHECK_EQ(serving_vehicle, model_->VehicleVar(next)->Value());
5563 const RoutingModel::StateDependentTransit transit_from_next =
5564 model_->StateDependentTransitCallback(
5565 state_dependent_class_evaluators_
5566 [state_dependent_vehicle_to_class_[serving_vehicle]])(next,
5567 next_next);
5568 // We have that transit[i+1] is a function of cumul[i+1].
5569 const int64_t next_cumul_min = CumulVar(next)->Min();
5570 const int64_t next_cumul_max = CumulVar(next)->Max();
5571 const int64_t optimal_next_cumul =
5572 transit_from_next.transit_plus_identity->RangeMinArgument(
5573 next_cumul_min, next_cumul_max + 1);
5574 // A few checks to make sure we're on the same page.
5575 DCHECK_LE(next_cumul_min, optimal_next_cumul);
5576 DCHECK_LE(optimal_next_cumul, next_cumul_max);
5577 // optimal_next_cumul = cumul + transit + optimal_slack, so
5578 // optimal_slack = optimal_next_cumul - cumul - transit.
5579 // In the current implementation TransitVar(i) = transit[i] + slack[i], so we
5580 // have to find the transit from the evaluators.
5581 const int64_t current_cumul = CumulVar(node)->Value();
5582 const int64_t current_state_independent_transit = model_->TransitCallback(
5583 class_evaluators_[vehicle_to_class_[serving_vehicle]])(node, next);
5584 const int64_t current_state_dependent_transit =
5585 model_
5586 ->StateDependentTransitCallback(
5587 state_dependent_class_evaluators_
5588 [state_dependent_vehicle_to_class_[serving_vehicle]])(node,
5589 next)
5590 .transit->Query(current_cumul);
5591 const int64_t optimal_slack = optimal_next_cumul - current_cumul -
5592 current_state_independent_transit -
5593 current_state_dependent_transit;
5594 CHECK_LE(SlackVar(node)->Min(), optimal_slack);
5595 CHECK_LE(optimal_slack, SlackVar(node)->Max());
5596 return optimal_slack;
5597}
5598
5599namespace {
5600class GreedyDescentLSOperator : public LocalSearchOperator {
5601 public:
5602 explicit GreedyDescentLSOperator(std::vector<IntVar*> variables);
5603
5604 // This type is neither copyable nor movable.
5605 GreedyDescentLSOperator(const GreedyDescentLSOperator&) = delete;
5606 GreedyDescentLSOperator& operator=(const GreedyDescentLSOperator&) = delete;
5607
5608 bool MakeNextNeighbor(Assignment* delta, Assignment* deltadelta) override;
5609 void Start(const Assignment* assignment) override;
5610
5611 private:
5612 int64_t FindMaxDistanceToDomain(const Assignment* assignment);
5613
5614 const std::vector<IntVar*> variables_;
5615 const Assignment* center_;
5616 int64_t current_step_;
5617 // The deltas are returned in this order:
5618 // (current_step_, 0, ... 0), (-current_step_, 0, ... 0),
5619 // (0, current_step_, ... 0), (0, -current_step_, ... 0),
5620 // ...
5621 // (0, ... 0, current_step_), (0, ... 0, -current_step_).
5622 // current_direction_ keeps track what was the last returned delta.
5623 int64_t current_direction_;
5624};
5625
5626GreedyDescentLSOperator::GreedyDescentLSOperator(std::vector<IntVar*> variables)
5627 : variables_(std::move(variables)),
5628 center_(nullptr),
5629 current_step_(0),
5630 current_direction_(0) {}
5631
5632bool GreedyDescentLSOperator::MakeNextNeighbor(Assignment* delta,
5633 Assignment* /*deltadelta*/) {
5634 static const int64_t sings[] = {1, -1};
5635 for (; 1 <= current_step_; current_step_ /= 2) {
5636 for (; current_direction_ < 2 * variables_.size();) {
5637 const int64_t variable_idx = current_direction_ / 2;
5638 IntVar* const variable = variables_[variable_idx];
5639 const int64_t sign_index = current_direction_ % 2;
5640 const int64_t sign = sings[sign_index];
5641 const int64_t offset = sign * current_step_;
5642 const int64_t new_value = center_->Value(variable) + offset;
5643 ++current_direction_;
5644 if (variable->Contains(new_value)) {
5645 delta->Add(variable);
5646 delta->SetValue(variable, new_value);
5647 return true;
5648 }
5649 }
5650 current_direction_ = 0;
5651 }
5652 return false;
5653}
5654
5655void GreedyDescentLSOperator::Start(const Assignment* assignment) {
5656 CHECK(assignment != nullptr);
5657 current_step_ = FindMaxDistanceToDomain(assignment);
5658 center_ = assignment;
5659}
5660
5661int64_t GreedyDescentLSOperator::FindMaxDistanceToDomain(
5662 const Assignment* assignment) {
5663 int64_t result = std::numeric_limits<int64_t>::min();
5664 for (const IntVar* const var : variables_) {
5665 result = std::max(result, std::abs(var->Max() - assignment->Value(var)));
5666 result = std::max(result, std::abs(var->Min() - assignment->Value(var)));
5667 }
5668 return result;
5669}
5670} // namespace
5671
5672std::unique_ptr<LocalSearchOperator> RoutingModel::MakeGreedyDescentLSOperator(
5673 std::vector<IntVar*> variables) {
5674 return std::unique_ptr<LocalSearchOperator>(
5675 new GreedyDescentLSOperator(std::move(variables)));
5676}
5677
5678DecisionBuilder* RoutingModel::MakeSelfDependentDimensionFinalizer(
5679 const RoutingDimension* dimension) {
5680 CHECK(dimension != nullptr);
5681 CHECK(dimension->base_dimension() == dimension);
5682 DecisionBuilder* const guided_finalizer =
5683 MakeGuidedSlackFinalizer(dimension, [dimension](int64_t index) {
5684 return dimension->ShortestTransitionSlack(index);
5685 });
5686 DecisionBuilder* const slacks_finalizer =
5687 solver_->MakeSolveOnce(guided_finalizer);
5688 std::vector<IntVar*> start_cumuls(vehicles_, nullptr);
5689 for (int64_t vehicle_idx = 0; vehicle_idx < vehicles_; ++vehicle_idx) {
5690 start_cumuls[vehicle_idx] = dimension->CumulVar(Start(vehicle_idx));
5691 }
5692 LocalSearchOperator* const hill_climber =
5693 solver_->RevAlloc(new GreedyDescentLSOperator(start_cumuls));
5694 LocalSearchPhaseParameters* const parameters =
5695 solver_->MakeLocalSearchPhaseParameters(CostVar(), hill_climber,
5696 slacks_finalizer);
5697 Assignment* const first_solution = solver_->MakeAssignment();
5698 first_solution->Add(start_cumuls);
5699 for (IntVar* const cumul : start_cumuls) {
5700 first_solution->SetValue(cumul, cumul->Min());
5701 }
5702 DecisionBuilder* const finalizer =
5703 solver_->MakeLocalSearchPhase(first_solution, parameters);
5704 return finalizer;
5705}
5706} // namespace operations_research
bool Contains(const T *val) const
const E & Element(const V *const var) const
const E * ElementPtrOrNull(const V *const var) const
void Copy(const Assignment *assignment)
AssignmentContainer< IntVar, IntVarElement > IntContainer
const IntContainer & IntVarContainer() const
bool RemoveItemFromBin(int item, int bin)
Removes item from bin, return whether the bin is feasible.
bool CheckAdditionFeasibility(int item, int bin) const
Checks whether adding item(s) is feasible w.r.t. dimensions.
int64_t TotalCost() const
Returns the total cost incurred by violating soft costs.
bool AddItemToBin(int item, int bin)
IndexType size() const
Returns how many bits this Bitset64 can hold.
Definition bitset.h:464
void Set(IndexType i)
Sets the bit at position i to 1.
Definition bitset.h:544
bool BuildSolutionInternal() override
Virtual method to redefine how to build a solution.
CheapestAdditionFilteredHeuristic(RoutingModel *model, std::function< bool()> stop_search, LocalSearchFilterManager *filter_manager)
CheapestAdditionFilteredHeuristic.
int64_t GetInsertionCostForNodeAtPosition(int64_t node_to_insert, int64_t insert_after, int64_t insert_before, int vehicle) const
std::function< int64_t(int64_t, int64_t, int64_t)> evaluator_
std::vector< std::vector< StartEndValue > > ComputeStartEndDistanceForVehicles(absl::Span< const int > vehicles)
void InitializeSeedQueue(std::vector< std::vector< StartEndValue > > *start_end_distances_per_node, SeedQueue *sq)
CheapestInsertionFilteredHeuristic(RoutingModel *model, std::function< bool()> stop_search, std::function< int64_t(int64_t, int64_t, int64_t)> evaluator, std::function< int64_t(int64_t)> penalty_evaluator, LocalSearchFilterManager *filter_manager)
Takes ownership of evaluator.
void AppendInsertionPositionsAfter(int64_t node_to_insert, int64_t start, int64_t next_after_start, int vehicle, bool ignore_cost, std::vector< NodeInsertion > *node_insertions)
int64_t GetUnperformedValue(int64_t node_to_insert) const
void InsertBetween(int64_t node, int64_t predecessor, int64_t successor, int vehicle=-1)
void AddSeedNodeToQueue(int node, std::vector< StartEndValue > *start_end_distances, SeedQueue *sq)
clang-format on
ChristofidesFilteredHeuristic(RoutingModel *model, std::function< bool()> stop_search, LocalSearchFilterManager *filter_manager, bool use_minimum_matching)
ChristofidesFilteredHeuristic.
void SetMatchingAlgorithm(MatchingAlgorithm matching)
std::vector< NodeIndex > TravelingSalesmanPath()
Returns the approximate TSP tour.
ComparatorCheapestAdditionFilteredHeuristic(RoutingModel *model, std::function< bool()> stop_search, Solver::VariableValueComparator comparator, LocalSearchFilterManager *filter_manager)
Takes ownership of evaluator.
EvaluatorCheapestAdditionFilteredHeuristic(RoutingModel *model, std::function< bool()> stop_search, std::function< int64_t(int64_t, int64_t)> evaluator, LocalSearchFilterManager *filter_manager)
Takes ownership of evaluator.
void PushInsertion(int64_t node, int64_t insert_after, int vehicle, int bucket, int64_t value)
GlobalCheapestInsertionFilteredHeuristic(RoutingModel *model, std::function< bool()> stop_search, std::function< int64_t(int64_t, int64_t, int64_t)> evaluator, std::function< int64_t(int64_t)> penalty_evaluator, LocalSearchFilterManager *filter_manager, GlobalCheapestInsertionParameters parameters)
Takes ownership of evaluators.
bool BuildSolutionInternal() override
Virtual method to redefine how to build a solution.
void AddInsertionSequence(int vehicle, std::initializer_list< Insertion > insertion_sequence)
void AppendPickupDeliveryMultitourInsertions(int pickup, int delivery, int vehicle, const std::vector< int > &path, const std::vector< bool > &path_node_is_pickup, const std::vector< bool > &path_node_is_delivery, InsertionSequenceContainer &insertions)
virtual bool Bound() const
Returns true if the min and the max of the expression are equal.
virtual int64_t Min() const =0
int64_t number_of_decisions() const
Returns statistics from its underlying heuristic.
std::string DebugString() const override
-------— Decision Builder -------—
IntVarFilteredDecisionBuilder(std::unique_ptr< IntVarFilteredHeuristic > heuristic)
— First solution decision builder —
bool HasSecondaryVars() const
Returns true if there are secondary variables.
void ResetSolution()
Resets the data members for a new solution.
IntVarFilteredHeuristic(Solver *solver, const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, LocalSearchFilterManager *filter_manager)
— First solution heuristics —
virtual bool BuildSolutionInternal()=0
Virtual method to redefine how to build a solution.
bool Contains(int64_t index) const
Returns true if the variable of index 'index' is in the current solution.
virtual void Initialize()
Initialize the heuristic; called before starting to build a new solution.
int64_t SecondaryVarIndex(int64_t index) const
Returns the index of a secondary var.
void SetValue(int64_t index, int64_t value)
virtual bool InitializeSolution()
Virtual method to initialize the solution.
const std::vector< int > & delta_indices() const
Returns the indices of the nodes currently in the insertion delta.
IntVar * Var(int64_t index) const
Returns the variable of index 'index'.
void SynchronizeFilters()
Synchronizes filters with an assignment (the current solution).
std::optional< int64_t > Evaluate(bool commit, bool ignore_upper_bound=false, bool update_upper_bound=true)
bool IsSecondaryVar(int64_t index) const
Returns true if 'index' is a secondary variable index.
virtual int64_t Value() const =0
void Initialize() override
Initialize the heuristic; called before starting to build a new solution.
LocalCheapestInsertionFilteredHeuristic(RoutingModel *model, std::function< bool()> stop_search, std::function< int64_t(int64_t, int64_t, int64_t)> evaluator, RoutingSearchParameters::PairInsertionStrategy pair_insertion_strategy, std::vector< RoutingSearchParameters::InsertionSortingProperty > insertion_sorting_properties, LocalSearchFilterManager *filter_manager, bool use_first_solution_hint, BinCapacities *bin_capacities=nullptr, std::function< bool(const std::vector< RoutingModel::VariableValuePair > &, std::vector< RoutingModel::VariableValuePair > *)> optimize_on_insertion=nullptr)
Takes ownership of evaluator.
bool BuildSolutionInternal() override
Virtual method to redefine how to build a solution.
bool Accept(LocalSearchMonitor *monitor, const Assignment *delta, const Assignment *deltadelta, int64_t objective_min, int64_t objective_max)
-------— Local Search Phase Parameters -------—
static IntOut SafeRound(FloatIn x)
Definition mathutil.h:234
int GetEndChainStart(int vehicle) const
Returns the start of the end chain of vehicle,.
Assignment * BuildSolutionFromRoutes(const std::function< int64_t(int64_t)> &next_accessor)
Builds a solution starting from the routes formed by the next accessor.
void SetNext(int64_t node, int64_t next, int vehicle)
bool MakeUnassignedNodesUnperformed()
Make all unassigned nodes unperformed, always returns true.
RoutingFilteredHeuristic(RoutingModel *model, std::function< bool()> stop_search, LocalSearchFilterManager *filter_manager)
RoutingFilteredHeuristic.
int GetStartChainEnd(int vehicle) const
Returns the end of the start chain of vehicle,.
bool StopSearch() override
Returns true if the search must be stopped.
void AddUnassignedNodesToEmptyVehicles()
Adds all unassigned nodes to empty vehicles.
const std::vector< Saving > & GetSortedSavingsForVehicleType(int type)
void InitializeContainer(int64_t size, int64_t saving_neighbors)
void AddNewSaving(const Saving &saving, int64_t total_cost, int64_t before_node, int64_t after_node, int vehicle_type)
SavingsContainer(const SavingsFilteredHeuristic *savings_db, int vehicle_types)
bool BuildSolutionInternal() override
Virtual method to redefine how to build a solution.
SavingsFilteredHeuristic(RoutingModel *model, std::function< bool()> stop_search, SavingsParameters parameters, LocalSearchFilterManager *filter_manager)
SavingsFilteredHeuristic.
virtual double ExtraSavingsMemoryMultiplicativeFactor() const =0
int StartNewRouteWithBestVehicleOfType(int type, int64_t before_node, int64_t after_node)
std::unique_ptr< SavingsContainer< Saving > > savings_container_
clang-format off
std::unique_ptr< VehicleTypeCurator > vehicle_type_curator_
clang-format on
For the time being, Solver is neither MT_SAFE nor MT_HOT.
void Fail()
Abandon the current branch in the search tree. A backtrack will follow.
LocalSearchMonitor * GetLocalSearchMonitor() const
Returns the local search monitor.
Assignment * MakeAssignment()
This method creates an empty assignment.
std::function< bool(int64_t, int64_t, int64_t)> VariableValueComparator
SweepArranger(absl::Span< const std::pair< int64_t, int64_t > > points)
void ArrangeIndices(std::vector< int64_t > *indices)
void Update(const std::function< bool(int)> &remove_vehicle)
bool HasCompatibleVehicleOfType(int type, const std::function< bool(int)> &vehicle_is_compatible) const
std::pair< int, int > GetCompatibleVehicleOfType(int type, const std::function< bool(int)> &vehicle_is_compatible, const std::function< bool(int)> &stop_and_return_vehicle)
void Reset(const std::function< bool(int)> &store_vehicle)
— VehicleTypeCurator —
time_limit
Definition solve.cc:22
void InsertOrDie(Collection *const collection, const typename Collection::value_type &value)
Definition map_util.h:159
void STLClearObject(T *obj)
Definition stl_util.h:123
For infeasible and unbounded see Not checked if options check_solutions_if_inf_or_unbounded and the If options first_solution_only is false
problem is infeasible or unbounded (default).
Definition matchers.h:468
In SWIG mode, we don't want anything besides these top-level includes.
const Assignment * SolveFromAssignmentWithAlternativeSolversAndParameters(const Assignment *assignment, RoutingModel *primary_model, const RoutingSearchParameters &primary_parameters, const std::vector< RoutingModel * > &alternative_models, const std::vector< RoutingSearchParameters > &alternative_parameters, int max_non_improving_iterations)
Same as above but taking alternative parameters for each alternative model.
DecisionBuilder * MakeAllUnperformed(RoutingModel *model)
Returns a DecisionBuilder making all nodes unperformed.
int64_t CapAdd(int64_t x, int64_t y)
void CapAddTo(int64_t x, int64_t *y)
FirstSolutionStrategy::Value AutomaticFirstSolutionStrategy(bool has_pickup_deliveries, bool has_node_precedences, bool has_single_vehicle_node)
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
std::vector< int64_t > ComputeVehicleEndChainStarts(const RoutingModel &model)
int64_t CapSub(int64_t x, int64_t y)
const Assignment * SolveFromAssignmentWithAlternativeSolvers(const Assignment *assignment, RoutingModel *primary_model, const std::vector< RoutingModel * > &alternative_models, const RoutingSearchParameters &parameters, int max_non_improving_iterations)
Same as above, but taking an initial solution.
static const int kUnassigned
--— Routing model --—
Definition routing.cc:507
DecisionBuilder * MakeSweepDecisionBuilder(RoutingModel *model, bool check_assignment)
double Cost
Basic non-strict type for cost. The speed penalty for using double is ~2%.
const Assignment * SolveWithAlternativeSolvers(RoutingModel *primary_model, const std::vector< RoutingModel * > &alternative_models, const RoutingSearchParameters &parameters, int max_non_improving_iterations)
int64_t CapOpp(int64_t v)
Note(user): -kint64min != kint64max, but kint64max == ~kint64min.
STL namespace.
inline ::absl::StatusOr< absl::Duration > DecodeGoogleApiProto(const google::protobuf::Duration &proto)
Definition protoutil.h:42
inline ::absl::StatusOr< google::protobuf::Duration > EncodeGoogleApiProto(absl::Duration d)
Definition protoutil.h:27
bool Next()
trees with all degrees equal to
ABSL_FLAG(bool, routing_shift_insertion_cost_by_penalty, true, "Shift insertion costs by the penalty of the inserted node(s).")
std::priority_queue< Seed, std::vector< Seed >, std::greater< Seed > > priority_queue
static const int64_t kint64max
Definition types.h:32