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