OR-Tools  9.3
routing_search.cc
Go to the documentation of this file.
1// Copyright 2010-2021 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 <deque>
25#include <functional>
26#include <iterator>
27#include <limits>
28#include <memory>
29#include <numeric>
30#include <queue>
31#include <set>
32#include <string>
33#include <tuple>
34#include <utility>
35#include <vector>
36
37#include "absl/base/attributes.h"
38#include "absl/container/flat_hash_map.h"
39#include "absl/container/flat_hash_set.h"
40#include "absl/flags/flag.h"
41#include "absl/memory/memory.h"
42#include "absl/meta/type_traits.h"
43#include "absl/strings/str_cat.h"
44#include "absl/strings/string_view.h"
49#include "ortools/base/macros.h"
58#include "ortools/util/bitset.h"
61
62namespace operations_research {
63class LocalSearchPhaseParameters;
64} // namespace operations_research
65
66ABSL_FLAG(bool, routing_shift_insertion_cost_by_penalty, true,
67 "Shift insertion costs by the penalty of the inserted node(s).");
68
69ABSL_FLAG(int64_t, sweep_sectors, 1,
70 "The number of sectors the space is divided into before it is sweeped"
71 " by the ray.");
72
73namespace operations_research {
74
75// --- VehicleTypeCurator ---
76
77void VehicleTypeCurator::Reset(const std::function<bool(int)>& store_vehicle) {
78 const std::vector<std::set<VehicleClassEntry>>& all_vehicle_classes_per_type =
79 vehicle_type_container_->sorted_vehicle_classes_per_type;
80 sorted_vehicle_classes_per_type_.resize(all_vehicle_classes_per_type.size());
81 const std::vector<std::deque<int>>& all_vehicles_per_class =
82 vehicle_type_container_->vehicles_per_vehicle_class;
83 vehicles_per_vehicle_class_.resize(all_vehicles_per_class.size());
84
85 for (int type = 0; type < all_vehicle_classes_per_type.size(); type++) {
86 std::set<VehicleClassEntry>& stored_class_entries =
87 sorted_vehicle_classes_per_type_[type];
88 stored_class_entries.clear();
89 for (VehicleClassEntry class_entry : all_vehicle_classes_per_type[type]) {
90 const int vehicle_class = class_entry.vehicle_class;
91 std::vector<int>& stored_vehicles =
92 vehicles_per_vehicle_class_[vehicle_class];
93 stored_vehicles.clear();
94 for (int vehicle : all_vehicles_per_class[vehicle_class]) {
95 if (store_vehicle(vehicle)) {
96 stored_vehicles.push_back(vehicle);
97 }
98 }
99 if (!stored_vehicles.empty()) {
100 stored_class_entries.insert(class_entry);
101 }
102 }
103 }
104}
105
107 const std::function<bool(int)>& remove_vehicle) {
108 for (std::set<VehicleClassEntry>& class_entries :
109 sorted_vehicle_classes_per_type_) {
110 auto class_entry_it = class_entries.begin();
111 while (class_entry_it != class_entries.end()) {
112 const int vehicle_class = class_entry_it->vehicle_class;
113 std::vector<int>& vehicles = vehicles_per_vehicle_class_[vehicle_class];
114 vehicles.erase(std::remove_if(vehicles.begin(), vehicles.end(),
115 [&remove_vehicle](int vehicle) {
116 return remove_vehicle(vehicle);
117 }),
118 vehicles.end());
119 if (vehicles.empty()) {
120 class_entry_it = class_entries.erase(class_entry_it);
121 } else {
122 class_entry_it++;
123 }
124 }
125 }
126}
127
129 int type, const std::function<bool(int)>& vehicle_is_compatible) const {
130 for (const VehicleClassEntry& vehicle_class_entry :
131 sorted_vehicle_classes_per_type_[type]) {
132 for (int vehicle :
133 vehicles_per_vehicle_class_[vehicle_class_entry.vehicle_class]) {
134 if (vehicle_is_compatible(vehicle)) return true;
135 }
136 }
137 return false;
138}
139
141 int type, std::function<bool(int)> vehicle_is_compatible,
142 std::function<bool(int)> stop_and_return_vehicle) {
143 std::set<VehicleTypeCurator::VehicleClassEntry>& sorted_classes =
144 sorted_vehicle_classes_per_type_[type];
145 auto vehicle_class_it = sorted_classes.begin();
146
147 while (vehicle_class_it != sorted_classes.end()) {
148 const int vehicle_class = vehicle_class_it->vehicle_class;
149 std::vector<int>& vehicles = vehicles_per_vehicle_class_[vehicle_class];
150 DCHECK(!vehicles.empty());
151
152 for (auto vehicle_it = vehicles.begin(); vehicle_it != vehicles.end();
153 vehicle_it++) {
154 const int vehicle = *vehicle_it;
155 if (vehicle_is_compatible(vehicle)) {
156 vehicles.erase(vehicle_it);
157 if (vehicles.empty()) {
158 sorted_classes.erase(vehicle_class_it);
159 }
160 return {vehicle, -1};
161 }
162 if (stop_and_return_vehicle(vehicle)) {
163 return {-1, vehicle};
164 }
165 }
166 // If no compatible vehicle was found in this class, move on to the next
167 // vehicle class.
168 vehicle_class_it++;
169 }
170 // No compatible vehicle of the given type was found and the stopping
171 // condition wasn't met.
172 return {-1, -1};
173}
174
175// - Models with pickup/deliveries or node precedences are best handled by
176// PARALLEL_CHEAPEST_INSERTION.
177// - As of January 2018, models with single nodes and at least one node with
178// only one allowed vehicle are better solved by PATH_MOST_CONSTRAINED_ARC.
179// - In all other cases, PATH_CHEAPEST_ARC is used.
180// TODO(user): Make this smarter.
182 bool has_pickup_deliveries, bool has_node_precedences,
183 bool has_single_vehicle_node) {
184 if (has_pickup_deliveries || has_node_precedences) {
185 return FirstSolutionStrategy::PARALLEL_CHEAPEST_INSERTION;
186 }
187 if (has_single_vehicle_node) {
188 return FirstSolutionStrategy::PATH_MOST_CONSTRAINED_ARC;
189 }
190 return FirstSolutionStrategy::PATH_CHEAPEST_ARC;
191}
192
193// --- First solution decision builder ---
194
195// IntVarFilteredDecisionBuilder
196
198 std::unique_ptr<IntVarFilteredHeuristic> heuristic)
199 : heuristic_(std::move(heuristic)) {}
200
202 Assignment* const assignment = heuristic_->BuildSolution();
203 if (assignment != nullptr) {
204 VLOG(2) << "Number of decisions: " << heuristic_->number_of_decisions();
205 VLOG(2) << "Number of rejected decisions: "
206 << heuristic_->number_of_rejects();
207 assignment->Restore();
208 } else {
209 solver->Fail();
210 }
211 return nullptr;
212}
213
215 return heuristic_->number_of_decisions();
216}
217
219 return heuristic_->number_of_rejects();
220}
221
223 return absl::StrCat("IntVarFilteredDecisionBuilder(",
224 heuristic_->DebugString(), ")");
225}
226
227// --- First solution heuristics ---
228
229// IntVarFilteredHeuristic
230
232 Solver* solver, const std::vector<IntVar*>& vars,
233 const std::vector<IntVar*>& secondary_vars,
234 LocalSearchFilterManager* filter_manager)
235 : assignment_(solver->MakeAssignment()),
236 solver_(solver),
237 vars_(vars),
238 base_vars_size_(vars.size()),
239 delta_(solver->MakeAssignment()),
240 is_in_delta_(vars_.size(), false),
241 empty_(solver->MakeAssignment()),
242 filter_manager_(filter_manager),
243 objective_upper_bound_(std::numeric_limits<int64_t>::max()),
244 number_of_decisions_(0),
245 number_of_rejects_(0) {
246 if (!secondary_vars.empty()) {
247 vars_.insert(vars_.end(), secondary_vars.begin(), secondary_vars.end());
248 }
249 assignment_->MutableIntVarContainer()->Resize(vars_.size());
250 delta_indices_.reserve(vars_.size());
251}
252
254 number_of_decisions_ = 0;
255 number_of_rejects_ = 0;
256 // Wiping assignment when starting a new search.
258 assignment_->MutableIntVarContainer()->Resize(vars_.size());
259 delta_->MutableIntVarContainer()->Clear();
261}
262
264 if (!InitializeSolution()) {
265 return nullptr;
266 }
267 if (BuildSolutionInternal()) {
268 return assignment_;
269 }
270 return nullptr;
271}
272
274 const std::function<int64_t(int64_t)>& next_accessor) {
275 // NOTE(b/219043402): The filter manager must first be synchronized with a
276 // valid solution that properly connects route starts to route ends in order
277 // for future FilterAccept() calls to correctly detect infeasibilities.
278 if (!InitializeSolution()) {
279 return nullptr;
280 }
281
282 for (int v = 0; v < model_->vehicles(); v++) {
283 int64_t node = model_->Start(v);
284 while (!model_->IsEnd(node)) {
285 const int64_t next = next_accessor(node);
286 DCHECK_NE(next, node);
287 SetValue(node, next);
288 // TODO(user): Add vehicle values to delta when this method will be
289 // used with cost filtering. The code should be similar to this:
290 // if (HasSecondaryVars()) {
291 // SetValue(SecondaryVarIndex(node), v);
292 // }
293 SetVehicleIndex(node, v);
294 node = next;
295 }
296 }
297 if (!Evaluate(/*commit=*/true).has_value()) {
299 return nullptr;
300 }
301 if (BuildSolutionInternal()) {
302 return assignment_;
303 }
304 return nullptr;
305}
306
307std::optional<int64_t> IntVarFilteredHeuristic::Evaluate(bool commit) {
308 ++number_of_decisions_;
309 const bool accept = FilterAccept();
310 if (accept) {
311 if (filter_manager_ != nullptr) {
312 // objective upper_bound_ is used to reduce the number of potential
313 // insertion candidates, specifically when filter_manager_ filters cost.
314 // Rationale: the best cost candidate will always be valid and will be
315 // inserted so no use accepting degrading ones. However when a candidate
316 // is committed, the upper bound is relaxed to make sure further
317 // (cost-degrading) insertions will be accepted
318 // (cf. SynchronizeFilters()).
319 DCHECK_LE(filter_manager_->GetAcceptedObjectiveValue(),
320 objective_upper_bound_);
321 objective_upper_bound_ = filter_manager_->GetAcceptedObjectiveValue();
322 }
323 if (commit) {
324 const Assignment::IntContainer& delta_container =
325 delta_->IntVarContainer();
326 const int delta_size = delta_container.Size();
327 Assignment::IntContainer* const container =
329 for (int i = 0; i < delta_size; ++i) {
330 const IntVarElement& delta_element = delta_container.Element(i);
331 IntVar* const var = delta_element.Var();
332 DCHECK_EQ(var, vars_[delta_indices_[i]]);
333 container->AddAtPosition(var, delta_indices_[i])
334 ->SetValue(delta_element.Value());
335 }
337 }
338 } else {
339 ++number_of_rejects_;
340 }
341 // Reset is_in_delta to all false.
342 for (const int delta_index : delta_indices_) {
343 is_in_delta_[delta_index] = false;
344 }
345 delta_->Clear();
346 delta_indices_.clear();
347 return accept ? std::optional<int64_t>{objective_upper_bound_} : std::nullopt;
348}
349
351 if (filter_manager_) filter_manager_->Synchronize(assignment_, delta_);
352 // Resetting the upper bound to allow cost-increasing insertions.
353 objective_upper_bound_ = std::numeric_limits<int64_t>::max();
354}
355
356bool IntVarFilteredHeuristic::FilterAccept() {
357 if (!filter_manager_) return true;
358 LocalSearchMonitor* const monitor = solver_->GetLocalSearchMonitor();
359 return filter_manager_->Accept(monitor, delta_, empty_,
361 objective_upper_bound_);
362}
363
364// RoutingFilteredHeuristic
365
368 bool omit_secondary_vars)
370 model->solver(), model->Nexts(),
371 omit_secondary_vars || model->CostsAreHomogeneousAcrossVehicles()
372 ? std::vector<IntVar*>()
373 : model->VehicleVars(),
374 filter_manager),
375 model_(model) {}
376
377bool RoutingFilteredHeuristic::InitializeSolution() {
380 // Find the chains of nodes (when nodes have their "Next" value bound in the
381 // current solution, it forms a link in a chain). Eventually, starts[end]
382 // will contain the index of the first node of the chain ending at node 'end'
383 // and ends[start] will be the last node of the chain starting at node
384 // 'start'. Values of starts[node] and ends[node] for other nodes is used
385 // for intermediary computations and do not necessarily reflect actual chain
386 // starts and ends.
387
388 // Start by adding partial start chains to current assignment.
389 start_chain_ends_.resize(model()->vehicles());
390 end_chain_starts_.resize(model()->vehicles());
391
393 for (int vehicle = 0; vehicle < model()->vehicles(); ++vehicle) {
394 int64_t node = model()->Start(vehicle);
395 while (!model()->IsEnd(node) && Var(node)->Bound()) {
396 const int64_t next = Var(node)->Min();
397 SetValue(node, next);
398 if (HasSecondaryVars()) {
399 SetValue(SecondaryVarIndex(node), vehicle);
400 }
401 SetVehicleIndex(node, vehicle);
402 node = next;
403 }
404 start_chain_ends_[vehicle] = node;
405 }
406
407 std::vector<int64_t> starts(Size() + model()->vehicles(), -1);
408 std::vector<int64_t> ends(Size() + model()->vehicles(), -1);
409 for (int node = 0; node < Size() + model()->vehicles(); ++node) {
410 // Each node starts as a singleton chain.
411 starts[node] = node;
412 ends[node] = node;
413 }
414 std::vector<bool> touched(Size(), false);
415 for (int node = 0; node < Size(); ++node) {
416 int current = node;
417 while (!model()->IsEnd(current) && !touched[current]) {
418 touched[current] = true;
419 IntVar* const next_var = Var(current);
420 if (next_var->Bound()) {
421 current = next_var->Value();
422 }
423 }
424 // Merge the sub-chain starting from 'node' and ending at 'current' with
425 // the existing sub-chain starting at 'current'.
426 starts[ends[current]] = starts[node];
427 ends[starts[node]] = ends[current];
428 }
429
430 // Set each route to be the concatenation of the chain at its start and the
431 // chain at its end, without nodes in between.
432 for (int vehicle = 0; vehicle < model()->vehicles(); ++vehicle) {
433 end_chain_starts_[vehicle] = starts[model()->End(vehicle)];
434 int64_t node = start_chain_ends_[vehicle];
435 if (!model()->IsEnd(node)) {
436 int64_t next = starts[model()->End(vehicle)];
437 SetValue(node, next);
438 if (HasSecondaryVars()) {
439 SetValue(SecondaryVarIndex(node), vehicle);
440 }
441 SetVehicleIndex(node, vehicle);
442 node = next;
443 while (!model()->IsEnd(node)) {
444 next = Var(node)->Min();
445 SetValue(node, next);
446 if (HasSecondaryVars()) {
447 SetValue(SecondaryVarIndex(node), vehicle);
448 }
449 SetVehicleIndex(node, vehicle);
450 node = next;
451 }
452 }
453 }
454
455 if (!Evaluate(/*commit=*/true).has_value()) {
457 return false;
458 }
459 return true;
460}
461
464 node, 1, [this, node](int alternate) {
465 if (node != alternate && !Contains(alternate)) {
466 SetValue(alternate, alternate);
467 }
468 });
469}
470
472 for (int index = 0; index < model_->Size(); ++index) {
474 if (!Contains(index)) {
476 if (HasSecondaryVars()) {
478 }
479 }
480 }
481}
482
484 std::vector<bool> to_make_unperformed(Size(), false);
485 for (const auto& [pickups, deliveries] :
486 model()->GetPickupAndDeliveryPairs()) {
487 int64_t performed_pickup = -1;
488 for (int64_t pickup : pickups) {
489 if (Contains(pickup) && Value(pickup) != pickup) {
490 performed_pickup = pickup;
491 break;
492 }
493 }
494 int64_t performed_delivery = -1;
495 for (int64_t delivery : deliveries) {
496 if (Contains(delivery) && Value(delivery) != delivery) {
497 performed_delivery = delivery;
498 break;
499 }
500 }
501 if ((performed_pickup == -1) != (performed_delivery == -1)) {
502 if (performed_pickup != -1) {
503 to_make_unperformed[performed_pickup] = true;
504 }
505 if (performed_delivery != -1) {
506 to_make_unperformed[performed_delivery] = true;
507 }
508 }
509 }
510 for (int index = 0; index < Size(); ++index) {
511 if (to_make_unperformed[index] || !Contains(index)) continue;
512 int64_t next = Value(index);
513 while (next < Size() && to_make_unperformed[next]) {
514 const int64_t next_of_next = Value(next);
515 SetValue(index, next_of_next);
517 next = next_of_next;
518 }
519 }
520}
521
522// CheapestInsertionFilteredHeuristic
523
526 std::function<int64_t(int64_t, int64_t, int64_t)> evaluator,
527 std::function<int64_t(int64_t)> penalty_evaluator,
528 LocalSearchFilterManager* filter_manager)
529 : RoutingFilteredHeuristic(model, filter_manager,
530 /*omit_secondary_vars=*/evaluator != nullptr),
531 evaluator_(std::move(evaluator)),
532 penalty_evaluator_(std::move(penalty_evaluator)) {}
533
534std::vector<std::vector<CheapestInsertionFilteredHeuristic::StartEndValue>>
536 const std::vector<int>& vehicles) {
537 const absl::flat_hash_set<int> vehicle_set(vehicles.begin(), vehicles.end());
538 std::vector<std::vector<StartEndValue>> start_end_distances_per_node(
539 model()->Size());
540
541 for (int node = 0; node < model()->Size(); node++) {
542 if (Contains(node)) continue;
543 std::vector<StartEndValue>& start_end_distances =
544 start_end_distances_per_node[node];
545 const IntVar* const vehicle_var = model()->VehicleVar(node);
546 const int64_t num_allowed_vehicles = vehicle_var->Size();
547
548 const auto add_distance = [this, node, num_allowed_vehicles,
549 &start_end_distances](int vehicle) {
550 const int64_t start = model()->Start(vehicle);
551 const int64_t end = model()->End(vehicle);
552
553 // We compute the distance of node to the start/end nodes of the route.
554 const int64_t distance =
555 CapAdd(model()->GetArcCostForVehicle(start, node, vehicle),
556 model()->GetArcCostForVehicle(node, end, vehicle));
557 start_end_distances.push_back({num_allowed_vehicles, distance, vehicle});
558 };
559 // Iterating over an IntVar domain is faster than calling Contains.
560 // Therefore we iterate on 'vehicles' only if it's smaller than the domain
561 // size of the VehicleVar.
562 if (num_allowed_vehicles < vehicles.size()) {
563 std::unique_ptr<IntVarIterator> it(
564 vehicle_var->MakeDomainIterator(false));
565 for (const int64_t vehicle : InitAndGetValues(it.get())) {
566 if (vehicle < 0 || !vehicle_set.contains(vehicle)) continue;
567 add_distance(vehicle);
568 }
569 } else {
570 for (const int vehicle : vehicles) {
571 if (!vehicle_var->Contains(vehicle)) continue;
572 add_distance(vehicle);
573 }
574 }
575 // Sort the distances for the node to all start/ends of available vehicles
576 // in decreasing order.
577 std::sort(start_end_distances.begin(), start_end_distances.end(),
578 [](const StartEndValue& first, const StartEndValue& second) {
579 return second < first;
580 });
581 }
582 return start_end_distances_per_node;
583}
584
585template <class Queue>
587 std::vector<std::vector<StartEndValue>>* start_end_distances_per_node,
588 Queue* priority_queue) {
589 const int num_nodes = model()->Size();
590 DCHECK_EQ(start_end_distances_per_node->size(), num_nodes);
591
592 for (int node = 0; node < num_nodes; node++) {
593 if (Contains(node)) continue;
594 std::vector<StartEndValue>& start_end_distances =
595 (*start_end_distances_per_node)[node];
596 if (start_end_distances.empty()) {
597 continue;
598 }
599 // Put the best StartEndValue for this node in the priority queue.
600 const StartEndValue& start_end_value = start_end_distances.back();
601 priority_queue->push(std::make_pair(start_end_value, node));
602 start_end_distances.pop_back();
603 }
604}
605
607 int64_t predecessor,
608 int64_t successor,
609 int vehicle) {
610 SetValue(predecessor, node);
611 SetValue(node, successor);
613 if (HasSecondaryVars() && vehicle != -1) {
614 SetValue(SecondaryVarIndex(predecessor), vehicle);
615 SetValue(SecondaryVarIndex(node), vehicle);
616 SetValue(SecondaryVarIndex(successor), vehicle);
617 }
618}
619
621 int64_t node_to_insert, int64_t start, int64_t next_after_start,
622 int vehicle, std::vector<NodeInsertion>* node_insertions) {
623 DCHECK(node_insertions != nullptr);
624 int64_t insert_after = start;
625 while (!model()->IsEnd(insert_after)) {
626 const int64_t insert_before =
627 (insert_after == start) ? next_after_start : Value(insert_after);
628 if (evaluator_ == nullptr) {
629 InsertBetween(node_to_insert, insert_after, insert_before, vehicle);
630 std::optional<int64_t> insertion_cost = Evaluate(/*commit=*/false);
631 if (insertion_cost.has_value()) {
632 node_insertions->push_back({insert_after, vehicle, *insertion_cost});
633 }
634 } else {
635 node_insertions->push_back(
636 {insert_after, vehicle,
637 GetInsertionCostForNodeAtPosition(node_to_insert, insert_after,
638 insert_before, vehicle)});
639 }
640 insert_after = insert_before;
641 }
642}
643
645 int64_t node_to_insert, int64_t insert_after, int64_t insert_before,
646 int vehicle) const {
647 DCHECK(evaluator_ != nullptr);
648 return CapSub(CapAdd(evaluator_(insert_after, node_to_insert, vehicle),
649 evaluator_(node_to_insert, insert_before, vehicle)),
650 evaluator_(insert_after, insert_before, vehicle));
651}
652
654 int64_t node_to_insert) const {
655 if (penalty_evaluator_ != nullptr) {
656 return penalty_evaluator_(node_to_insert);
657 }
659}
660
661namespace {
662template <class T>
663void SortAndExtractPairSeconds(std::vector<std::pair<int64_t, T>>* pairs,
664 std::vector<T>* sorted_seconds) {
665 CHECK(pairs != nullptr);
666 CHECK(sorted_seconds != nullptr);
667 std::sort(pairs->begin(), pairs->end());
668 sorted_seconds->reserve(pairs->size());
669 for (const std::pair<int64_t, T>& p : *pairs) {
670 sorted_seconds->push_back(p.second);
671 }
672}
673} // namespace
674
675// Priority queue entries used by global cheapest insertion heuristic.
676
677// Entry in priority queue containing the insertion positions of a node pair.
679 public:
682 int64_t bucket)
683 : value_(std::numeric_limits<int64_t>::max()),
684 heap_index_(-1),
685 pickup_to_insert_(pickup_to_insert),
686 pickup_insert_after_(pickup_insert_after),
687 delivery_to_insert_(delivery_to_insert),
688 delivery_insert_after_(delivery_insert_after),
689 vehicle_(vehicle),
690 bucket_(bucket) {}
691 // Note: for compatibility reasons, comparator follows tie-breaking rules used
692 // in the first version of GlobalCheapestInsertion.
693 bool operator<(const PairEntry& other) const {
694 // We give higher priority to insertions from lower buckets.
695 if (bucket_ != other.bucket_) {
696 return bucket_ > other.bucket_;
697 }
698 // We then compare by value, then we favor insertions (vehicle != -1).
699 // The rest of the tie-breaking is done with std::tie.
700 if (value_ != other.value_) {
701 return value_ > other.value_;
702 }
703 if ((vehicle_ == -1) ^ (other.vehicle_ == -1)) {
704 return vehicle_ == -1;
705 }
706 return std::tie(pickup_insert_after_, pickup_to_insert_,
707 delivery_insert_after_, delivery_to_insert_, vehicle_) >
708 std::tie(other.pickup_insert_after_, other.pickup_to_insert_,
709 other.delivery_insert_after_, other.delivery_to_insert_,
710 other.vehicle_);
711 }
712 void SetHeapIndex(int h) { heap_index_ = h; }
713 int GetHeapIndex() const { return heap_index_; }
714 void set_value(int64_t value) { value_ = value; }
715 int pickup_to_insert() const { return pickup_to_insert_; }
716 int pickup_insert_after() const { return pickup_insert_after_; }
718 pickup_insert_after_ = pickup_insert_after;
719 }
720 int delivery_to_insert() const { return delivery_to_insert_; }
721 int delivery_insert_after() const { return delivery_insert_after_; }
722 int vehicle() const { return vehicle_; }
723 void set_vehicle(int vehicle) { vehicle_ = vehicle; }
724
725 private:
726 int64_t value_;
727 int heap_index_;
728 const int pickup_to_insert_;
729 int pickup_insert_after_;
730 const int delivery_to_insert_;
731 const int delivery_insert_after_;
732 int vehicle_;
733 const int64_t bucket_;
734};
735
736// Entry in priority queue containing the insertion position of a node.
738 public:
739 NodeEntry(int node_to_insert, int insert_after, int vehicle, int64_t bucket)
740 : value_(std::numeric_limits<int64_t>::max()),
741 heap_index_(-1),
742 node_to_insert_(node_to_insert),
743 insert_after_(insert_after),
744 vehicle_(vehicle),
745 bucket_(bucket) {}
746 bool operator<(const NodeEntry& other) const {
747 // See PairEntry::operator<(), above. This one is similar.
748 if (bucket_ != other.bucket_) {
749 return bucket_ > other.bucket_;
750 }
751 if (value_ != other.value_) {
752 return value_ > other.value_;
753 }
754 if ((vehicle_ == -1) ^ (other.vehicle_ == -1)) {
755 return vehicle_ == -1;
756 }
757 return std::tie(insert_after_, node_to_insert_, vehicle_) >
758 std::tie(other.insert_after_, other.node_to_insert_, other.vehicle_);
759 }
760 void SetHeapIndex(int h) { heap_index_ = h; }
761 int GetHeapIndex() const { return heap_index_; }
762 void set_value(int64_t value) { value_ = value; }
763 int node_to_insert() const { return node_to_insert_; }
764 int insert_after() const { return insert_after_; }
765 void set_insert_after(int insert_after) { insert_after_ = insert_after; }
766 int vehicle() const { return vehicle_; }
767 void set_vehicle(int vehicle) { vehicle_ = vehicle; }
768
769 private:
770 int64_t value_;
771 int heap_index_;
772 const int node_to_insert_;
773 int insert_after_;
774 int vehicle_;
775 const int64_t bucket_;
776};
777
778// GlobalCheapestInsertionFilteredHeuristic
779
783 std::function<int64_t(int64_t, int64_t, int64_t)> evaluator,
784 std::function<int64_t(int64_t)> penalty_evaluator,
785 LocalSearchFilterManager* filter_manager,
787 : CheapestInsertionFilteredHeuristic(model, std::move(evaluator),
788 std::move(penalty_evaluator),
789 filter_manager),
790 gci_params_(parameters),
791 node_index_to_vehicle_(model->Size(), -1),
792 empty_vehicle_type_curator_(nullptr) {
793 CHECK_GT(gci_params_.neighbors_ratio, 0);
794 CHECK_LE(gci_params_.neighbors_ratio, 1);
795 CHECK_GE(gci_params_.min_neighbors, 1);
796
797 if (NumNeighbors() >= NumNonStartEndNodes() - 1) {
798 // All nodes are neighbors, so we set the neighbors_ratio to 1 to avoid
799 // unnecessary computations in the code.
800 gci_params_.neighbors_ratio = 1;
801 }
802
803 if (gci_params_.neighbors_ratio == 1) {
804 gci_params_.use_neighbors_ratio_for_initialization = false;
805 all_nodes_.resize(model->Size());
806 std::iota(all_nodes_.begin(), all_nodes_.end(), 0);
807 }
808}
809
810void GlobalCheapestInsertionFilteredHeuristic::ComputeNeighborhoods() {
811 if (gci_params_.neighbors_ratio == 1 ||
812 !node_index_to_neighbors_by_cost_class_.empty()) {
813 // Neighborhood computations not needed or already done.
814 return;
815 }
816
817 // TODO(user): Refactor the neighborhood computations in RoutingModel.
818 const int64_t num_neighbors = NumNeighbors();
819 // If num_neighbors was greater or equal num_non_start_end_nodes - 1,
820 // gci_params_.neighbors_ratio should have been set to 1.
821 DCHECK_LT(num_neighbors, NumNonStartEndNodes() - 1);
822
823 const RoutingModel& routing_model = *model();
824 const int64_t size = routing_model.Size();
825 node_index_to_neighbors_by_cost_class_.resize(size);
826 const int num_cost_classes = routing_model.GetCostClassesCount();
827 for (int64_t node_index = 0; node_index < size; node_index++) {
828 node_index_to_neighbors_by_cost_class_[node_index].resize(num_cost_classes);
829 for (int cc = 0; cc < num_cost_classes; cc++) {
830 node_index_to_neighbors_by_cost_class_[node_index][cc] =
831 absl::make_unique<SparseBitset<int64_t>>(size);
832 }
833 }
834
835 for (int64_t node_index = 0; node_index < size; ++node_index) {
836 DCHECK(!routing_model.IsEnd(node_index));
837 if (routing_model.IsStart(node_index)) {
838 // We don't compute neighbors for vehicle starts: all nodes are considered
839 // neighbors for a vehicle start.
840 continue;
841 }
842
843 // TODO(user): Use the model's IndexNeighborFinder when available.
844 for (int cost_class = 0; cost_class < num_cost_classes; cost_class++) {
845 if (!routing_model.HasVehicleWithCostClassIndex(
846 RoutingCostClassIndex(cost_class))) {
847 // No vehicle with this cost class, avoid unnecessary computations.
848 continue;
849 }
850 std::vector<std::pair</*cost*/ int64_t, /*node*/ int64_t>>
851 costed_after_nodes;
852 costed_after_nodes.reserve(size);
853 for (int after_node = 0; after_node < size; ++after_node) {
854 if (after_node != node_index && !routing_model.IsStart(after_node)) {
855 costed_after_nodes.push_back(
856 std::make_pair(routing_model.GetArcCostForClass(
857 node_index, after_node, cost_class),
858 after_node));
859 }
860 }
861 std::nth_element(costed_after_nodes.begin(),
862 costed_after_nodes.begin() + num_neighbors - 1,
863 costed_after_nodes.end());
864 costed_after_nodes.resize(num_neighbors);
865
866 for (auto [cost, neighbor] : costed_after_nodes) {
867 node_index_to_neighbors_by_cost_class_[node_index][cost_class]->Set(
868 neighbor);
869
870 // Add reverse neighborhood.
871 DCHECK(!routing_model.IsEnd(neighbor) &&
872 !routing_model.IsStart(neighbor));
873 node_index_to_neighbors_by_cost_class_[neighbor][cost_class]->Set(
874 node_index);
875 }
876 // Add all vehicle starts as neighbors to this node and vice-versa.
877 for (int vehicle = 0; vehicle < routing_model.vehicles(); vehicle++) {
878 const int64_t vehicle_start = routing_model.Start(vehicle);
879 node_index_to_neighbors_by_cost_class_[node_index][cost_class]->Set(
880 vehicle_start);
881 node_index_to_neighbors_by_cost_class_[vehicle_start][cost_class]->Set(
882 node_index);
883 }
884 }
885 }
886}
887
888bool GlobalCheapestInsertionFilteredHeuristic::IsNeighborForCostClass(
889 int cost_class, int64_t node_index, int64_t neighbor_index) const {
890 return gci_params_.neighbors_ratio == 1 ||
891 (*node_index_to_neighbors_by_cost_class_[node_index]
892 [cost_class])[neighbor_index];
893}
894
895bool GlobalCheapestInsertionFilteredHeuristic::CheckVehicleIndices() const {
896 std::vector<bool> node_is_visited(model()->Size(), -1);
897 for (int v = 0; v < model()->vehicles(); v++) {
898 for (int node = model()->Start(v); !model()->IsEnd(node);
899 node = Value(node)) {
900 if (node_index_to_vehicle_[node] != v) {
901 return false;
902 }
903 node_is_visited[node] = true;
904 }
905 }
906
907 for (int node = 0; node < model()->Size(); node++) {
908 if (!node_is_visited[node] && node_index_to_vehicle_[node] != -1) {
909 return false;
910 }
911 }
912
913 return true;
914}
915
917 ComputeNeighborhoods();
918 if (empty_vehicle_type_curator_ == nullptr) {
919 empty_vehicle_type_curator_ = absl::make_unique<VehicleTypeCurator>(
920 model()->GetVehicleTypeContainer());
921 }
922 // Store all empty vehicles in the empty_vehicle_type_curator_.
923 empty_vehicle_type_curator_->Reset(
924 [this](int vehicle) { return VehicleIsEmpty(vehicle); });
925 // Insert partially inserted pairs.
926 const RoutingModel::IndexPairs& pickup_delivery_pairs =
928 std::map<int64_t, std::vector<int>> pairs_to_insert_by_bucket;
929 absl::flat_hash_map<int, std::map<int64_t, std::vector<int>>>
930 vehicle_to_pair_nodes;
931 for (int index = 0; index < pickup_delivery_pairs.size(); index++) {
932 const RoutingModel::IndexPair& index_pair = pickup_delivery_pairs[index];
933 int pickup_vehicle = -1;
934 for (int64_t pickup : index_pair.first) {
935 if (Contains(pickup)) {
936 pickup_vehicle = node_index_to_vehicle_[pickup];
937 break;
938 }
939 }
940 int delivery_vehicle = -1;
941 for (int64_t delivery : index_pair.second) {
942 if (Contains(delivery)) {
943 delivery_vehicle = node_index_to_vehicle_[delivery];
944 break;
945 }
946 }
947 if (pickup_vehicle < 0 && delivery_vehicle < 0) {
948 pairs_to_insert_by_bucket[GetBucketOfPair(index_pair)].push_back(index);
949 }
950 if (pickup_vehicle >= 0 && delivery_vehicle < 0) {
951 std::vector<int>& pair_nodes = vehicle_to_pair_nodes[pickup_vehicle][1];
952 for (int64_t delivery : index_pair.second) {
953 pair_nodes.push_back(delivery);
954 }
955 }
956 if (pickup_vehicle < 0 && delivery_vehicle >= 0) {
957 std::vector<int>& pair_nodes = vehicle_to_pair_nodes[delivery_vehicle][1];
958 for (int64_t pickup : index_pair.first) {
959 pair_nodes.push_back(pickup);
960 }
961 }
962 }
963 for (const auto& [vehicle, nodes] : vehicle_to_pair_nodes) {
964 if (!InsertNodesOnRoutes(nodes, {vehicle})) return false;
965 }
966
967 if (!InsertPairsAndNodesByRequirementTopologicalOrder()) return false;
968
969 // TODO(user): Adapt the pair insertions to also support seed and
970 // sequential insertion.
971 if (!InsertPairs(pairs_to_insert_by_bucket)) return false;
972 std::map<int64_t, std::vector<int>> nodes_by_bucket;
973 for (int node = 0; node < model()->Size(); ++node) {
974 if (!Contains(node) && model()->GetPickupIndexPairs(node).empty() &&
975 model()->GetDeliveryIndexPairs(node).empty()) {
976 nodes_by_bucket[GetBucketOfNode(node)].push_back(node);
977 }
978 }
979 InsertFarthestNodesAsSeeds();
980 if (gci_params_.is_sequential) {
981 if (!SequentialInsertNodes(nodes_by_bucket)) return false;
982 } else if (!InsertNodesOnRoutes(nodes_by_bucket, {})) {
983 return false;
984 }
986 DCHECK(CheckVehicleIndices());
987 return Evaluate(/*commit=*/true).has_value();
988}
989
990bool GlobalCheapestInsertionFilteredHeuristic::
991 InsertPairsAndNodesByRequirementTopologicalOrder() {
992 const RoutingModel::IndexPairs& pickup_delivery_pairs =
994 for (const std::vector<int>& types :
995 model()->GetTopologicallySortedVisitTypes()) {
996 for (int type : types) {
997 std::map<int64_t, std::vector<int>> pairs_to_insert_by_bucket;
998 for (int index : model()->GetPairIndicesOfType(type)) {
999 pairs_to_insert_by_bucket[GetBucketOfPair(pickup_delivery_pairs[index])]
1000 .push_back(index);
1001 }
1002 if (!InsertPairs(pairs_to_insert_by_bucket)) return false;
1003 std::map<int64_t, std::vector<int>> nodes_by_bucket;
1004 for (int node : model()->GetSingleNodesOfType(type)) {
1005 nodes_by_bucket[GetBucketOfNode(node)].push_back(node);
1006 }
1007 if (!InsertNodesOnRoutes(nodes_by_bucket, {})) return false;
1008 }
1009 }
1010 return true;
1011}
1012
1013bool GlobalCheapestInsertionFilteredHeuristic::InsertPairs(
1014 const std::map<int64_t, std::vector<int>>& pair_indices_by_bucket) {
1016 std::vector<PairEntries> pickup_to_entries;
1017 std::vector<PairEntries> delivery_to_entries;
1018 std::vector<int> pair_indices_to_insert;
1019 for (const auto& [bucket, pair_indices] : pair_indices_by_bucket) {
1020 pair_indices_to_insert.insert(pair_indices_to_insert.end(),
1021 pair_indices.begin(), pair_indices.end());
1022 if (!InitializePairPositions(pair_indices_to_insert, &priority_queue,
1023 &pickup_to_entries, &delivery_to_entries)) {
1024 return false;
1025 }
1026 while (!priority_queue.IsEmpty()) {
1027 if (StopSearchAndCleanup(&priority_queue)) {
1028 return false;
1029 }
1030 PairEntry* const entry = priority_queue.Top();
1031 const int64_t pickup = entry->pickup_to_insert();
1032 const int64_t delivery = entry->delivery_to_insert();
1033 if (Contains(pickup) || Contains(delivery)) {
1034 DeletePairEntry(entry, &priority_queue, &pickup_to_entries,
1035 &delivery_to_entries);
1036 continue;
1037 }
1038
1039 const int entry_vehicle = entry->vehicle();
1040 if (entry_vehicle == -1) {
1041 // Pair is unperformed.
1042 SetValue(pickup, pickup);
1043 SetValue(delivery, delivery);
1044 if (!Evaluate(/*commit=*/true).has_value()) {
1045 DeletePairEntry(entry, &priority_queue, &pickup_to_entries,
1046 &delivery_to_entries);
1047 }
1048 continue;
1049 }
1050
1051 // Pair is performed.
1052 if (UseEmptyVehicleTypeCuratorForVehicle(entry_vehicle)) {
1053 if (!InsertPairEntryUsingEmptyVehicleTypeCurator(
1054 pair_indices_to_insert, entry, &priority_queue,
1055 &pickup_to_entries, &delivery_to_entries)) {
1056 return false;
1057 }
1058 // The entry corresponded to an insertion on an empty vehicle, which was
1059 // handled by the call to InsertPairEntryUsingEmptyVehicleTypeCurator().
1060 continue;
1061 }
1062
1063 const int64_t pickup_insert_after = entry->pickup_insert_after();
1064 const int64_t pickup_insert_before = Value(pickup_insert_after);
1065 InsertBetween(pickup, pickup_insert_after, pickup_insert_before);
1066
1067 const int64_t delivery_insert_after = entry->delivery_insert_after();
1068 const int64_t delivery_insert_before = (delivery_insert_after == pickup)
1069 ? pickup_insert_before
1070 : Value(delivery_insert_after);
1071 InsertBetween(delivery, delivery_insert_after, delivery_insert_before);
1072 if (Evaluate(/*commit=*/true).has_value()) {
1073 if (!UpdateAfterPairInsertion(
1074 pair_indices_to_insert, entry_vehicle, pickup,
1075 pickup_insert_after, delivery, delivery_insert_after,
1076 &priority_queue, &pickup_to_entries, &delivery_to_entries)) {
1077 return false;
1078 }
1079 } else {
1080 DeletePairEntry(entry, &priority_queue, &pickup_to_entries,
1081 &delivery_to_entries);
1082 }
1083 }
1084 // In case all pairs could not be inserted, pushing uninserted ones to the
1085 // next bucket.
1086 const RoutingModel::IndexPairs& pickup_delivery_pairs =
1088 pair_indices_to_insert.erase(
1089 std::remove_if(
1090 pair_indices_to_insert.begin(), pair_indices_to_insert.end(),
1091 [this, &pickup_delivery_pairs](int pair_index) {
1092 // Checking a pickup has been inserted is enough to make sure the
1093 // full pair has been inserted since a pickup cannot be inserted
1094 // without its delivery (and vice versa).
1095 for (int64_t pickup : pickup_delivery_pairs[pair_index].first) {
1096 if (Contains(pickup)) return true;
1097 }
1098 return false;
1099 }),
1100 pair_indices_to_insert.end());
1101 }
1102 return true;
1103}
1104
1105bool GlobalCheapestInsertionFilteredHeuristic::
1106 InsertPairEntryUsingEmptyVehicleTypeCurator(
1107 const std::vector<int>& pair_indices,
1108 GlobalCheapestInsertionFilteredHeuristic::PairEntry* const pair_entry,
1110 GlobalCheapestInsertionFilteredHeuristic::PairEntry>*
1111 priority_queue,
1112 std::vector<GlobalCheapestInsertionFilteredHeuristic::PairEntries>*
1113 pickup_to_entries,
1114 std::vector<GlobalCheapestInsertionFilteredHeuristic::PairEntries>*
1115 delivery_to_entries) {
1116 const int entry_vehicle = pair_entry->vehicle();
1117 DCHECK(UseEmptyVehicleTypeCuratorForVehicle(entry_vehicle));
1118
1119 // Trying to insert on an empty vehicle.
1120 // As we only have one pair_entry per empty vehicle type, we try inserting on
1121 // all vehicles of this type with the same fixed cost, as they all have the
1122 // same insertion value.
1123 const int64_t pickup = pair_entry->pickup_to_insert();
1124 const int64_t delivery = pair_entry->delivery_to_insert();
1125 const int64_t entry_fixed_cost =
1126 model()->GetFixedCostOfVehicle(entry_vehicle);
1127 auto vehicle_is_compatible = [this, entry_fixed_cost, pickup,
1128 delivery](int vehicle) {
1129 if (model()->GetFixedCostOfVehicle(vehicle) != entry_fixed_cost) {
1130 return false;
1131 }
1132 // NOTE: Only empty vehicles should be in the vehicle_curator_.
1133 DCHECK(VehicleIsEmpty(vehicle));
1134 const int64_t end = model()->End(vehicle);
1135 InsertBetween(pickup, model()->Start(vehicle), end, vehicle);
1136 InsertBetween(delivery, pickup, end, vehicle);
1137 return Evaluate(/*commit=*/true).has_value();
1138 };
1139 // Since the vehicles of the same type are sorted by increasing fixed
1140 // cost by the curator, we can stop as soon as a vehicle with a fixed cost
1141 // higher than the entry_fixed_cost is found which is empty, and adapt the
1142 // pair entry with this new vehicle.
1143 auto stop_and_return_vehicle = [this, entry_fixed_cost](int vehicle) {
1144 return model()->GetFixedCostOfVehicle(vehicle) > entry_fixed_cost;
1145 };
1146 const auto [compatible_vehicle, next_fixed_cost_empty_vehicle] =
1147 empty_vehicle_type_curator_->GetCompatibleVehicleOfType(
1148 empty_vehicle_type_curator_->Type(entry_vehicle),
1149 vehicle_is_compatible, stop_and_return_vehicle);
1150 if (compatible_vehicle >= 0) {
1151 // The pair was inserted on this vehicle.
1152 const int64_t vehicle_start = model()->Start(compatible_vehicle);
1153 const int num_previous_vehicle_entries =
1154 pickup_to_entries->at(vehicle_start).size() +
1155 delivery_to_entries->at(vehicle_start).size();
1156 if (!UpdateAfterPairInsertion(
1157 pair_indices, compatible_vehicle, pickup, vehicle_start, delivery,
1158 pickup, priority_queue, pickup_to_entries, delivery_to_entries)) {
1159 return false;
1160 }
1161 if (compatible_vehicle != entry_vehicle) {
1162 // The pair was inserted on another empty vehicle of the same type
1163 // and same fixed cost as entry_vehicle.
1164 // Since this vehicle is empty and has the same fixed cost as the
1165 // entry_vehicle, it shouldn't be the representative of empty vehicles
1166 // for any pickup/delivery in the priority queue.
1167 DCHECK_EQ(num_previous_vehicle_entries, 0);
1168 return true;
1169 }
1170 // The previously unused entry_vehicle is now used, so we use the next
1171 // available vehicle of the same type to compute and store insertions on
1172 // empty vehicles.
1173 const int new_empty_vehicle =
1174 empty_vehicle_type_curator_->GetLowestFixedCostVehicleOfType(
1175 empty_vehicle_type_curator_->Type(compatible_vehicle));
1176
1177 if (new_empty_vehicle >= 0) {
1178 DCHECK(VehicleIsEmpty(new_empty_vehicle));
1179 // Add node entries after this vehicle start for uninserted pairs which
1180 // don't have entries on this empty vehicle.
1181 UpdatePairPositions(pair_indices, new_empty_vehicle,
1182 model()->Start(new_empty_vehicle), priority_queue,
1183 pickup_to_entries, delivery_to_entries);
1184 }
1185 } else if (next_fixed_cost_empty_vehicle >= 0) {
1186 // Could not insert on this vehicle or any other vehicle of the same type
1187 // with the same fixed cost, but found an empty vehicle of this type with
1188 // higher fixed cost.
1189 DCHECK(VehicleIsEmpty(next_fixed_cost_empty_vehicle));
1190 // Update the pair entry to correspond to an insertion on this
1191 // next_fixed_cost_empty_vehicle instead of the previous entry_vehicle.
1192 pair_entry->set_vehicle(next_fixed_cost_empty_vehicle);
1193 pickup_to_entries->at(pair_entry->pickup_insert_after()).erase(pair_entry);
1194 pair_entry->set_pickup_insert_after(
1195 model()->Start(next_fixed_cost_empty_vehicle));
1196 pickup_to_entries->at(pair_entry->pickup_insert_after()).insert(pair_entry);
1197 DCHECK_EQ(pair_entry->delivery_insert_after(), pickup);
1198 UpdatePairEntry(pair_entry, priority_queue);
1199 } else {
1200 DeletePairEntry(pair_entry, priority_queue, pickup_to_entries,
1201 delivery_to_entries);
1202 }
1203
1204 return true;
1205}
1206
1207bool GlobalCheapestInsertionFilteredHeuristic::InsertNodesOnRoutes(
1208 const std::map<int64_t, std::vector<int>>& nodes_by_bucket,
1209 const absl::flat_hash_set<int>& vehicles) {
1211 std::vector<NodeEntries> position_to_node_entries;
1212 std::vector<int> nodes_to_insert;
1213 for (const auto& [bucket, nodes] : nodes_by_bucket) {
1214 nodes_to_insert.insert(nodes_to_insert.end(), nodes.begin(), nodes.end());
1215 if (!InitializePositions(nodes_to_insert, vehicles, &priority_queue,
1216 &position_to_node_entries)) {
1217 return false;
1218 }
1219 // The following boolean indicates whether or not all vehicles are being
1220 // considered for insertion of the nodes simultaneously.
1221 // In the sequential version of the heuristic, as well as when inserting
1222 // single pickup or deliveries from pickup/delivery pairs, this will be
1223 // false. In the general parallel version of the heuristic, all_vehicles is
1224 // true.
1225 const bool all_vehicles =
1226 vehicles.empty() || vehicles.size() == model()->vehicles();
1227
1228 while (!priority_queue.IsEmpty()) {
1229 NodeEntry* const node_entry = priority_queue.Top();
1230 if (StopSearchAndCleanup(&priority_queue)) {
1231 return false;
1232 }
1233 const int64_t node_to_insert = node_entry->node_to_insert();
1234 if (Contains(node_to_insert)) {
1235 DeleteNodeEntry(node_entry, &priority_queue, &position_to_node_entries);
1236 continue;
1237 }
1238
1239 const int entry_vehicle = node_entry->vehicle();
1240 if (entry_vehicle == -1) {
1241 DCHECK(all_vehicles);
1242 // Make node unperformed.
1243 SetValue(node_to_insert, node_to_insert);
1244 if (!Evaluate(/*commit=*/true).has_value()) {
1245 DeleteNodeEntry(node_entry, &priority_queue,
1246 &position_to_node_entries);
1247 }
1248 continue;
1249 }
1250
1251 // Make node performed.
1252 if (UseEmptyVehicleTypeCuratorForVehicle(entry_vehicle, all_vehicles)) {
1253 DCHECK(all_vehicles);
1254 if (!InsertNodeEntryUsingEmptyVehicleTypeCurator(
1255 nodes_to_insert, all_vehicles, node_entry, &priority_queue,
1256 &position_to_node_entries)) {
1257 return false;
1258 }
1259 continue;
1260 }
1261
1262 const int64_t insert_after = node_entry->insert_after();
1263 InsertBetween(node_to_insert, insert_after, Value(insert_after));
1264 if (Evaluate(/*commit=*/true).has_value()) {
1265 if (!UpdatePositions(nodes_to_insert, entry_vehicle, node_to_insert,
1266 all_vehicles, &priority_queue,
1267 &position_to_node_entries) ||
1268 !UpdatePositions(nodes_to_insert, entry_vehicle, insert_after,
1269 all_vehicles, &priority_queue,
1270 &position_to_node_entries)) {
1271 return false;
1272 }
1273 SetVehicleIndex(node_to_insert, entry_vehicle);
1274 } else {
1275 DeleteNodeEntry(node_entry, &priority_queue, &position_to_node_entries);
1276 }
1277 }
1278 // In case all nodes could not be inserted, pushing uninserted ones to the
1279 // next bucket.
1280 nodes_to_insert.erase(
1281 std::remove_if(nodes_to_insert.begin(), nodes_to_insert.end(),
1282 [this](int node) { return Contains(node); }),
1283 nodes_to_insert.end());
1284 }
1285 return true;
1286}
1287
1288bool GlobalCheapestInsertionFilteredHeuristic::
1289 InsertNodeEntryUsingEmptyVehicleTypeCurator(
1290 const std::vector<int>& nodes, bool all_vehicles,
1291 GlobalCheapestInsertionFilteredHeuristic::NodeEntry* const node_entry,
1293 GlobalCheapestInsertionFilteredHeuristic::NodeEntry>*
1294 priority_queue,
1295 std::vector<GlobalCheapestInsertionFilteredHeuristic::NodeEntries>*
1296 position_to_node_entries) {
1297 const int entry_vehicle = node_entry->vehicle();
1298 DCHECK(UseEmptyVehicleTypeCuratorForVehicle(entry_vehicle, all_vehicles));
1299
1300 // Trying to insert on an empty vehicle, and all vehicles are being
1301 // considered simultaneously.
1302 // As we only have one node_entry per type, we try inserting on all vehicles
1303 // of this type with the same fixed cost as they all have the same insertion
1304 // value.
1305 const int64_t node_to_insert = node_entry->node_to_insert();
1306 const int64_t entry_fixed_cost =
1307 model()->GetFixedCostOfVehicle(entry_vehicle);
1308 auto vehicle_is_compatible = [this, entry_fixed_cost,
1309 node_to_insert](int vehicle) {
1310 if (model()->GetFixedCostOfVehicle(vehicle) != entry_fixed_cost) {
1311 return false;
1312 }
1313 // NOTE: Only empty vehicles should be in the vehicle_curator_.
1314 DCHECK(VehicleIsEmpty(vehicle));
1315 InsertBetween(node_to_insert, model()->Start(vehicle),
1316 model()->End(vehicle), vehicle);
1317 return Evaluate(/*commit=*/true).has_value();
1318 };
1319 // Since the vehicles of the same type are sorted by increasing fixed
1320 // cost by the curator, we can stop as soon as an empty vehicle with a fixed
1321 // cost higher than the entry_fixed_cost is found, and add new entries for
1322 // this new vehicle.
1323 auto stop_and_return_vehicle = [this, entry_fixed_cost](int vehicle) {
1324 return model()->GetFixedCostOfVehicle(vehicle) > entry_fixed_cost;
1325 };
1326 const auto [compatible_vehicle, next_fixed_cost_empty_vehicle] =
1327 empty_vehicle_type_curator_->GetCompatibleVehicleOfType(
1328 empty_vehicle_type_curator_->Type(entry_vehicle),
1329 vehicle_is_compatible, stop_and_return_vehicle);
1330 if (compatible_vehicle >= 0) {
1331 // The node was inserted on this vehicle.
1332 if (!UpdatePositions(nodes, compatible_vehicle, node_to_insert,
1333 all_vehicles, priority_queue,
1334 position_to_node_entries)) {
1335 return false;
1336 }
1337 const int64_t compatible_start = model()->Start(compatible_vehicle);
1338 const bool no_prior_entries_for_this_vehicle =
1339 position_to_node_entries->at(compatible_start).empty();
1340 if (!UpdatePositions(nodes, compatible_vehicle, compatible_start,
1341 all_vehicles, priority_queue,
1342 position_to_node_entries)) {
1343 return false;
1344 }
1345 SetVehicleIndex(node_to_insert, compatible_vehicle);
1346 if (compatible_vehicle != entry_vehicle) {
1347 // The node was inserted on another empty vehicle of the same type
1348 // and same fixed cost as entry_vehicle.
1349 // Since this vehicle is empty and has the same fixed cost as the
1350 // entry_vehicle, it shouldn't be the representative of empty vehicles
1351 // for any node in the priority queue.
1352 DCHECK(no_prior_entries_for_this_vehicle);
1353 return true;
1354 }
1355 // The previously unused entry_vehicle is now used, so we use the next
1356 // available vehicle of the same type to compute and store insertions on
1357 // empty vehicles.
1358 const int new_empty_vehicle =
1359 empty_vehicle_type_curator_->GetLowestFixedCostVehicleOfType(
1360 empty_vehicle_type_curator_->Type(compatible_vehicle));
1361
1362 if (new_empty_vehicle >= 0) {
1363 DCHECK(VehicleIsEmpty(new_empty_vehicle));
1364 // Add node entries after this vehicle start for uninserted nodes which
1365 // don't have entries on this empty vehicle.
1366 if (!UpdatePositions(nodes, new_empty_vehicle,
1367 model()->Start(new_empty_vehicle), all_vehicles,
1368 priority_queue, position_to_node_entries)) {
1369 return false;
1370 }
1371 }
1372 } else if (next_fixed_cost_empty_vehicle >= 0) {
1373 // Could not insert on this vehicle or any other vehicle of the same
1374 // type with the same fixed cost, but found an empty vehicle of this type
1375 // with higher fixed cost.
1376 DCHECK(VehicleIsEmpty(next_fixed_cost_empty_vehicle));
1377 // Update the insertion entry to be on next_empty_vehicle instead of the
1378 // previous entry_vehicle.
1379 position_to_node_entries->at(node_entry->insert_after()).erase(node_entry);
1380 node_entry->set_insert_after(model()->Start(next_fixed_cost_empty_vehicle));
1381 position_to_node_entries->at(node_entry->insert_after()).insert(node_entry);
1382 node_entry->set_vehicle(next_fixed_cost_empty_vehicle);
1383 UpdateNodeEntry(node_entry, priority_queue);
1384 } else {
1385 DeleteNodeEntry(node_entry, priority_queue, position_to_node_entries);
1386 }
1387
1388 return true;
1389}
1390
1391bool GlobalCheapestInsertionFilteredHeuristic::SequentialInsertNodes(
1392 const std::map<int64_t, std::vector<int>>& nodes_by_bucket) {
1393 std::vector<bool> is_vehicle_used;
1394 absl::flat_hash_set<int> used_vehicles;
1395 std::vector<int> unused_vehicles;
1396
1397 DetectUsedVehicles(&is_vehicle_used, &unused_vehicles, &used_vehicles);
1398 if (!used_vehicles.empty() &&
1399 !InsertNodesOnRoutes(nodes_by_bucket, used_vehicles)) {
1400 return false;
1401 }
1402
1403 std::vector<std::vector<StartEndValue>> start_end_distances_per_node =
1404 ComputeStartEndDistanceForVehicles(unused_vehicles);
1405 std::priority_queue<Seed, std::vector<Seed>, std::greater<Seed>>
1406 first_node_queue;
1407 InitializePriorityQueue(&start_end_distances_per_node, &first_node_queue);
1408
1409 int vehicle = InsertSeedNode(&start_end_distances_per_node, &first_node_queue,
1410 &is_vehicle_used);
1411
1412 while (vehicle >= 0) {
1413 if (!InsertNodesOnRoutes(nodes_by_bucket, {vehicle})) {
1414 return false;
1415 }
1416 vehicle = InsertSeedNode(&start_end_distances_per_node, &first_node_queue,
1417 &is_vehicle_used);
1418 }
1419 return true;
1420}
1421
1422void GlobalCheapestInsertionFilteredHeuristic::DetectUsedVehicles(
1423 std::vector<bool>* is_vehicle_used, std::vector<int>* unused_vehicles,
1424 absl::flat_hash_set<int>* used_vehicles) {
1425 is_vehicle_used->clear();
1426 is_vehicle_used->resize(model()->vehicles());
1427
1428 used_vehicles->clear();
1429 used_vehicles->reserve(model()->vehicles());
1430
1431 unused_vehicles->clear();
1432 unused_vehicles->reserve(model()->vehicles());
1433
1434 for (int vehicle = 0; vehicle < model()->vehicles(); vehicle++) {
1435 if (!VehicleIsEmpty(vehicle)) {
1436 (*is_vehicle_used)[vehicle] = true;
1437 used_vehicles->insert(vehicle);
1438 } else {
1439 (*is_vehicle_used)[vehicle] = false;
1440 unused_vehicles->push_back(vehicle);
1441 }
1442 }
1443}
1444
1445void GlobalCheapestInsertionFilteredHeuristic::InsertFarthestNodesAsSeeds() {
1446 if (gci_params_.farthest_seeds_ratio <= 0) return;
1447 // Insert at least 1 farthest Seed if the parameter is positive.
1448 const int num_seeds = static_cast<int>(
1449 std::ceil(gci_params_.farthest_seeds_ratio * model()->vehicles()));
1450
1451 std::vector<bool> is_vehicle_used;
1452 absl::flat_hash_set<int> used_vehicles;
1453 std::vector<int> unused_vehicles;
1454 DetectUsedVehicles(&is_vehicle_used, &unused_vehicles, &used_vehicles);
1455 std::vector<std::vector<StartEndValue>> start_end_distances_per_node =
1456 ComputeStartEndDistanceForVehicles(unused_vehicles);
1457
1458 // Priority queue where the Seeds with a larger distance are given higher
1459 // priority.
1460 std::priority_queue<Seed> farthest_node_queue;
1461 InitializePriorityQueue(&start_end_distances_per_node, &farthest_node_queue);
1462
1463 int inserted_seeds = 0;
1464 while (inserted_seeds++ < num_seeds) {
1465 if (InsertSeedNode(&start_end_distances_per_node, &farthest_node_queue,
1466 &is_vehicle_used) < 0) {
1467 break;
1468 }
1469 }
1470
1471 // NOTE: As we don't use the empty_vehicle_type_curator_ when inserting seed
1472 // nodes on routes, some previously empty vehicles may now be used, so we
1473 // update the curator accordingly to ensure it still only stores empty
1474 // vehicles.
1475 DCHECK(empty_vehicle_type_curator_ != nullptr);
1476 empty_vehicle_type_curator_->Update(
1477 [this](int vehicle) { return !VehicleIsEmpty(vehicle); });
1478}
1479
1480template <class Queue>
1481int GlobalCheapestInsertionFilteredHeuristic::InsertSeedNode(
1482 std::vector<std::vector<StartEndValue>>* start_end_distances_per_node,
1483 Queue* priority_queue, std::vector<bool>* is_vehicle_used) {
1484 while (!priority_queue->empty()) {
1485 if (StopSearch()) return -1;
1486 const Seed& seed = priority_queue->top();
1487
1488 const int seed_node = seed.second;
1489 const int seed_vehicle = seed.first.vehicle;
1490
1491 std::vector<StartEndValue>& other_start_end_values =
1492 (*start_end_distances_per_node)[seed_node];
1493
1494 if (Contains(seed_node)) {
1495 // The node is already inserted, it is therefore no longer considered as
1496 // a potential seed.
1497 priority_queue->pop();
1498 other_start_end_values.clear();
1499 continue;
1500 }
1501 if (!(*is_vehicle_used)[seed_vehicle]) {
1502 // Try to insert this seed_node on this vehicle's route.
1503 const int64_t start = model()->Start(seed_vehicle);
1504 const int64_t end = model()->End(seed_vehicle);
1506 InsertBetween(seed_node, start, end, seed_vehicle);
1507 if (Evaluate(/*commit=*/true).has_value()) {
1508 priority_queue->pop();
1509 (*is_vehicle_used)[seed_vehicle] = true;
1510 other_start_end_values.clear();
1511 SetVehicleIndex(seed_node, seed_vehicle);
1512 return seed_vehicle;
1513 }
1514 }
1515 // Either the vehicle is already used, or the Commit() wasn't successful.
1516 // In both cases, we remove this Seed from the priority queue, and insert
1517 // the next StartEndValue from start_end_distances_per_node[seed_node]
1518 // in the priority queue.
1519 priority_queue->pop();
1520 if (!other_start_end_values.empty()) {
1521 const StartEndValue& next_seed_value = other_start_end_values.back();
1522 priority_queue->push(std::make_pair(next_seed_value, seed_node));
1523 other_start_end_values.pop_back();
1524 }
1525 }
1526 // No seed node was inserted.
1527 return -1;
1528}
1529
1530bool GlobalCheapestInsertionFilteredHeuristic::InitializePairPositions(
1531 const std::vector<int>& pair_indices,
1533 GlobalCheapestInsertionFilteredHeuristic::PairEntry>* priority_queue,
1534 std::vector<GlobalCheapestInsertionFilteredHeuristic::PairEntries>*
1535 pickup_to_entries,
1536 std::vector<GlobalCheapestInsertionFilteredHeuristic::PairEntries>*
1537 delivery_to_entries) {
1538 priority_queue->Clear();
1539 pickup_to_entries->clear();
1540 pickup_to_entries->resize(model()->Size());
1541 delivery_to_entries->clear();
1542 delivery_to_entries->resize(model()->Size());
1543 const RoutingModel::IndexPairs& pickup_delivery_pairs =
1544 model()->GetPickupAndDeliveryPairs();
1545 for (int index : pair_indices) {
1546 const RoutingModel::IndexPair& index_pair = pickup_delivery_pairs[index];
1547 for (int64_t pickup : index_pair.first) {
1548 if (Contains(pickup)) continue;
1549 for (int64_t delivery : index_pair.second) {
1550 if (Contains(delivery)) continue;
1551 if (StopSearchAndCleanup(priority_queue)) return false;
1552 // Add insertion entry making pair unperformed. When the pair is part
1553 // of a disjunction we do not try to make any of its pairs unperformed
1554 // as it requires having an entry with all pairs being unperformed.
1555 // TODO(user): Adapt the code to make pair disjunctions unperformed.
1556 if (gci_params_.add_unperformed_entries &&
1557 index_pair.first.size() == 1 && index_pair.second.size() == 1 &&
1558 GetUnperformedValue(pickup) !=
1560 GetUnperformedValue(delivery) !=
1562 AddPairEntry(pickup, -1, delivery, -1, -1, priority_queue, nullptr,
1563 nullptr);
1564 }
1565 // Add all other insertion entries with pair performed.
1566 InitializeInsertionEntriesPerformingPair(
1567 pickup, delivery, priority_queue, pickup_to_entries,
1568 delivery_to_entries);
1569 }
1570 }
1571 }
1572 return true;
1573}
1574
1575void GlobalCheapestInsertionFilteredHeuristic::
1576 InitializeInsertionEntriesPerformingPair(
1577 int64_t pickup, int64_t delivery,
1579 GlobalCheapestInsertionFilteredHeuristic::PairEntry>*
1580 priority_queue,
1581 std::vector<GlobalCheapestInsertionFilteredHeuristic::PairEntries>*
1582 pickup_to_entries,
1583 std::vector<GlobalCheapestInsertionFilteredHeuristic::PairEntries>*
1584 delivery_to_entries) {
1585 if (!gci_params_.use_neighbors_ratio_for_initialization) {
1586 struct PairInsertion {
1587 int64_t insert_pickup_after;
1588 int64_t insert_delivery_after;
1589 int vehicle;
1590 };
1591 std::vector<PairInsertion> pair_insertions;
1592 for (int vehicle = 0; vehicle < model()->vehicles(); ++vehicle) {
1593 if (VehicleIsEmpty(vehicle) &&
1594 empty_vehicle_type_curator_->GetLowestFixedCostVehicleOfType(
1595 empty_vehicle_type_curator_->Type(vehicle)) != vehicle) {
1596 // We only consider the least expensive empty vehicle of each type for
1597 // entries.
1598 continue;
1599 }
1600 const int64_t start = model()->Start(vehicle);
1601 std::vector<NodeInsertion> pickup_insertions;
1602 AppendInsertionPositionsAfter(pickup, start, Value(start), vehicle,
1603 &pickup_insertions);
1604 for (const auto [insert_pickup_after, pickup_vehicle,
1605 unused_pickup_value] : pickup_insertions) {
1606 CHECK(!model()->IsEnd(insert_pickup_after));
1607 std::vector<NodeInsertion> delivery_insertions;
1608 AppendInsertionPositionsAfter(delivery, pickup,
1609 Value(insert_pickup_after), vehicle,
1610 &delivery_insertions);
1611 for (const auto [insert_delivery_after, delivery_vehicle,
1612 unused_delivery_value] : delivery_insertions) {
1613 pair_insertions.push_back(
1614 {insert_pickup_after, insert_delivery_after, vehicle});
1615 }
1616 }
1617 }
1618 for (const auto& [insert_pickup_after, insert_delivery_after, vehicle] :
1619 pair_insertions) {
1620 DCHECK_NE(insert_pickup_after, insert_delivery_after);
1621 AddPairEntry(pickup, insert_pickup_after, delivery, insert_delivery_after,
1622 vehicle, priority_queue, pickup_to_entries,
1623 delivery_to_entries);
1624 }
1625 return;
1626 }
1627
1628 // We're only considering the closest neighbors as insertion positions for
1629 // the pickup/delivery pair.
1630 for (int cost_class = 0; cost_class < model()->GetCostClassesCount();
1631 cost_class++) {
1632 absl::flat_hash_set<std::pair<int64_t, int64_t>>
1633 existing_insertion_positions;
1634 // Explore the neighborhood of the pickup.
1635 for (const int64_t pickup_insert_after :
1636 GetNeighborsOfNodeForCostClass(cost_class, pickup)) {
1637 if (!Contains(pickup_insert_after)) {
1638 continue;
1639 }
1640 const int vehicle = node_index_to_vehicle_[pickup_insert_after];
1641 if (vehicle < 0 ||
1642 model()->GetCostClassIndexOfVehicle(vehicle).value() != cost_class) {
1643 continue;
1644 }
1645
1646 if (VehicleIsEmpty(vehicle) &&
1647 empty_vehicle_type_curator_->GetLowestFixedCostVehicleOfType(
1648 empty_vehicle_type_curator_->Type(vehicle)) != vehicle) {
1649 // We only consider the least expensive empty vehicle of each type for
1650 // entries.
1651 continue;
1652 }
1653
1654 int64_t delivery_insert_after = pickup;
1655 while (!model()->IsEnd(delivery_insert_after)) {
1656 const std::pair<int64_t, int64_t> insertion_position = {
1657 pickup_insert_after, delivery_insert_after};
1658 DCHECK(!existing_insertion_positions.contains(insertion_position));
1659 existing_insertion_positions.insert(insertion_position);
1660
1661 AddPairEntry(pickup, pickup_insert_after, delivery,
1662 delivery_insert_after, vehicle, priority_queue,
1663 pickup_to_entries, delivery_to_entries);
1664 delivery_insert_after = (delivery_insert_after == pickup)
1665 ? Value(pickup_insert_after)
1666 : Value(delivery_insert_after);
1667 }
1668 }
1669
1670 // Explore the neighborhood of the delivery.
1671 for (const int64_t delivery_insert_after :
1672 GetNeighborsOfNodeForCostClass(cost_class, delivery)) {
1673 if (!Contains(delivery_insert_after)) {
1674 continue;
1675 }
1676 const int vehicle = node_index_to_vehicle_[delivery_insert_after];
1677 if (vehicle < 0 ||
1678 model()->GetCostClassIndexOfVehicle(vehicle).value() != cost_class) {
1679 continue;
1680 }
1681
1682 if (VehicleIsEmpty(vehicle)) {
1683 // Vehicle is empty.
1684 DCHECK_EQ(delivery_insert_after, model()->Start(vehicle));
1685 }
1686
1687 int64_t pickup_insert_after = model()->Start(vehicle);
1688 while (pickup_insert_after != delivery_insert_after) {
1689 if (!existing_insertion_positions.contains(
1690 std::make_pair(pickup_insert_after, delivery_insert_after))) {
1691 AddPairEntry(pickup, pickup_insert_after, delivery,
1692 delivery_insert_after, vehicle, priority_queue,
1693 pickup_to_entries, delivery_to_entries);
1694 }
1695 pickup_insert_after = Value(pickup_insert_after);
1696 }
1697 }
1698 }
1699}
1700
1701bool GlobalCheapestInsertionFilteredHeuristic::UpdateAfterPairInsertion(
1702 const std::vector<int>& pair_indices, int vehicle, int64_t pickup,
1703 int64_t pickup_position, int64_t delivery, int64_t delivery_position,
1704 AdjustablePriorityQueue<PairEntry>* priority_queue,
1705 std::vector<PairEntries>* pickup_to_entries,
1706 std::vector<PairEntries>* delivery_to_entries) {
1707 if (!UpdatePairPositions(pair_indices, vehicle, pickup_position,
1708 priority_queue, pickup_to_entries,
1709 delivery_to_entries) ||
1710 !UpdatePairPositions(pair_indices, vehicle, pickup, priority_queue,
1711 pickup_to_entries, delivery_to_entries) ||
1712 !UpdatePairPositions(pair_indices, vehicle, delivery, priority_queue,
1713 pickup_to_entries, delivery_to_entries)) {
1714 return false;
1715 }
1716 if (delivery_position != pickup &&
1717 !UpdatePairPositions(pair_indices, vehicle, delivery_position,
1718 priority_queue, pickup_to_entries,
1719 delivery_to_entries)) {
1720 return false;
1721 }
1722 SetVehicleIndex(pickup, vehicle);
1723 SetVehicleIndex(delivery, vehicle);
1724 return true;
1725}
1726
1727bool GlobalCheapestInsertionFilteredHeuristic::UpdatePickupPositions(
1728 const std::vector<int>& pair_indices, int vehicle,
1729 int64_t pickup_insert_after,
1731 GlobalCheapestInsertionFilteredHeuristic::PairEntry>* priority_queue,
1732 std::vector<GlobalCheapestInsertionFilteredHeuristic::PairEntries>*
1733 pickup_to_entries,
1734 std::vector<GlobalCheapestInsertionFilteredHeuristic::PairEntries>*
1735 delivery_to_entries) {
1736 // First, remove entries which have already been inserted and keep track of
1737 // the entries which are being kept and must be updated.
1738 using Pair = std::pair<int64_t, int64_t>;
1739 using Insertion = std::pair<Pair, /*delivery_insert_after*/ int64_t>;
1740 absl::flat_hash_set<Insertion> existing_insertions;
1741 std::vector<PairEntry*> to_remove;
1742 for (PairEntry* const pair_entry :
1743 pickup_to_entries->at(pickup_insert_after)) {
1744 DCHECK(priority_queue->Contains(pair_entry));
1745 DCHECK_EQ(pair_entry->pickup_insert_after(), pickup_insert_after);
1746 if (Contains(pair_entry->pickup_to_insert()) ||
1747 Contains(pair_entry->delivery_to_insert())) {
1748 to_remove.push_back(pair_entry);
1749 } else {
1750 DCHECK(delivery_to_entries->at(pair_entry->delivery_insert_after())
1751 .contains(pair_entry));
1752 UpdatePairEntry(pair_entry, priority_queue);
1753 existing_insertions.insert(
1754 {{pair_entry->pickup_to_insert(), pair_entry->delivery_to_insert()},
1755 pair_entry->delivery_insert_after()});
1756 }
1757 }
1758 for (PairEntry* const pair_entry : to_remove) {
1759 DeletePairEntry(pair_entry, priority_queue, pickup_to_entries,
1760 delivery_to_entries);
1761 }
1762 // Create new entries for which the pickup is to be inserted after
1763 // pickup_insert_after.
1764 const int cost_class = model()->GetCostClassIndexOfVehicle(vehicle).value();
1765 const int64_t pickup_insert_before = Value(pickup_insert_after);
1766 const RoutingModel::IndexPairs& pickup_delivery_pairs =
1767 model()->GetPickupAndDeliveryPairs();
1768 for (int pair_index : pair_indices) {
1769 if (StopSearchAndCleanup(priority_queue)) return false;
1770 const RoutingModel::IndexPair& index_pair =
1771 pickup_delivery_pairs[pair_index];
1772 for (int64_t pickup : index_pair.first) {
1773 if (Contains(pickup) ||
1774 !IsNeighborForCostClass(cost_class, pickup_insert_after, pickup)) {
1775 continue;
1776 }
1777 for (int64_t delivery : index_pair.second) {
1778 if (Contains(delivery)) {
1779 continue;
1780 }
1781 int64_t delivery_insert_after = pickup;
1782 while (!model()->IsEnd(delivery_insert_after)) {
1783 const Insertion insertion = {{pickup, delivery},
1784 delivery_insert_after};
1785 if (!existing_insertions.contains(insertion)) {
1786 AddPairEntry(pickup, pickup_insert_after, delivery,
1787 delivery_insert_after, vehicle, priority_queue,
1788 pickup_to_entries, delivery_to_entries);
1789 }
1790 if (delivery_insert_after == pickup) {
1791 delivery_insert_after = pickup_insert_before;
1792 } else {
1793 delivery_insert_after = Value(delivery_insert_after);
1794 }
1795 }
1796 }
1797 }
1798 }
1799 return true;
1800}
1801
1802bool GlobalCheapestInsertionFilteredHeuristic::UpdateDeliveryPositions(
1803 const std::vector<int>& pair_indices, int vehicle,
1804 int64_t delivery_insert_after,
1806 GlobalCheapestInsertionFilteredHeuristic::PairEntry>* priority_queue,
1807 std::vector<GlobalCheapestInsertionFilteredHeuristic::PairEntries>*
1808 pickup_to_entries,
1809 std::vector<GlobalCheapestInsertionFilteredHeuristic::PairEntries>*
1810 delivery_to_entries) {
1811 // First, remove entries which have already been inserted and keep track of
1812 // the entries which are being kept and must be updated.
1813 using Pair = std::pair<int64_t, int64_t>;
1814 using Insertion = std::pair<Pair, /*pickup_insert_after*/ int64_t>;
1815 absl::flat_hash_set<Insertion> existing_insertions;
1816 std::vector<PairEntry*> to_remove;
1817 for (PairEntry* const pair_entry :
1818 delivery_to_entries->at(delivery_insert_after)) {
1819 DCHECK(priority_queue->Contains(pair_entry));
1820 DCHECK_EQ(pair_entry->delivery_insert_after(), delivery_insert_after);
1821 if (Contains(pair_entry->pickup_to_insert()) ||
1822 Contains(pair_entry->delivery_to_insert())) {
1823 to_remove.push_back(pair_entry);
1824 } else {
1825 DCHECK(pickup_to_entries->at(pair_entry->pickup_insert_after())
1826 .contains(pair_entry));
1827 existing_insertions.insert(
1828 {{pair_entry->pickup_to_insert(), pair_entry->delivery_to_insert()},
1829 pair_entry->pickup_insert_after()});
1830 UpdatePairEntry(pair_entry, priority_queue);
1831 }
1832 }
1833 for (PairEntry* const pair_entry : to_remove) {
1834 DeletePairEntry(pair_entry, priority_queue, pickup_to_entries,
1835 delivery_to_entries);
1836 }
1837 // Create new entries for which the delivery is to be inserted after
1838 // delivery_insert_after.
1839 const int cost_class = model()->GetCostClassIndexOfVehicle(vehicle).value();
1840 const RoutingModel::IndexPairs& pickup_delivery_pairs =
1841 model()->GetPickupAndDeliveryPairs();
1842 for (int pair_index : pair_indices) {
1843 if (StopSearchAndCleanup(priority_queue)) return false;
1844 const RoutingModel::IndexPair& index_pair =
1845 pickup_delivery_pairs[pair_index];
1846 for (int64_t delivery : index_pair.second) {
1847 if (Contains(delivery) ||
1848 !IsNeighborForCostClass(cost_class, delivery_insert_after,
1849 delivery)) {
1850 continue;
1851 }
1852 for (int64_t pickup : index_pair.first) {
1853 if (Contains(pickup)) {
1854 continue;
1855 }
1856 int64_t pickup_insert_after = model()->Start(vehicle);
1857 while (pickup_insert_after != delivery_insert_after) {
1858 const Insertion insertion = {{pickup, delivery}, pickup_insert_after};
1859 if (!existing_insertions.contains(insertion)) {
1860 AddPairEntry(pickup, pickup_insert_after, delivery,
1861 delivery_insert_after, vehicle, priority_queue,
1862 pickup_to_entries, delivery_to_entries);
1863 }
1864 pickup_insert_after = Value(pickup_insert_after);
1865 }
1866 }
1867 }
1868 }
1869 return true;
1870}
1871
1872void GlobalCheapestInsertionFilteredHeuristic::DeletePairEntry(
1873 GlobalCheapestInsertionFilteredHeuristic::PairEntry* entry,
1875 GlobalCheapestInsertionFilteredHeuristic::PairEntry>* priority_queue,
1876 std::vector<PairEntries>* pickup_to_entries,
1877 std::vector<PairEntries>* delivery_to_entries) {
1878 priority_queue->Remove(entry);
1879 if (entry->pickup_insert_after() != -1) {
1880 pickup_to_entries->at(entry->pickup_insert_after()).erase(entry);
1881 }
1882 if (entry->delivery_insert_after() != -1) {
1883 delivery_to_entries->at(entry->delivery_insert_after()).erase(entry);
1884 }
1885 delete entry;
1886}
1887
1888void GlobalCheapestInsertionFilteredHeuristic::AddPairEntry(
1889 int64_t pickup, int64_t pickup_insert_after, int64_t delivery,
1890 int64_t delivery_insert_after, int vehicle,
1892 GlobalCheapestInsertionFilteredHeuristic::PairEntry>* priority_queue,
1893 std::vector<GlobalCheapestInsertionFilteredHeuristic::PairEntries>*
1894 pickup_entries,
1895 std::vector<GlobalCheapestInsertionFilteredHeuristic::PairEntries>*
1896 delivery_entries) const {
1897 const IntVar* pickup_vehicle_var = model()->VehicleVar(pickup);
1898 const IntVar* delivery_vehicle_var = model()->VehicleVar(delivery);
1899 if (!pickup_vehicle_var->Contains(vehicle) ||
1900 !delivery_vehicle_var->Contains(vehicle)) {
1901 if (vehicle == -1 || !VehicleIsEmpty(vehicle)) return;
1902 // We need to check there is not an equivalent empty vehicle the pair
1903 // could fit on.
1904 const auto vehicle_is_compatible = [pickup_vehicle_var,
1905 delivery_vehicle_var](int vehicle) {
1906 return pickup_vehicle_var->Contains(vehicle) &&
1907 delivery_vehicle_var->Contains(vehicle);
1908 };
1909 if (!empty_vehicle_type_curator_->HasCompatibleVehicleOfType(
1910 empty_vehicle_type_curator_->Type(vehicle),
1911 vehicle_is_compatible)) {
1912 return;
1913 }
1914 }
1915 const int num_allowed_vehicles =
1916 std::min(pickup_vehicle_var->Size(), delivery_vehicle_var->Size());
1917 if (pickup_insert_after == -1) {
1918 DCHECK_EQ(delivery_insert_after, -1);
1919 DCHECK_EQ(vehicle, -1);
1920 PairEntry* pair_entry =
1921 new PairEntry(pickup, -1, delivery, -1, -1, num_allowed_vehicles);
1922 pair_entry->set_value(
1923 absl::GetFlag(FLAGS_routing_shift_insertion_cost_by_penalty)
1924 ? 0
1925 : CapAdd(GetUnperformedValue(pickup),
1926 GetUnperformedValue(delivery)));
1927 priority_queue->Add(pair_entry);
1928 return;
1929 }
1930
1931 PairEntry* const pair_entry =
1932 new PairEntry(pickup, pickup_insert_after, delivery,
1933 delivery_insert_after, vehicle, num_allowed_vehicles);
1934 pair_entry->set_value(GetInsertionValueForPairAtPositions(
1935 pickup, pickup_insert_after, delivery, delivery_insert_after, vehicle));
1936
1937 // Add entry to priority_queue and pickup_/delivery_entries.
1938 DCHECK(!priority_queue->Contains(pair_entry));
1939 pickup_entries->at(pickup_insert_after).insert(pair_entry);
1940 delivery_entries->at(delivery_insert_after).insert(pair_entry);
1941 priority_queue->Add(pair_entry);
1942}
1943
1944void GlobalCheapestInsertionFilteredHeuristic::UpdatePairEntry(
1945 GlobalCheapestInsertionFilteredHeuristic::PairEntry* const pair_entry,
1947 GlobalCheapestInsertionFilteredHeuristic::PairEntry>* priority_queue)
1948 const {
1949 pair_entry->set_value(GetInsertionValueForPairAtPositions(
1950 pair_entry->pickup_to_insert(), pair_entry->pickup_insert_after(),
1951 pair_entry->delivery_to_insert(), pair_entry->delivery_insert_after(),
1952 pair_entry->vehicle()));
1953
1954 // Update the priority_queue.
1955 DCHECK(priority_queue->Contains(pair_entry));
1956 priority_queue->NoteChangedPriority(pair_entry);
1957}
1958
1959int64_t
1960GlobalCheapestInsertionFilteredHeuristic::GetInsertionValueForPairAtPositions(
1961 int64_t pickup, int64_t pickup_insert_after, int64_t delivery,
1962 int64_t delivery_insert_after, int vehicle) const {
1963 DCHECK_GE(pickup_insert_after, 0);
1964 const int64_t pickup_insert_before = Value(pickup_insert_after);
1965 const int64_t pickup_value = GetInsertionCostForNodeAtPosition(
1966 pickup, pickup_insert_after, pickup_insert_before, vehicle);
1967
1968 DCHECK_GE(delivery_insert_after, 0);
1969 const int64_t delivery_insert_before = (delivery_insert_after == pickup)
1970 ? pickup_insert_before
1971 : Value(delivery_insert_after);
1972 const int64_t delivery_value = GetInsertionCostForNodeAtPosition(
1973 delivery, delivery_insert_after, delivery_insert_before, vehicle);
1974
1975 const int64_t penalty_shift =
1976 absl::GetFlag(FLAGS_routing_shift_insertion_cost_by_penalty)
1977 ? CapAdd(GetUnperformedValue(pickup), GetUnperformedValue(delivery))
1978 : 0;
1979 return CapSub(CapAdd(pickup_value, delivery_value), penalty_shift);
1980}
1981
1982bool GlobalCheapestInsertionFilteredHeuristic::InitializePositions(
1983 const std::vector<int>& nodes, const absl::flat_hash_set<int>& vehicles,
1985 GlobalCheapestInsertionFilteredHeuristic::NodeEntry>* priority_queue,
1986 std::vector<GlobalCheapestInsertionFilteredHeuristic::NodeEntries>*
1987 position_to_node_entries) {
1988 priority_queue->Clear();
1989 position_to_node_entries->clear();
1990 position_to_node_entries->resize(model()->Size());
1991
1992 const int num_vehicles =
1993 vehicles.empty() ? model()->vehicles() : vehicles.size();
1994 const bool all_vehicles = (num_vehicles == model()->vehicles());
1995
1996 for (int node : nodes) {
1997 if (Contains(node)) {
1998 continue;
1999 }
2000 if (StopSearchAndCleanup(priority_queue)) return false;
2001 // Add insertion entry making node unperformed.
2002 if (gci_params_.add_unperformed_entries &&
2003 GetUnperformedValue(node) != std::numeric_limits<int64_t>::max()) {
2004 AddNodeEntry(node, -1, -1, all_vehicles, priority_queue, nullptr);
2005 }
2006 // Add all insertion entries making node performed.
2007 InitializeInsertionEntriesPerformingNode(node, vehicles, priority_queue,
2008 position_to_node_entries);
2009 }
2010 return true;
2011}
2012
2013void GlobalCheapestInsertionFilteredHeuristic::
2014 InitializeInsertionEntriesPerformingNode(
2015 int64_t node, const absl::flat_hash_set<int>& vehicles,
2017 GlobalCheapestInsertionFilteredHeuristic::NodeEntry>*
2018 priority_queue,
2019 std::vector<GlobalCheapestInsertionFilteredHeuristic::NodeEntries>*
2020 position_to_node_entries) {
2021 const int num_vehicles =
2022 vehicles.empty() ? model()->vehicles() : vehicles.size();
2023 const bool all_vehicles = (num_vehicles == model()->vehicles());
2024
2025 if (!gci_params_.use_neighbors_ratio_for_initialization) {
2026 auto vehicles_it = vehicles.begin();
2027 for (int v = 0; v < num_vehicles; v++) {
2028 const int vehicle = vehicles.empty() ? v : *vehicles_it++;
2029
2030 const int64_t start = model()->Start(vehicle);
2031 if (all_vehicles && VehicleIsEmpty(vehicle) &&
2032 empty_vehicle_type_curator_->GetLowestFixedCostVehicleOfType(
2033 empty_vehicle_type_curator_->Type(vehicle)) != vehicle) {
2034 // We only consider the least expensive empty vehicle of each type for
2035 // entries.
2036 continue;
2037 }
2038 std::vector<NodeInsertion> insertions;
2039 AppendInsertionPositionsAfter(node, start, Value(start), vehicle,
2040 &insertions);
2041 for (const auto [insert_after, insertion_vehicle, value] : insertions) {
2042 DCHECK_EQ(insertion_vehicle, vehicle);
2043 AddNodeEntry(node, insert_after, vehicle, all_vehicles, priority_queue,
2044 position_to_node_entries);
2045 }
2046 }
2047 return;
2048 }
2049
2050 // We're only considering the closest neighbors as insertion positions for
2051 // the node.
2052 const auto insert_on_vehicle_for_cost_class = [this, &vehicles, all_vehicles](
2053 int v, int cost_class) {
2054 return (model()->GetCostClassIndexOfVehicle(v).value() == cost_class) &&
2055 (all_vehicles || vehicles.contains(v));
2056 };
2057 for (int cost_class = 0; cost_class < model()->GetCostClassesCount();
2058 cost_class++) {
2059 for (const int64_t insert_after :
2060 GetNeighborsOfNodeForCostClass(cost_class, node)) {
2061 if (!Contains(insert_after)) {
2062 continue;
2063 }
2064 const int vehicle = node_index_to_vehicle_[insert_after];
2065 if (vehicle == -1 ||
2066 !insert_on_vehicle_for_cost_class(vehicle, cost_class)) {
2067 continue;
2068 }
2069 if (all_vehicles && VehicleIsEmpty(vehicle) &&
2070 empty_vehicle_type_curator_->GetLowestFixedCostVehicleOfType(
2071 empty_vehicle_type_curator_->Type(vehicle)) != vehicle) {
2072 // We only consider the least expensive empty vehicle of each type for
2073 // entries.
2074 continue;
2075 }
2076 AddNodeEntry(node, insert_after, vehicle, all_vehicles, priority_queue,
2077 position_to_node_entries);
2078 }
2079 }
2080}
2081
2082bool GlobalCheapestInsertionFilteredHeuristic::UpdatePositions(
2083 const std::vector<int>& nodes, int vehicle, int64_t insert_after,
2084 bool all_vehicles,
2086 GlobalCheapestInsertionFilteredHeuristic::NodeEntry>* priority_queue,
2087 std::vector<GlobalCheapestInsertionFilteredHeuristic::NodeEntries>*
2088 node_entries) {
2089 // Either create new entries if we are inserting after a newly inserted node
2090 // or remove entries which have already been inserted.
2091 std::vector<NodeEntry*> to_remove;
2092 absl::flat_hash_set<int> existing_insertions;
2093 for (NodeEntry* const node_entry : node_entries->at(insert_after)) {
2094 DCHECK_EQ(node_entry->insert_after(), insert_after);
2095 const int64_t node_to_insert = node_entry->node_to_insert();
2096 if (Contains(node_to_insert)) {
2097 to_remove.push_back(node_entry);
2098 } else {
2099 UpdateNodeEntry(node_entry, priority_queue);
2100 existing_insertions.insert(node_to_insert);
2101 }
2102 }
2103 for (NodeEntry* const node_entry : to_remove) {
2104 DeleteNodeEntry(node_entry, priority_queue, node_entries);
2105 }
2106 const int cost_class = model()->GetCostClassIndexOfVehicle(vehicle).value();
2107 for (int node_to_insert : nodes) {
2108 if (StopSearchAndCleanup(priority_queue)) return false;
2109 if (!Contains(node_to_insert) &&
2110 !existing_insertions.contains(node_to_insert) &&
2111 IsNeighborForCostClass(cost_class, insert_after, node_to_insert)) {
2112 AddNodeEntry(node_to_insert, insert_after, vehicle, all_vehicles,
2113 priority_queue, node_entries);
2114 }
2115 }
2116 return true;
2117}
2118
2119void GlobalCheapestInsertionFilteredHeuristic::DeleteNodeEntry(
2120 GlobalCheapestInsertionFilteredHeuristic::NodeEntry* entry,
2122 GlobalCheapestInsertionFilteredHeuristic::NodeEntry>* priority_queue,
2123 std::vector<NodeEntries>* node_entries) {
2124 priority_queue->Remove(entry);
2125 if (entry->insert_after() != -1) {
2126 node_entries->at(entry->insert_after()).erase(entry);
2127 }
2128 delete entry;
2129}
2130
2131void GlobalCheapestInsertionFilteredHeuristic::AddNodeEntry(
2132 int64_t node, int64_t insert_after, int vehicle, bool all_vehicles,
2133 AdjustablePriorityQueue<NodeEntry>* priority_queue,
2134 std::vector<NodeEntries>* node_entries) const {
2135 const int64_t node_penalty = GetUnperformedValue(node);
2136 const int64_t penalty_shift =
2137 absl::GetFlag(FLAGS_routing_shift_insertion_cost_by_penalty)
2138 ? node_penalty
2139 : 0;
2140 const IntVar* const vehicle_var = model()->VehicleVar(node);
2141 if (!vehicle_var->Contains(vehicle)) {
2142 if (vehicle == -1 || !VehicleIsEmpty(vehicle)) return;
2143 // We need to check there is not an equivalent empty vehicle the node
2144 // could fit on.
2145 const auto vehicle_is_compatible = [vehicle_var](int vehicle) {
2146 return vehicle_var->Contains(vehicle);
2147 };
2148 if (!empty_vehicle_type_curator_->HasCompatibleVehicleOfType(
2149 empty_vehicle_type_curator_->Type(vehicle),
2150 vehicle_is_compatible)) {
2151 return;
2152 }
2153 }
2154 const int num_allowed_vehicles = vehicle_var->Size();
2155 if (insert_after == -1) {
2156 DCHECK_EQ(vehicle, -1);
2157 if (!all_vehicles) {
2158 // NOTE: In the case where we're not considering all routes
2159 // simultaneously, we don't add insertion entries making nodes
2160 // unperformed.
2161 return;
2162 }
2163 NodeEntry* const node_entry =
2164 new NodeEntry(node, -1, -1, num_allowed_vehicles);
2165 node_entry->set_value(CapSub(node_penalty, penalty_shift));
2166 priority_queue->Add(node_entry);
2167 return;
2168 }
2169
2170 const int64_t insertion_cost = GetInsertionCostForNodeAtPosition(
2171 node, insert_after, Value(insert_after), vehicle);
2172 if (!all_vehicles && insertion_cost > node_penalty) {
2173 // NOTE: When all vehicles aren't considered for insertion, we don't
2174 // add entries making nodes unperformed, so we don't add insertions
2175 // which cost more than the node penalty either.
2176 return;
2177 }
2178
2179 NodeEntry* const node_entry =
2180 new NodeEntry(node, insert_after, vehicle, num_allowed_vehicles);
2181 node_entry->set_value(CapSub(insertion_cost, penalty_shift));
2182 // Add entry to priority_queue and node_entries.
2183 DCHECK(!priority_queue->Contains(node_entry));
2184 node_entries->at(insert_after).insert(node_entry);
2185 priority_queue->Add(node_entry);
2186}
2187
2188void GlobalCheapestInsertionFilteredHeuristic::UpdateNodeEntry(
2189 NodeEntry* const node_entry,
2190 AdjustablePriorityQueue<NodeEntry>* priority_queue) const {
2191 const int64_t node = node_entry->node_to_insert();
2192 const int64_t insert_after = node_entry->insert_after();
2193 DCHECK_GE(insert_after, 0);
2194 const int64_t insertion_cost = GetInsertionCostForNodeAtPosition(
2195 node, insert_after, Value(insert_after), node_entry->vehicle());
2196 const int64_t penalty_shift =
2197 absl::GetFlag(FLAGS_routing_shift_insertion_cost_by_penalty)
2198 ? GetUnperformedValue(node)
2199 : 0;
2200
2201 node_entry->set_value(CapSub(insertion_cost, penalty_shift));
2202 // Update the priority_queue.
2203 DCHECK(priority_queue->Contains(node_entry));
2204 priority_queue->NoteChangedPriority(node_entry);
2205}
2206
2207// LocalCheapestInsertionFilteredHeuristic
2208// TODO(user): Add support for penalty costs.
2209LocalCheapestInsertionFilteredHeuristic::
2210 LocalCheapestInsertionFilteredHeuristic(
2212 std::function<int64_t(int64_t, int64_t, int64_t)> evaluator,
2213 bool evaluate_pickup_delivery_costs_independently,
2214 LocalSearchFilterManager* filter_manager)
2215 : CheapestInsertionFilteredHeuristic(model, std::move(evaluator), nullptr,
2216 filter_manager),
2217 evaluate_pickup_delivery_costs_independently_(
2218 evaluate_pickup_delivery_costs_independently) {
2219 DCHECK(evaluator_ != nullptr ||
2220 !evaluate_pickup_delivery_costs_independently_);
2221 std::vector<int> all_vehicles(model->vehicles());
2222 std::iota(std::begin(all_vehicles), std::end(all_vehicles), 0);
2223
2224 start_end_distances_per_node_ =
2226}
2227
2229 // Marking if we've tried inserting a node.
2230 std::vector<bool> visited(model()->Size(), false);
2231 // Iterating on pickup and delivery pairs
2232 const RoutingModel::IndexPairs& index_pairs =
2234 // Sort pairs according to number of possible vehicles.
2235 struct PairDomainSize {
2236 uint64_t domain_size;
2237 int pair_index;
2238
2239 bool operator<(const PairDomainSize& other) const {
2240 return std::tie(domain_size, pair_index) <
2241 std::tie(other.domain_size, other.pair_index);
2242 }
2243 };
2244 std::vector<PairDomainSize> pair_domain_sizes;
2245 for (int pair_index = 0; pair_index < index_pairs.size(); ++pair_index) {
2246 uint64_t domain_size = std::numeric_limits<uint64_t>::max();
2247 for (int64_t pickup : index_pairs[pair_index].first) {
2248 domain_size = std::min(domain_size, model()->VehicleVar(pickup)->Size());
2249 }
2250 for (int64_t delivery : index_pairs[pair_index].second) {
2251 domain_size =
2252 std::min(domain_size, model()->VehicleVar(delivery)->Size());
2253 }
2254 pair_domain_sizes.push_back({domain_size, pair_index});
2255 }
2256 std::sort(pair_domain_sizes.begin(), pair_domain_sizes.end());
2257 const auto insert_pair = [this](int64_t pickup, int64_t insert_pickup_after,
2258 int64_t delivery,
2259 int64_t insert_delivery_after, int vehicle) {
2260 const int64_t insert_pickup_before = Value(insert_pickup_after);
2261 InsertBetween(pickup, insert_pickup_after, insert_pickup_before, vehicle);
2262 DCHECK_NE(insert_delivery_after, insert_pickup_after);
2263 const int64_t insert_delivery_before = (insert_delivery_after == pickup)
2264 ? insert_pickup_before
2265 : Value(insert_delivery_after);
2266 InsertBetween(delivery, insert_delivery_after, insert_delivery_before,
2267 vehicle);
2268 return Evaluate(/*commit=*/true).has_value();
2269 };
2270 for (const auto [unused_domain_size, pair_index] : pair_domain_sizes) {
2271 const auto& index_pair = index_pairs[pair_index];
2272 bool pair_inserted = false;
2273 for (int64_t pickup : index_pair.first) {
2274 if (pair_inserted || Contains(pickup)) break;
2275 std::vector<NodeInsertion> pickup_insertion_positions;
2276 for (int64_t delivery : index_pair.second) {
2277 // If either is already in the solution, let it be inserted in the
2278 // standard node insertion loop.
2279 if (Contains(delivery)) pair_inserted = true;
2280 if (pair_inserted) break;
2281 if (StopSearch()) return false;
2282 visited[pickup] = true;
2283 visited[delivery] = true;
2284 if (evaluate_pickup_delivery_costs_independently_) {
2285 // Lazily fetching pickup insertion positions.
2286 if (pickup_insertion_positions.empty()) {
2287 pickup_insertion_positions =
2288 ComputeEvaluatorSortedPositions(pickup);
2289 }
2290 for (const auto [insert_pickup_after, pickup_vehicle,
2291 unused_pickup_value] : pickup_insertion_positions) {
2292 for (const auto [insert_delivery_after, unused_delivery_vehicle,
2293 unused_delivery_value] :
2294 ComputeEvaluatorSortedPositionsOnRouteAfter(
2295 delivery, pickup, Value(insert_pickup_after),
2296 pickup_vehicle)) {
2297 if (insert_pair(pickup, insert_pickup_after, delivery,
2298 insert_delivery_after, pickup_vehicle)) {
2299 pair_inserted = true;
2300 break;
2301 }
2302 }
2303 if (pair_inserted) break;
2304 }
2305 } else {
2306 for (const auto [insert_pickup_after, insert_delivery_after,
2307 unused_value, vehicle] :
2308 ComputeEvaluatorSortedPairPositions(pickup, delivery)) {
2309 if (insert_pair(pickup, insert_pickup_after, delivery,
2310 insert_delivery_after, vehicle)) {
2311 pair_inserted = true;
2312 break;
2313 }
2314 }
2315 }
2316 }
2317 }
2318 }
2319
2320 std::priority_queue<Seed> node_queue;
2321 InitializePriorityQueue(&start_end_distances_per_node_, &node_queue);
2322
2323 // Possible positions where the current node can be inserted.
2324 while (!node_queue.empty()) {
2325 const int node = node_queue.top().second;
2326 node_queue.pop();
2327 if (Contains(node) || visited[node]) continue;
2328 for (const auto [insert_after, vehicle, unused_value] :
2329 ComputeEvaluatorSortedPositions(node)) {
2330 if (StopSearch()) return false;
2331 InsertBetween(node, insert_after, Value(insert_after), vehicle);
2332 if (Evaluate(/*commit=*/true).has_value()) {
2333 break;
2334 }
2335 }
2336 }
2338 return Evaluate(/*commit=*/true).has_value();
2339}
2340
2341std::vector<LocalCheapestInsertionFilteredHeuristic::NodeInsertion>
2342LocalCheapestInsertionFilteredHeuristic::ComputeEvaluatorSortedPositions(
2343 int64_t node) {
2344 DCHECK(!Contains(node));
2345 std::vector<NodeInsertion> sorted_insertions;
2346 const int size = model()->Size();
2347 if (node < size) {
2348 for (int vehicle = 0; vehicle < model()->vehicles(); ++vehicle) {
2349 const int64_t start = model()->Start(vehicle);
2351 &sorted_insertions);
2352 }
2353 std::sort(sorted_insertions.begin(), sorted_insertions.end());
2354 }
2355 return sorted_insertions;
2356}
2357
2358std::vector<LocalCheapestInsertionFilteredHeuristic::NodeInsertion>
2359LocalCheapestInsertionFilteredHeuristic::
2360 ComputeEvaluatorSortedPositionsOnRouteAfter(int64_t node, int64_t start,
2361 int64_t next_after_start,
2362 int vehicle) {
2363 DCHECK(!Contains(node));
2364 std::vector<NodeInsertion> sorted_insertions;
2365 const int size = model()->Size();
2366 if (node < size) {
2367 AppendInsertionPositionsAfter(node, start, next_after_start, vehicle,
2368 &sorted_insertions);
2369 std::sort(sorted_insertions.begin(), sorted_insertions.end());
2370 }
2371 return sorted_insertions;
2372}
2373
2374std::vector<LocalCheapestInsertionFilteredHeuristic::PickupDeliveryInsertion>
2375LocalCheapestInsertionFilteredHeuristic::ComputeEvaluatorSortedPairPositions(
2376 int64_t pickup, int64_t delivery) {
2377 std::vector<PickupDeliveryInsertion> sorted_pickup_delivery_insertions;
2378 const int size = model()->Size();
2379 DCHECK_LT(pickup, size);
2380 DCHECK_LT(delivery, size);
2381 for (int vehicle = 0; vehicle < model()->vehicles(); ++vehicle) {
2382 int64_t insert_pickup_after = model()->Start(vehicle);
2383 while (!model()->IsEnd(insert_pickup_after)) {
2384 const int64_t insert_pickup_before = Value(insert_pickup_after);
2385 int64_t insert_delivery_after = pickup;
2386 while (!model()->IsEnd(insert_delivery_after)) {
2387 const int64_t insert_delivery_before =
2388 insert_delivery_after == pickup ? insert_pickup_before
2389 : Value(insert_delivery_after);
2390 if (evaluator_ == nullptr) {
2391 InsertBetween(pickup, insert_pickup_after, insert_pickup_before,
2392 vehicle);
2393 InsertBetween(delivery, insert_delivery_after, insert_delivery_before,
2394 vehicle);
2395 std::optional<int64_t> insertion_cost = Evaluate(/*commit=*/false);
2396 if (insertion_cost.has_value()) {
2397 sorted_pickup_delivery_insertions.push_back(
2398 {insert_pickup_after, insert_delivery_after, *insertion_cost,
2399 vehicle});
2400 }
2401 } else {
2402 sorted_pickup_delivery_insertions.push_back(
2403 {insert_pickup_after, insert_delivery_after,
2405 pickup, insert_pickup_after, insert_pickup_before,
2406 vehicle),
2408 delivery, insert_delivery_after,
2409 insert_delivery_before, vehicle)),
2410 vehicle});
2411 }
2412 insert_delivery_after = insert_delivery_before;
2413 }
2414 insert_pickup_after = insert_pickup_before;
2415 }
2416 }
2417 std::sort(sorted_pickup_delivery_insertions.begin(),
2418 sorted_pickup_delivery_insertions.end());
2419 return sorted_pickup_delivery_insertions;
2420}
2421
2422// CheapestAdditionFilteredHeuristic
2423
2426 : RoutingFilteredHeuristic(model, filter_manager) {}
2427
2429 const int kUnassigned = -1;
2431 std::vector<std::vector<int64_t>> deliveries(Size());
2432 std::vector<std::vector<int64_t>> pickups(Size());
2433 for (const RoutingModel::IndexPair& pair : pairs) {
2434 for (int first : pair.first) {
2435 for (int second : pair.second) {
2436 deliveries[first].push_back(second);
2437 pickups[second].push_back(first);
2438 }
2439 }
2440 }
2441 // To mimic the behavior of PathSelector (cf. search.cc), iterating on
2442 // routes with partial route at their start first then on routes with largest
2443 // index.
2444 std::vector<int> sorted_vehicles(model()->vehicles(), 0);
2445 for (int vehicle = 0; vehicle < model()->vehicles(); ++vehicle) {
2446 sorted_vehicles[vehicle] = vehicle;
2447 }
2448 std::sort(sorted_vehicles.begin(), sorted_vehicles.end(),
2449 PartialRoutesAndLargeVehicleIndicesFirst(*this));
2450 // Neighbors of the node currently being extended.
2451 for (const int vehicle : sorted_vehicles) {
2452 int64_t last_node = GetStartChainEnd(vehicle);
2453 bool extend_route = true;
2454 // Extend the route of the current vehicle while it's possible. We can
2455 // iterate more than once if pickup and delivery pairs have been inserted
2456 // in the last iteration (see comment below); the new iteration will try to
2457 // extend the route after the last delivery on the route.
2458 while (extend_route) {
2459 extend_route = false;
2460 bool found = true;
2461 int64_t index = last_node;
2462 int64_t end = GetEndChainStart(vehicle);
2463 // Extend the route until either the end node of the vehicle is reached
2464 // or no node or node pair can be added. Deliveries in pickup and
2465 // delivery pairs are added at the same time as pickups, at the end of the
2466 // route, in reverse order of the pickups. Deliveries are never added
2467 // alone.
2468 while (found && !model()->IsEnd(index)) {
2469 found = false;
2470 std::vector<int64_t> neighbors;
2471 if (index < model()->Nexts().size()) {
2472 std::unique_ptr<IntVarIterator> it(
2473 model()->Nexts()[index]->MakeDomainIterator(false));
2474 auto next_values = InitAndGetValues(it.get());
2475 neighbors = GetPossibleNextsFromIterator(index, next_values.begin(),
2476 next_values.end());
2477 }
2478 for (int i = 0; !found && i < neighbors.size(); ++i) {
2479 int64_t next = -1;
2480 switch (i) {
2481 case 0:
2482 next = FindTopSuccessor(index, neighbors);
2483 break;
2484 case 1:
2485 SortSuccessors(index, &neighbors);
2486 ABSL_FALLTHROUGH_INTENDED;
2487 default:
2488 next = neighbors[i];
2489 }
2490 if (model()->IsEnd(next) && next != end) {
2491 continue;
2492 }
2493 // Only add a delivery if one of its pickups has been added already.
2494 if (!model()->IsEnd(next) && !pickups[next].empty()) {
2495 bool contains_pickups = false;
2496 for (int64_t pickup : pickups[next]) {
2497 if (Contains(pickup)) {
2498 contains_pickups = true;
2499 break;
2500 }
2501 }
2502 if (!contains_pickups) {
2503 continue;
2504 }
2505 }
2506 std::vector<int64_t> next_deliveries;
2507 if (next < deliveries.size()) {
2508 next_deliveries = GetPossibleNextsFromIterator(
2509 next, deliveries[next].begin(), deliveries[next].end());
2510 }
2511 if (next_deliveries.empty()) next_deliveries = {kUnassigned};
2512 for (int j = 0; !found && j < next_deliveries.size(); ++j) {
2513 if (StopSearch()) return false;
2514 int delivery = -1;
2515 switch (j) {
2516 case 0:
2517 delivery = FindTopSuccessor(next, next_deliveries);
2518 break;
2519 case 1:
2520 SortSuccessors(next, &next_deliveries);
2521 ABSL_FALLTHROUGH_INTENDED;
2522 default:
2523 delivery = next_deliveries[j];
2524 }
2525 // Insert "next" after "index", and before "end" if it is not the
2526 // end already.
2528 if (!model()->IsEnd(next)) {
2529 SetValue(next, end);
2531 if (delivery != kUnassigned) {
2532 SetValue(next, delivery);
2533 SetValue(delivery, end);
2535 }
2536 }
2537 if (Evaluate(/*commit=*/true).has_value()) {
2538 index = next;
2539 found = true;
2540 if (delivery != kUnassigned) {
2541 if (model()->IsEnd(end) && last_node != delivery) {
2542 last_node = delivery;
2543 extend_route = true;
2544 }
2545 end = delivery;
2546 }
2547 break;
2548 }
2549 }
2550 }
2551 }
2552 }
2553 }
2555 return Evaluate(/*commit=*/true).has_value();
2556}
2557
2558bool CheapestAdditionFilteredHeuristic::
2559 PartialRoutesAndLargeVehicleIndicesFirst::operator()(int vehicle1,
2560 int vehicle2) const {
2561 const bool has_partial_route1 = (builder_.model()->Start(vehicle1) !=
2562 builder_.GetStartChainEnd(vehicle1));
2563 const bool has_partial_route2 = (builder_.model()->Start(vehicle2) !=
2564 builder_.GetStartChainEnd(vehicle2));
2565 if (has_partial_route1 == has_partial_route2) {
2566 return vehicle2 < vehicle1;
2567 } else {
2568 return has_partial_route2 < has_partial_route1;
2569 }
2570}
2571
2572// EvaluatorCheapestAdditionFilteredHeuristic
2573
2576 RoutingModel* model, std::function<int64_t(int64_t, int64_t)> evaluator,
2577 LocalSearchFilterManager* filter_manager)
2578 : CheapestAdditionFilteredHeuristic(model, filter_manager),
2579 evaluator_(std::move(evaluator)) {}
2580
2581int64_t EvaluatorCheapestAdditionFilteredHeuristic::FindTopSuccessor(
2582 int64_t node, const std::vector<int64_t>& successors) {
2583 int64_t best_evaluation = std::numeric_limits<int64_t>::max();
2584 int64_t best_successor = -1;
2585 for (int64_t successor : successors) {
2586 const int64_t evaluation = (successor >= 0)
2587 ? evaluator_(node, successor)
2589 if (evaluation < best_evaluation ||
2590 (evaluation == best_evaluation && successor > best_successor)) {
2591 best_evaluation = evaluation;
2592 best_successor = successor;
2593 }
2594 }
2595 return best_successor;
2596}
2597
2598void EvaluatorCheapestAdditionFilteredHeuristic::SortSuccessors(
2599 int64_t node, std::vector<int64_t>* successors) {
2600 std::vector<std::pair<int64_t, int64_t>> values;
2601 values.reserve(successors->size());
2602 for (int64_t successor : *successors) {
2603 // Tie-breaking on largest node index to mimic the behavior of
2604 // CheapestValueSelector (search.cc).
2605 values.push_back({evaluator_(node, successor), -successor});
2606 }
2607 std::sort(values.begin(), values.end());
2608 successors->clear();
2609 for (auto value : values) {
2610 successors->push_back(-value.second);
2611 }
2612}
2613
2614// ComparatorCheapestAdditionFilteredHeuristic
2615
2619 LocalSearchFilterManager* filter_manager)
2620 : CheapestAdditionFilteredHeuristic(model, filter_manager),
2621 comparator_(std::move(comparator)) {}
2622
2623int64_t ComparatorCheapestAdditionFilteredHeuristic::FindTopSuccessor(
2624 int64_t node, const std::vector<int64_t>& successors) {
2625 return *std::min_element(successors.begin(), successors.end(),
2626 [this, node](int successor1, int successor2) {
2627 return comparator_(node, successor1, successor2);
2628 });
2629}
2630
2631void ComparatorCheapestAdditionFilteredHeuristic::SortSuccessors(
2632 int64_t node, std::vector<int64_t>* successors) {
2633 std::sort(successors->begin(), successors->end(),
2634 [this, node](int successor1, int successor2) {
2635 return comparator_(node, successor1, successor2);
2636 });
2637}
2638
2639// Class storing and allowing access to the savings according to the number of
2640// vehicle types.
2641// The savings are stored and sorted in sorted_savings_per_vehicle_type_.
2642// Furthermore, when there is more than one vehicle type, the savings for a same
2643// before-->after arc are sorted in costs_and_savings_per_arc_[arc] by
2644// increasing cost(s-->before-->after-->e), where s and e are the start and end
2645// of the route, in order to make sure the arc is served by the route with the
2646// closest depot (start/end) possible.
2647// When there is only one vehicle "type" (i.e. all vehicles have the same
2648// start/end and cost class), each arc has a single saving value associated to
2649// it, so we ignore this last step to avoid unnecessary computations, and only
2650// work with sorted_savings_per_vehicle_type_[0].
2651// In case of multiple vehicle types, the best savings for each arc, i.e. the
2652// savings corresponding to the closest vehicle type, are inserted and sorted in
2653// sorted_savings_.
2654//
2655// This class also handles skipped Savings:
2656// The vectors skipped_savings_starting/ending_at_ contain all the Savings that
2657// weren't added to the model, which we want to consider for later:
2658// 1) When a Saving before-->after with both nodes uncontained cannot be used to
2659// start a new route (no more available vehicles or could not commit on any
2660// of those available).
2661// 2) When only one of the nodes of the Saving is contained but on a different
2662// vehicle type.
2663// In these cases, the Update() method is called with update_best_saving = true,
2664// which in turn calls SkipSavingForArc() (within
2665// UpdateNextAndSkippedSavingsForArcWithType()) to mark the Saving for this arc
2666// (with the correct type in the second case) as "skipped", by storing it in
2667// skipped_savings_starting_at_[before] and skipped_savings_ending_at_[after].
2668//
2669// UpdateNextAndSkippedSavingsForArcWithType() also updates the next_savings_
2670// vector, which stores the savings to go through once we've iterated through
2671// all sorted_savings_.
2672// In the first case above, where neither nodes are contained, we skip the
2673// current Saving (current_saving_), and add the next best Saving for this arc
2674// to next_savings_ (in case this skipped Saving is never considered).
2675// In the second case with a specific type, we search for the Saving with the
2676// correct type for this arc, and add it to both next_savings_ and the skipped
2677// Savings.
2678//
2679// The skipped Savings are then re-considered when one of their ends gets
2680// inserted:
2681// When another Saving other_node-->before (or after-->other_node) gets
2682// inserted, all skipped Savings in skipped_savings_starting_at_[before] (or
2683// skipped_savings_ending_at_[after]) are once again considered by calling
2684// ReinjectSkippedSavingsStartingAt() (or ReinjectSkippedSavingsEndingAt()).
2685// Then, when calling GetSaving(), we iterate through the reinjected Savings in
2686// order of insertion in the vectors while there are reinjected savings.
2687template <typename Saving>
2689 public:
2690 explicit SavingsContainer(const SavingsFilteredHeuristic* savings_db,
2691 int vehicle_types)
2692 : savings_db_(savings_db),
2693 index_in_sorted_savings_(0),
2694 vehicle_types_(vehicle_types),
2695 single_vehicle_type_(vehicle_types == 1),
2696 using_incoming_reinjected_saving_(false),
2697 sorted_(false),
2698 to_update_(true) {}
2699
2700 void InitializeContainer(int64_t size, int64_t saving_neighbors) {
2701 sorted_savings_per_vehicle_type_.clear();
2702 sorted_savings_per_vehicle_type_.resize(vehicle_types_);
2703 for (std::vector<Saving>& savings : sorted_savings_per_vehicle_type_) {
2704 savings.reserve(size * saving_neighbors);
2705 }
2706
2707 sorted_savings_.clear();
2708 costs_and_savings_per_arc_.clear();
2709 arc_indices_per_before_node_.clear();
2710
2711 if (!single_vehicle_type_) {
2712 costs_and_savings_per_arc_.reserve(size * saving_neighbors);
2713 arc_indices_per_before_node_.resize(size);
2714 for (int before_node = 0; before_node < size; before_node++) {
2715 arc_indices_per_before_node_[before_node].reserve(saving_neighbors);
2716 }
2717 }
2718 skipped_savings_starting_at_.clear();
2719 skipped_savings_starting_at_.resize(size);
2720 skipped_savings_ending_at_.clear();
2721 skipped_savings_ending_at_.resize(size);
2722 incoming_reinjected_savings_ = nullptr;
2723 outgoing_reinjected_savings_ = nullptr;
2724 incoming_new_reinjected_savings_ = nullptr;
2725 outgoing_new_reinjected_savings_ = nullptr;
2726 }
2727
2728 void AddNewSaving(const Saving& saving, int64_t total_cost,
2729 int64_t before_node, int64_t after_node, int vehicle_type) {
2730 CHECK(!sorted_savings_per_vehicle_type_.empty())
2731 << "Container not initialized!";
2732 sorted_savings_per_vehicle_type_[vehicle_type].push_back(saving);
2733 UpdateArcIndicesCostsAndSavings(before_node, after_node,
2734 {total_cost, saving});
2735 }
2736
2737 void Sort() {
2738 CHECK(!sorted_) << "Container already sorted!";
2739
2740 for (std::vector<Saving>& savings : sorted_savings_per_vehicle_type_) {
2741 std::sort(savings.begin(), savings.end());
2742 }
2743
2744 if (single_vehicle_type_) {
2745 const auto& savings = sorted_savings_per_vehicle_type_[0];
2746 sorted_savings_.resize(savings.size());
2747 std::transform(savings.begin(), savings.end(), sorted_savings_.begin(),
2748 [](const Saving& saving) {
2749 return SavingAndArc({saving, /*arc_index*/ -1});
2750 });
2751 } else {
2752 // For each arc, sort the savings by decreasing total cost
2753 // start-->a-->b-->end.
2754 // The best saving for each arc is therefore the last of its vector.
2755 sorted_savings_.reserve(vehicle_types_ *
2756 costs_and_savings_per_arc_.size());
2757
2758 for (int arc_index = 0; arc_index < costs_and_savings_per_arc_.size();
2759 arc_index++) {
2760 std::vector<std::pair<int64_t, Saving>>& costs_and_savings =
2761 costs_and_savings_per_arc_[arc_index];
2762 DCHECK(!costs_and_savings.empty());
2763
2764 std::sort(
2765 costs_and_savings.begin(), costs_and_savings.end(),
2766 [](const std::pair<int64_t, Saving>& cs1,
2767 const std::pair<int64_t, Saving>& cs2) { return cs1 > cs2; });
2768
2769 // Insert all Savings for this arc with the lowest cost into
2770 // sorted_savings_.
2771 // TODO(user): Also do this when reiterating on next_savings_.
2772 const int64_t cost = costs_and_savings.back().first;
2773 while (!costs_and_savings.empty() &&
2774 costs_and_savings.back().first == cost) {
2775 sorted_savings_.push_back(
2776 {costs_and_savings.back().second, arc_index});
2777 costs_and_savings.pop_back();
2778 }
2779 }
2780 std::sort(sorted_savings_.begin(), sorted_savings_.end());
2781 next_saving_type_and_index_for_arc_.clear();
2782 next_saving_type_and_index_for_arc_.resize(
2783 costs_and_savings_per_arc_.size(), {-1, -1});
2784 }
2785 sorted_ = true;
2786 index_in_sorted_savings_ = 0;
2787 to_update_ = false;
2788 }
2789
2790 bool HasSaving() {
2791 return index_in_sorted_savings_ < sorted_savings_.size() ||
2792 HasReinjectedSavings();
2793 }
2794
2796 CHECK(sorted_) << "Calling GetSaving() before Sort() !";
2797 CHECK(!to_update_)
2798 << "Update() should be called between two calls to GetSaving() !";
2799
2800 to_update_ = true;
2801
2802 if (HasReinjectedSavings()) {
2803 if (incoming_reinjected_savings_ != nullptr &&
2804 outgoing_reinjected_savings_ != nullptr) {
2805 // Get the best Saving among the two.
2806 SavingAndArc& incoming_saving = incoming_reinjected_savings_->front();
2807 SavingAndArc& outgoing_saving = outgoing_reinjected_savings_->front();
2808 if (incoming_saving < outgoing_saving) {
2809 current_saving_ = incoming_saving;
2810 using_incoming_reinjected_saving_ = true;
2811 } else {
2812 current_saving_ = outgoing_saving;
2813 using_incoming_reinjected_saving_ = false;
2814 }
2815 } else {
2816 if (incoming_reinjected_savings_ != nullptr) {
2817 current_saving_ = incoming_reinjected_savings_->front();
2818 using_incoming_reinjected_saving_ = true;
2819 }
2820 if (outgoing_reinjected_savings_ != nullptr) {
2821 current_saving_ = outgoing_reinjected_savings_->front();
2822 using_incoming_reinjected_saving_ = false;
2823 }
2824 }
2825 } else {
2826 current_saving_ = sorted_savings_[index_in_sorted_savings_];
2827 }
2828 return current_saving_.saving;
2829 }
2830
2831 void Update(bool update_best_saving, int type = -1) {
2832 CHECK(to_update_) << "Container already up to date!";
2833 if (update_best_saving) {
2834 const int64_t arc_index = current_saving_.arc_index;
2835 UpdateNextAndSkippedSavingsForArcWithType(arc_index, type);
2836 }
2837 if (!HasReinjectedSavings()) {
2838 index_in_sorted_savings_++;
2839
2840 if (index_in_sorted_savings_ == sorted_savings_.size()) {
2841 sorted_savings_.swap(next_savings_);
2842 gtl::STLClearObject(&next_savings_);
2843 index_in_sorted_savings_ = 0;
2844
2845 std::sort(sorted_savings_.begin(), sorted_savings_.end());
2846 next_saving_type_and_index_for_arc_.clear();
2847 next_saving_type_and_index_for_arc_.resize(
2848 costs_and_savings_per_arc_.size(), {-1, -1});
2849 }
2850 }
2851 UpdateReinjectedSavings();
2852 to_update_ = false;
2853 }
2854
2855 void UpdateWithType(int type) {
2856 CHECK(!single_vehicle_type_);
2857 Update(/*update_best_saving*/ true, type);
2858 }
2859
2860 const std::vector<Saving>& GetSortedSavingsForVehicleType(int type) {
2861 CHECK(sorted_) << "Savings not sorted yet!";
2862 CHECK_LT(type, vehicle_types_);
2863 return sorted_savings_per_vehicle_type_[type];
2864 }
2865
2867 CHECK(outgoing_new_reinjected_savings_ == nullptr);
2868 outgoing_new_reinjected_savings_ = &(skipped_savings_starting_at_[node]);
2869 }
2870
2872 CHECK(incoming_new_reinjected_savings_ == nullptr);
2873 incoming_new_reinjected_savings_ = &(skipped_savings_ending_at_[node]);
2874 }
2875
2876 private:
2877 struct SavingAndArc {
2878 Saving saving;
2879 int64_t arc_index;
2880
2881 bool operator<(const SavingAndArc& other) const {
2882 return std::tie(saving, arc_index) <
2883 std::tie(other.saving, other.arc_index);
2884 }
2885 };
2886
2887 // Skips the Saving for the arc before_node-->after_node, by adding it to the
2888 // skipped_savings_ vector of the nodes, if they're uncontained.
2889 void SkipSavingForArc(const SavingAndArc& saving_and_arc) {
2890 const Saving& saving = saving_and_arc.saving;
2891 const int64_t before_node = savings_db_->GetBeforeNodeFromSaving(saving);
2892 const int64_t after_node = savings_db_->GetAfterNodeFromSaving(saving);
2893 if (!savings_db_->Contains(before_node)) {
2894 skipped_savings_starting_at_[before_node].push_back(saving_and_arc);
2895 }
2896 if (!savings_db_->Contains(after_node)) {
2897 skipped_savings_ending_at_[after_node].push_back(saving_and_arc);
2898 }
2899 }
2900
2901 // Called within Update() when update_best_saving is true, this method updates
2902 // the next_savings_ and skipped savings vectors for a given arc_index and
2903 // vehicle type.
2904 // When a Saving with the right type has already been added to next_savings_
2905 // for this arc, no action is needed on next_savings_.
2906 // Otherwise, if such a Saving exists, GetNextSavingForArcWithType() will find
2907 // and assign it to next_saving, which is then used to update next_savings_.
2908 // Finally, the right Saving is skipped for this arc: if looking for a
2909 // specific type (i.e. type != -1), next_saving (which has the correct type)
2910 // is skipped, otherwise the current_saving_ is.
2911 void UpdateNextAndSkippedSavingsForArcWithType(int64_t arc_index, int type) {
2912 if (single_vehicle_type_) {
2913 // No next Saving, skip the current Saving.
2914 CHECK_EQ(type, -1);
2915 SkipSavingForArc(current_saving_);
2916 return;
2917 }
2918 CHECK_GE(arc_index, 0);
2919 auto& type_and_index = next_saving_type_and_index_for_arc_[arc_index];
2920 const int previous_index = type_and_index.second;
2921 const int previous_type = type_and_index.first;
2922 bool next_saving_added = false;
2923 Saving next_saving;
2924
2925 if (previous_index >= 0) {
2926 // Next Saving already added for this arc.
2927 DCHECK_GE(previous_type, 0);
2928 if (type == -1 || previous_type == type) {
2929 // Not looking for a specific type, or correct type already in
2930 // next_savings_.
2931 next_saving_added = true;
2932 next_saving = next_savings_[previous_index].saving;
2933 }
2934 }
2935
2936 if (!next_saving_added &&
2937 GetNextSavingForArcWithType(arc_index, type, &next_saving)) {
2938 type_and_index.first = savings_db_->GetVehicleTypeFromSaving(next_saving);
2939 if (previous_index >= 0) {
2940 // Update the previous saving.
2941 next_savings_[previous_index] = {next_saving, arc_index};
2942 } else {
2943 // Insert the new next Saving for this arc.
2944 type_and_index.second = next_savings_.size();
2945 next_savings_.push_back({next_saving, arc_index});
2946 }
2947 next_saving_added = true;
2948 }
2949
2950 // Skip the Saving based on the vehicle type.
2951 if (type == -1) {
2952 // Skip the current Saving.
2953 SkipSavingForArc(current_saving_);
2954 } else {
2955 // Skip the Saving with the correct type, already added to next_savings_
2956 // if it was found.
2957 if (next_saving_added) {
2958 SkipSavingForArc({next_saving, arc_index});
2959 }
2960 }
2961 }
2962
2963 void UpdateReinjectedSavings() {
2964 UpdateGivenReinjectedSavings(incoming_new_reinjected_savings_,
2965 &incoming_reinjected_savings_,
2966 using_incoming_reinjected_saving_);
2967 UpdateGivenReinjectedSavings(outgoing_new_reinjected_savings_,
2968 &outgoing_reinjected_savings_,
2969 !using_incoming_reinjected_saving_);
2970 incoming_new_reinjected_savings_ = nullptr;
2971 outgoing_new_reinjected_savings_ = nullptr;
2972 }
2973
2974 void UpdateGivenReinjectedSavings(
2975 std::deque<SavingAndArc>* new_reinjected_savings,
2976 std::deque<SavingAndArc>** reinjected_savings,
2977 bool using_reinjected_savings) {
2978 if (new_reinjected_savings == nullptr) {
2979 // No new reinjected savings, update the previous ones if needed.
2980 if (*reinjected_savings != nullptr && using_reinjected_savings) {
2981 CHECK(!(*reinjected_savings)->empty());
2982 (*reinjected_savings)->pop_front();
2983 if ((*reinjected_savings)->empty()) {
2984 *reinjected_savings = nullptr;
2985 }
2986 }
2987 return;
2988 }
2989
2990 // New savings reinjected.
2991 // Forget about the previous reinjected savings and add the new ones if
2992 // there are any.
2993 if (*reinjected_savings != nullptr) {
2994 (*reinjected_savings)->clear();
2995 }
2996 *reinjected_savings = nullptr;
2997 if (!new_reinjected_savings->empty()) {
2998 *reinjected_savings = new_reinjected_savings;
2999 }
3000 }
3001
3002 bool HasReinjectedSavings() {
3003 return outgoing_reinjected_savings_ != nullptr ||
3004 incoming_reinjected_savings_ != nullptr;
3005 }
3006
3007 void UpdateArcIndicesCostsAndSavings(
3008 int64_t before_node, int64_t after_node,
3009 const std::pair<int64_t, Saving>& cost_and_saving) {
3010 if (single_vehicle_type_) {
3011 return;
3012 }
3013 absl::flat_hash_map<int, int>& arc_indices =
3014 arc_indices_per_before_node_[before_node];
3015 const auto& arc_inserted = arc_indices.insert(
3016 std::make_pair(after_node, costs_and_savings_per_arc_.size()));
3017 const int index = arc_inserted.first->second;
3018 if (arc_inserted.second) {
3019 costs_and_savings_per_arc_.push_back({cost_and_saving});
3020 } else {
3021 DCHECK_LT(index, costs_and_savings_per_arc_.size());
3022 costs_and_savings_per_arc_[index].push_back(cost_and_saving);
3023 }
3024 }
3025
3026 bool GetNextSavingForArcWithType(int64_t arc_index, int type,
3027 Saving* next_saving) {
3028 std::vector<std::pair<int64_t, Saving>>& costs_and_savings =
3029 costs_and_savings_per_arc_[arc_index];
3030
3031 bool found_saving = false;
3032 while (!costs_and_savings.empty() && !found_saving) {
3033 const Saving& saving = costs_and_savings.back().second;
3034 if (type == -1 || savings_db_->GetVehicleTypeFromSaving(saving) == type) {
3035 *next_saving = saving;
3036 found_saving = true;
3037 }
3038 costs_and_savings.pop_back();
3039 }
3040 return found_saving;
3041 }
3042
3043 const SavingsFilteredHeuristic* const savings_db_;
3044 int64_t index_in_sorted_savings_;
3045 std::vector<std::vector<Saving>> sorted_savings_per_vehicle_type_;
3046 std::vector<SavingAndArc> sorted_savings_;
3047 std::vector<SavingAndArc> next_savings_;
3048 std::vector<std::pair</*type*/ int, /*index*/ int>>
3049 next_saving_type_and_index_for_arc_;
3050 SavingAndArc current_saving_;
3051 std::vector<std::vector<std::pair</*cost*/ int64_t, Saving>>>
3052 costs_and_savings_per_arc_;
3053 std::vector<absl::flat_hash_map</*after_node*/ int, /*arc_index*/ int>>
3054 arc_indices_per_before_node_;
3055 std::vector<std::deque<SavingAndArc>> skipped_savings_starting_at_;
3056 std::vector<std::deque<SavingAndArc>> skipped_savings_ending_at_;
3057 std::deque<SavingAndArc>* outgoing_reinjected_savings_;
3058 std::deque<SavingAndArc>* incoming_reinjected_savings_;
3059 std::deque<SavingAndArc>* outgoing_new_reinjected_savings_;
3060 std::deque<SavingAndArc>* incoming_new_reinjected_savings_;
3061 const int vehicle_types_;
3062 const bool single_vehicle_type_;
3063 bool using_incoming_reinjected_saving_;
3064 bool sorted_;
3065 bool to_update_;
3066};
3067
3068// SavingsFilteredHeuristic
3069
3072 LocalSearchFilterManager* filter_manager)
3073 : RoutingFilteredHeuristic(model, filter_manager),
3074 vehicle_type_curator_(nullptr),
3075 savings_params_(parameters) {
3076 DCHECK_GT(savings_params_.neighbors_ratio, 0);
3077 DCHECK_LE(savings_params_.neighbors_ratio, 1);
3078 DCHECK_GT(savings_params_.max_memory_usage_bytes, 0);
3079 DCHECK_GT(savings_params_.arc_coefficient, 0);
3080 const int size = model->Size();
3081 size_squared_ = size * size;
3082}
3083
3085
3087 if (vehicle_type_curator_ == nullptr) {
3088 vehicle_type_curator_ = absl::make_unique<VehicleTypeCurator>(
3089 model()->GetVehicleTypeContainer());
3090 }
3091 // Only store empty vehicles in the vehicle_type_curator_.
3092 vehicle_type_curator_->Reset(
3093 [this](int vehicle) { return VehicleIsEmpty(vehicle); });
3094 if (!ComputeSavings()) return false;
3096 // Free all the space used to store the Savings in the container.
3097 savings_container_.reset();
3099 if (!Evaluate(/*commit=*/true).has_value()) return false;
3101 return Evaluate(/*commit=*/true).has_value();
3102}
3103
3105 int type, int64_t before_node, int64_t after_node) {
3106 auto vehicle_is_compatible = [this, before_node, after_node](int vehicle) {
3107 if (!model()->VehicleVar(before_node)->Contains(vehicle) ||
3108 !model()->VehicleVar(after_node)->Contains(vehicle)) {
3109 return false;
3110 }
3111 // Try to commit the arc on this vehicle.
3112 DCHECK(VehicleIsEmpty(vehicle));
3113 const int64_t start = model()->Start(vehicle);
3114 const int64_t end = model()->End(vehicle);
3115 SetValue(start, before_node);
3116 SetValue(before_node, after_node);
3117 SetValue(after_node, end);
3118 return Evaluate(/*commit=*/true).has_value();
3119 };
3120
3122 ->GetCompatibleVehicleOfType(
3123 type, vehicle_is_compatible,
3124 /*stop_and_return_vehicle*/ [](int) { return false; })
3125 .first;
3126}
3127
3128void SavingsFilteredHeuristic::AddSymmetricArcsToAdjacencyLists(
3129 std::vector<std::vector<int64_t>>* adjacency_lists) {
3130 for (int64_t node = 0; node < adjacency_lists->size(); node++) {
3131 for (int64_t neighbor : (*adjacency_lists)[node]) {
3132 if (model()->IsStart(neighbor) || model()->IsEnd(neighbor)) {
3133 continue;
3134 }
3135 (*adjacency_lists)[neighbor].push_back(node);
3136 }
3137 }
3138 std::transform(adjacency_lists->begin(), adjacency_lists->end(),
3139 adjacency_lists->begin(), [](std::vector<int64_t> vec) {
3140 std::sort(vec.begin(), vec.end());
3141 vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
3142 return vec;
3143 });
3144}
3145
3146// Computes the savings related to each pair of non-start and non-end nodes.
3147// The savings value for an arc a-->b for a vehicle starting at node s and
3148// ending at node e is:
3149// saving = cost(s-->a-->e) + cost(s-->b-->e) - cost(s-->a-->b-->e), i.e.
3150// saving = cost(a-->e) + cost(s-->b) - cost(a-->b)
3151// The saving value also considers a coefficient for the cost of the arc
3152// a-->b, which results in:
3153// saving = cost(a-->e) + cost(s-->b) - [arc_coefficient_ * cost(a-->b)]
3154// The higher this saving value, the better the arc.
3155// Here, the value stored for the savings is -saving, which are therefore
3156// considered in decreasing order.
3157bool SavingsFilteredHeuristic::ComputeSavings() {
3158 const int num_vehicle_types = vehicle_type_curator_->NumTypes();
3159 const int size = model()->Size();
3160
3161 std::vector<int64_t> uncontained_non_start_end_nodes;
3162 uncontained_non_start_end_nodes.reserve(size);
3163 for (int node = 0; node < size; node++) {
3164 if (!model()->IsStart(node) && !model()->IsEnd(node) && !Contains(node)) {
3165 uncontained_non_start_end_nodes.push_back(node);
3166 }
3167 }
3168
3169 const int64_t saving_neighbors =
3170 std::min(MaxNumNeighborsPerNode(num_vehicle_types),
3171 static_cast<int64_t>(uncontained_non_start_end_nodes.size()));
3172
3174 absl::make_unique<SavingsContainer<Saving>>(this, num_vehicle_types);
3175 savings_container_->InitializeContainer(size, saving_neighbors);
3176 if (StopSearch()) return false;
3177 std::vector<std::vector<int64_t>> adjacency_lists(size);
3178
3179 for (int type = 0; type < num_vehicle_types; ++type) {
3180 const int vehicle =
3181 vehicle_type_curator_->GetLowestFixedCostVehicleOfType(type);
3182 if (vehicle < 0) {
3183 continue;
3184 }
3185
3186 const int64_t cost_class =
3187 model()->GetCostClassIndexOfVehicle(vehicle).value();
3188 const int64_t start = model()->Start(vehicle);
3189 const int64_t end = model()->End(vehicle);
3190 const int64_t fixed_cost = model()->GetFixedCostOfVehicle(vehicle);
3191
3192 // Compute the neighbors for each non-start/end node not already inserted in
3193 // the model.
3194 for (int before_node : uncontained_non_start_end_nodes) {
3195 std::vector<std::pair</*cost*/ int64_t, /*node*/ int64_t>>
3196 costed_after_nodes;
3197 costed_after_nodes.reserve(uncontained_non_start_end_nodes.size());
3198 if (StopSearch()) return false;
3199 for (int after_node : uncontained_non_start_end_nodes) {
3200 if (after_node != before_node) {
3201 costed_after_nodes.push_back(std::make_pair(
3202 model()->GetArcCostForClass(before_node, after_node, cost_class),
3203 after_node));
3204 }
3205 }
3206 if (saving_neighbors < costed_after_nodes.size()) {
3207 std::nth_element(costed_after_nodes.begin(),
3208 costed_after_nodes.begin() + saving_neighbors,
3209 costed_after_nodes.end());
3210 costed_after_nodes.resize(saving_neighbors);
3211 }
3212 adjacency_lists[before_node].resize(costed_after_nodes.size());
3213 std::transform(costed_after_nodes.begin(), costed_after_nodes.end(),
3214 adjacency_lists[before_node].begin(),
3215 [](std::pair<int64_t, int64_t> cost_and_node) {
3216 return cost_and_node.second;
3217 });
3218 }
3219 if (savings_params_.add_reverse_arcs) {
3220 AddSymmetricArcsToAdjacencyLists(&adjacency_lists);
3221 }
3222 if (StopSearch()) return false;
3223
3224 // Build the savings for this vehicle type given the adjacency_lists.
3225 for (int before_node : uncontained_non_start_end_nodes) {
3226 const int64_t before_to_end_cost =
3227 model()->GetArcCostForClass(before_node, end, cost_class);
3228 const int64_t start_to_before_cost =
3229 CapSub(model()->GetArcCostForClass(start, before_node, cost_class),
3230 fixed_cost);
3231 if (StopSearch()) return false;
3232 for (int64_t after_node : adjacency_lists[before_node]) {
3233 if (model()->IsStart(after_node) || model()->IsEnd(after_node) ||
3234 before_node == after_node || Contains(after_node)) {
3235 continue;
3236 }
3237 const int64_t arc_cost =
3238 model()->GetArcCostForClass(before_node, after_node, cost_class);
3239 const int64_t start_to_after_cost =
3240 CapSub(model()->GetArcCostForClass(start, after_node, cost_class),
3241 fixed_cost);
3242 const int64_t after_to_end_cost =
3243 model()->GetArcCostForClass(after_node, end, cost_class);
3244
3245 const double weighted_arc_cost_fp =
3246 savings_params_.arc_coefficient * arc_cost;
3247 const int64_t weighted_arc_cost =
3249 ? static_cast<int64_t>(weighted_arc_cost_fp)
3250 : std::numeric_limits<int64_t>::max();
3251 const int64_t saving_value = CapSub(
3252 CapAdd(before_to_end_cost, start_to_after_cost), weighted_arc_cost);
3253
3254 const Saving saving =
3255 BuildSaving(-saving_value, type, before_node, after_node);
3256
3257 const int64_t total_cost =
3258 CapAdd(CapAdd(start_to_before_cost, arc_cost), after_to_end_cost);
3259
3260 savings_container_->AddNewSaving(saving, total_cost, before_node,
3261 after_node, type);
3262 }
3263 }
3264 }
3265 savings_container_->Sort();
3266 return !StopSearch();
3267}
3268
3269int64_t SavingsFilteredHeuristic::MaxNumNeighborsPerNode(
3270 int num_vehicle_types) const {
3271 const int64_t size = model()->Size();
3272
3273 const int64_t num_neighbors_with_ratio =
3274 std::max(1.0, size * savings_params_.neighbors_ratio);
3275
3276 // A single Saving takes 2*8 bytes of memory.
3277 // max_memory_usage_in_savings_unit = num_savings * multiplicative_factor,
3278 // Where multiplicative_factor is the memory taken (in Savings unit) for each
3279 // computed Saving.
3280 const double max_memory_usage_in_savings_unit =
3281 savings_params_.max_memory_usage_bytes / 16;
3282
3283 // In the SavingsContainer, for each Saving, the Savings are stored:
3284 // - Once in "sorted_savings_per_vehicle_type", and (at most) once in
3285 // "sorted_savings_" --> factor 2
3286 // - If num_vehicle_types > 1, they're also stored by arc_index in
3287 // "costs_and_savings_per_arc", along with their int64_t cost --> factor 1.5
3288 //
3289 // On top of that,
3290 // - In the sequential version, the Saving* are also stored by in-coming and
3291 // outgoing node (in in/out_savings_ptr), adding another 2*8 bytes per
3292 // Saving --> factor 1.
3293 // - In the parallel version, skipped Savings are also stored in
3294 // skipped_savings_starting/ending_at_, resulting in a maximum added factor
3295 // of 2 for each Saving.
3296 // These extra factors are given by ExtraSavingsMemoryMultiplicativeFactor().
3297 double multiplicative_factor = 2.0 + ExtraSavingsMemoryMultiplicativeFactor();
3298 if (num_vehicle_types > 1) {
3299 multiplicative_factor += 1.5;
3300 }
3301 const double num_savings =
3302 max_memory_usage_in_savings_unit / multiplicative_factor;
3303 const int64_t num_neighbors_with_memory_restriction =
3304 std::max(1.0, num_savings / (num_vehicle_types * size));
3305
3306 return std::min(num_neighbors_with_ratio,
3307 num_neighbors_with_memory_restriction);
3308}
3309
3310// SequentialSavingsFilteredHeuristic
3311
3312void SequentialSavingsFilteredHeuristic::BuildRoutesFromSavings() {
3313 const int vehicle_types = vehicle_type_curator_->NumTypes();
3314 DCHECK_GT(vehicle_types, 0);
3315 const int size = model()->Size();
3316 // Store savings for each incoming and outgoing node and by vehicle type. This
3317 // is necessary to quickly extend partial chains without scanning all savings.
3318 std::vector<std::vector<const Saving*>> in_savings_ptr(size * vehicle_types);
3319 std::vector<std::vector<const Saving*>> out_savings_ptr(size * vehicle_types);
3320 for (int type = 0; type < vehicle_types; type++) {
3321 const int vehicle_type_offset = type * size;
3322 const std::vector<Saving>& sorted_savings_for_type =
3323 savings_container_->GetSortedSavingsForVehicleType(type);
3324 for (const Saving& saving : sorted_savings_for_type) {
3325 DCHECK_EQ(GetVehicleTypeFromSaving(saving), type);
3326 const int before_node = GetBeforeNodeFromSaving(saving);
3327 in_savings_ptr[vehicle_type_offset + before_node].push_back(&saving);
3328 const int after_node = GetAfterNodeFromSaving(saving);
3329 out_savings_ptr[vehicle_type_offset + after_node].push_back(&saving);
3330 }
3331 }
3332
3333 // Build routes from savings.
3334 while (savings_container_->HasSaving()) {
3335 if (StopSearch()) return;
3336 // First find the best saving to start a new route.
3337 const Saving saving = savings_container_->GetSaving();
3338 int before_node = GetBeforeNodeFromSaving(saving);
3339 int after_node = GetAfterNodeFromSaving(saving);
3340 const bool nodes_not_contained =
3341 !Contains(before_node) && !Contains(after_node);
3342
3343 bool committed = false;
3344
3345 if (nodes_not_contained) {
3346 // Find the right vehicle to start the route with this Saving.
3347 const int type = GetVehicleTypeFromSaving(saving);
3348 const int vehicle =
3349 StartNewRouteWithBestVehicleOfType(type, before_node, after_node);
3350
3351 if (vehicle >= 0) {
3352 committed = true;
3353 const int64_t start = model()->Start(vehicle);
3354 const int64_t end = model()->End(vehicle);
3355 // Then extend the route from both ends of the partial route.
3356 int in_index = 0;
3357 int out_index = 0;
3358 const int saving_offset = type * size;
3359
3360 while (in_index < in_savings_ptr[saving_offset + after_node].size() ||
3361 out_index <
3362 out_savings_ptr[saving_offset + before_node].size()) {
3363 if (StopSearch()) return;
3364 // First determine how to extend the route.
3365 int before_before_node = -1;
3366 int after_after_node = -1;
3367 if (in_index < in_savings_ptr[saving_offset + after_node].size()) {
3368 const Saving& in_saving =
3369 *(in_savings_ptr[saving_offset + after_node][in_index]);
3370 if (out_index <
3371 out_savings_ptr[saving_offset + before_node].size()) {
3372 const Saving& out_saving =
3373 *(out_savings_ptr[saving_offset + before_node][out_index]);
3374 if (GetSavingValue(in_saving) < GetSavingValue(out_saving)) {
3375 // Should extend after after_node
3376 after_after_node = GetAfterNodeFromSaving(in_saving);
3377 } else {
3378 // Should extend before before_node
3379 before_before_node = GetBeforeNodeFromSaving(out_saving);
3380 }
3381 } else {
3382 // Should extend after after_node
3383 after_after_node = GetAfterNodeFromSaving(in_saving);
3384 }
3385 } else {
3386 // Should extend before before_node
3387 before_before_node = GetBeforeNodeFromSaving(
3388 *(out_savings_ptr[saving_offset + before_node][out_index]));
3389 }
3390 // Extend the route
3391 if (after_after_node != -1) {
3392 DCHECK_EQ(before_before_node, -1);
3393 // Extending after after_node
3394 if (!Contains(after_after_node)) {
3395 SetValue(after_node, after_after_node);
3396 SetValue(after_after_node, end);
3397 if (Evaluate(/*commit=*/true).has_value()) {
3398 in_index = 0;
3399 after_node = after_after_node;
3400 } else {
3401 ++in_index;
3402 }
3403 } else {
3404 ++in_index;
3405 }
3406 } else {
3407 // Extending before before_node
3408 CHECK_GE(before_before_node, 0);
3409 if (!Contains(before_before_node)) {
3410 SetValue(start, before_before_node);
3411 SetValue(before_before_node, before_node);
3412 if (Evaluate(/*commit=*/true).has_value()) {
3413 out_index = 0;
3414 before_node = before_before_node;
3415 } else {
3416 ++out_index;
3417 }
3418 } else {
3419 ++out_index;
3420 }
3421 }
3422 }
3423 }
3424 }
3425 savings_container_->Update(nodes_not_contained && !committed);
3426 }
3427}
3428
3429// ParallelSavingsFilteredHeuristic
3430
3431void ParallelSavingsFilteredHeuristic::BuildRoutesFromSavings() {
3432 // Initialize the vehicles of the first/last non start/end nodes served by
3433 // each route.
3434 const int64_t size = model()->Size();
3435 const int vehicles = model()->vehicles();
3436
3437 first_node_on_route_.resize(vehicles, -1);
3438 last_node_on_route_.resize(vehicles, -1);
3439 vehicle_of_first_or_last_node_.resize(size, -1);
3440
3441 for (int vehicle = 0; vehicle < vehicles; vehicle++) {
3442 const int64_t start = model()->Start(vehicle);
3443 const int64_t end = model()->End(vehicle);
3444 if (!Contains(start)) {
3445 continue;
3446 }
3447 int64_t node = Value(start);
3448 if (node != end) {
3449 vehicle_of_first_or_last_node_[node] = vehicle;
3450 first_node_on_route_[vehicle] = node;
3451
3452 int64_t next = Value(node);
3453 while (next != end) {
3454 node = next;
3455 next = Value(node);
3456 }
3457 vehicle_of_first_or_last_node_[node] = vehicle;
3458 last_node_on_route_[vehicle] = node;
3459 }
3460 }
3461
3462 while (savings_container_->HasSaving()) {
3463 if (StopSearch()) return;
3464 const Saving saving = savings_container_->GetSaving();
3465 const int64_t before_node = GetBeforeNodeFromSaving(saving);
3466 const int64_t after_node = GetAfterNodeFromSaving(saving);
3467 const int type = GetVehicleTypeFromSaving(saving);
3468
3469 if (!Contains(before_node) && !Contains(after_node)) {
3470 // Neither nodes are contained, start a new route.
3471 bool committed = false;
3472
3473 const int vehicle =
3474 StartNewRouteWithBestVehicleOfType(type, before_node, after_node);
3475
3476 if (vehicle >= 0) {
3477 committed = true;
3478 // Store before_node and after_node as first and last nodes of the route
3479 vehicle_of_first_or_last_node_[before_node] = vehicle;
3480 vehicle_of_first_or_last_node_[after_node] = vehicle;
3481 first_node_on_route_[vehicle] = before_node;
3482 last_node_on_route_[vehicle] = after_node;
3483 savings_container_->ReinjectSkippedSavingsStartingAt(after_node);
3484 savings_container_->ReinjectSkippedSavingsEndingAt(before_node);
3485 }
3486 savings_container_->Update(!committed);
3487 continue;
3488 }
3489
3490 if (Contains(before_node) && Contains(after_node)) {
3491 // Merge the two routes if before_node is last and after_node first of its
3492 // route, the two nodes aren't already on the same route, and the vehicle
3493 // types are compatible.
3494 const int v1 = vehicle_of_first_or_last_node_[before_node];
3495 const int64_t last_node = v1 == -1 ? -1 : last_node_on_route_[v1];
3496
3497 const int v2 = vehicle_of_first_or_last_node_[after_node];
3498 const int64_t first_node = v2 == -1 ? -1 : first_node_on_route_[v2];
3499
3500 if (before_node == last_node && after_node == first_node && v1 != v2 &&
3501 vehicle_type_curator_->Type(v1) == vehicle_type_curator_->Type(v2)) {
3502 CHECK_EQ(Value(before_node), model()->End(v1));
3503 CHECK_EQ(Value(model()->Start(v2)), after_node);
3504
3505 // We try merging the two routes.
3506 // TODO(user): Try to use skipped savings to start new routes when
3507 // a vehicle becomes available after a merge (not trivial because it can
3508 // result in an infinite loop).
3509 MergeRoutes(v1, v2, before_node, after_node);
3510 }
3511 }
3512
3513 if (Contains(before_node) && !Contains(after_node)) {
3514 const int vehicle = vehicle_of_first_or_last_node_[before_node];
3515 const int64_t last_node =
3516 vehicle == -1 ? -1 : last_node_on_route_[vehicle];
3517
3518 if (before_node == last_node) {
3519 const int64_t end = model()->End(vehicle);
3520 CHECK_EQ(Value(before_node), end);
3521
3522 const int route_type = vehicle_type_curator_->Type(vehicle);
3523 if (type != route_type) {
3524 // The saving doesn't correspond to the type of the vehicle serving
3525 // before_node. We update the container with the correct type.
3526 savings_container_->UpdateWithType(route_type);
3527 continue;
3528 }
3529
3530 // Try adding after_node on route of before_node.
3531 SetValue(before_node, after_node);
3532 SetValue(after_node, end);
3533 if (Evaluate(/*commit=*/true).has_value()) {
3534 if (first_node_on_route_[vehicle] != before_node) {
3535 // before_node is no longer the start or end of its route
3536 DCHECK_NE(Value(model()->Start(vehicle)), before_node);
3537 vehicle_of_first_or_last_node_[before_node] = -1;
3538 }
3539 vehicle_of_first_or_last_node_[after_node] = vehicle;
3540 last_node_on_route_[vehicle] = after_node;
3541 savings_container_->ReinjectSkippedSavingsStartingAt(after_node);
3542 }
3543 }
3544 }
3545
3546 if (!Contains(before_node) && Contains(after_node)) {
3547 const int vehicle = vehicle_of_first_or_last_node_[after_node];
3548 const int64_t first_node =
3549 vehicle == -1 ? -1 : first_node_on_route_[vehicle];
3550
3551 if (after_node == first_node) {
3552 const int64_t start = model()->Start(vehicle);
3553 CHECK_EQ(Value(start), after_node);
3554
3555 const int route_type = vehicle_type_curator_->Type(vehicle);
3556 if (type != route_type) {
3557 // The saving doesn't correspond to the type of the vehicle serving
3558 // after_node. We update the container with the correct type.
3559 savings_container_->UpdateWithType(route_type);
3560 continue;
3561 }
3562
3563 // Try adding before_node on route of after_node.
3564 SetValue(before_node, after_node);
3565 SetValue(start, before_node);
3566 if (Evaluate(/*commit=*/true).has_value()) {
3567 if (last_node_on_route_[vehicle] != after_node) {
3568 // after_node is no longer the start or end of its route
3569 DCHECK_NE(Value(after_node), model()->End(vehicle));
3570 vehicle_of_first_or_last_node_[after_node] = -1;
3571 }
3572 vehicle_of_first_or_last_node_[before_node] = vehicle;
3573 first_node_on_route_[vehicle] = before_node;
3574 savings_container_->ReinjectSkippedSavingsEndingAt(before_node);
3575 }
3576 }
3577 }
3578 savings_container_->Update(/*update_best_saving*/ false);
3579 }
3580}
3581
3582void ParallelSavingsFilteredHeuristic::MergeRoutes(int first_vehicle,
3583 int second_vehicle,
3584 int64_t before_node,
3585 int64_t after_node) {
3586 if (StopSearch()) return;
3587 const int64_t new_first_node = first_node_on_route_[first_vehicle];
3588 DCHECK_EQ(vehicle_of_first_or_last_node_[new_first_node], first_vehicle);
3589 CHECK_EQ(Value(model()->Start(first_vehicle)), new_first_node);
3590 const int64_t new_last_node = last_node_on_route_[second_vehicle];
3591 DCHECK_EQ(vehicle_of_first_or_last_node_[new_last_node], second_vehicle);
3592 CHECK_EQ(Value(new_last_node), model()->End(second_vehicle));
3593
3594 // Select the vehicle with lower fixed cost to merge the routes.
3595 int used_vehicle = first_vehicle;
3596 int unused_vehicle = second_vehicle;
3597 if (model()->GetFixedCostOfVehicle(first_vehicle) >
3598 model()->GetFixedCostOfVehicle(second_vehicle)) {
3599 used_vehicle = second_vehicle;
3600 unused_vehicle = first_vehicle;
3601 }
3602
3603 SetValue(before_node, after_node);
3604 SetValue(model()->Start(unused_vehicle), model()->End(unused_vehicle));
3605 if (used_vehicle == first_vehicle) {
3606 SetValue(new_last_node, model()->End(used_vehicle));
3607 } else {
3608 SetValue(model()->Start(used_vehicle), new_first_node);
3609 }
3610 bool committed = Evaluate(/*commit=*/true).has_value();
3611 if (!committed &&
3612 model()->GetVehicleClassIndexOfVehicle(first_vehicle).value() !=
3613 model()->GetVehicleClassIndexOfVehicle(second_vehicle).value()) {
3614 // Try committing on other vehicle instead.
3615 std::swap(used_vehicle, unused_vehicle);
3616 SetValue(before_node, after_node);
3617 SetValue(model()->Start(unused_vehicle), model()->End(unused_vehicle));
3618 if (used_vehicle == first_vehicle) {
3619 SetValue(new_last_node, model()->End(used_vehicle));
3620 } else {
3621 SetValue(model()->Start(used_vehicle), new_first_node);
3622 }
3623 committed = Evaluate(/*commit=*/true).has_value();
3624 }
3625 if (committed) {
3626 // Make unused_vehicle available
3627 vehicle_type_curator_->ReinjectVehicleOfClass(
3628 unused_vehicle,
3629 model()->GetVehicleClassIndexOfVehicle(unused_vehicle).value(),
3630 model()->GetFixedCostOfVehicle(unused_vehicle));
3631
3632 // Update the first and last nodes on vehicles.
3633 first_node_on_route_[unused_vehicle] = -1;
3634 last_node_on_route_[unused_vehicle] = -1;
3635 vehicle_of_first_or_last_node_[before_node] = -1;
3636 vehicle_of_first_or_last_node_[after_node] = -1;
3637 first_node_on_route_[used_vehicle] = new_first_node;
3638 last_node_on_route_[used_vehicle] = new_last_node;
3639 vehicle_of_first_or_last_node_[new_last_node] = used_vehicle;
3640 vehicle_of_first_or_last_node_[new_first_node] = used_vehicle;
3641 }
3642}
3643
3644// ChristofidesFilteredHeuristic
3645
3648 bool use_minimum_matching)
3649 : RoutingFilteredHeuristic(model, filter_manager),
3650 use_minimum_matching_(use_minimum_matching) {}
3651
3652// TODO(user): Support pickup & delivery.
3654 const int size = model()->Size() - model()->vehicles() + 1;
3655 // Node indices for Christofides solver.
3656 // 0: start/end node
3657 // >0: non start/end nodes
3658 // TODO(user): Add robustness to fixed arcs by collapsing them into meta-
3659 // nodes.
3660 std::vector<int> indices(1, 0);
3661 for (int i = 1; i < size; ++i) {
3662 if (!model()->IsStart(i) && !model()->IsEnd(i)) {
3663 indices.push_back(i);
3664 }
3665 }
3666 const int num_cost_classes = model()->GetCostClassesCount();
3667 std::vector<std::vector<int>> path_per_cost_class(num_cost_classes);
3668 std::vector<bool> class_covered(num_cost_classes, false);
3669 for (int vehicle = 0; vehicle < model()->vehicles(); ++vehicle) {
3670 const int64_t cost_class =
3671 model()->GetCostClassIndexOfVehicle(vehicle).value();
3672 if (!class_covered[cost_class]) {
3673 class_covered[cost_class] = true;
3674 const int64_t start = model()->Start(vehicle);
3675 const int64_t end = model()->End(vehicle);
3676 auto cost = [this, &indices, start, end, cost_class](int from, int to) {
3677 DCHECK_LT(from, indices.size());
3678 DCHECK_LT(to, indices.size());
3679 const int from_index = (from == 0) ? start : indices[from];
3680 const int to_index = (to == 0) ? end : indices[to];
3681 const int64_t cost =
3682 model()->GetArcCostForClass(from_index, to_index, cost_class);
3683 // To avoid overflow issues, capping costs at kint64max/2, the maximum
3684 // value supported by MinCostPerfectMatching.
3685 // TODO(user): Investigate if ChristofidesPathSolver should not
3686 // return a status to bail out fast in case of problem.
3688 };
3689 using Cost = decltype(cost);
3691 indices.size(), cost);
3692 if (use_minimum_matching_) {
3693 christofides_solver.SetMatchingAlgorithm(
3695 MatchingAlgorithm::MINIMUM_WEIGHT_MATCHING);
3696 }
3697 if (christofides_solver.Solve()) {
3698 path_per_cost_class[cost_class] =
3699 christofides_solver.TravelingSalesmanPath();
3700 }
3701 }
3702 }
3703 // TODO(user): Investigate if sorting paths per cost improves solutions.
3704 for (int vehicle = 0; vehicle < model()->vehicles(); ++vehicle) {
3705 const int64_t cost_class =
3706 model()->GetCostClassIndexOfVehicle(vehicle).value();
3707 const std::vector<int>& path = path_per_cost_class[cost_class];
3708 if (path.empty()) continue;
3709 DCHECK_EQ(0, path[0]);
3710 DCHECK_EQ(0, path.back());
3711 // Extend route from start.
3712 int prev = GetStartChainEnd(vehicle);
3713 const int end = model()->End(vehicle);
3714 for (int i = 1; i < path.size() - 1 && prev != end; ++i) {
3715 if (StopSearch()) return false;
3716 int next = indices[path[i]];
3717 if (!Contains(next)) {
3718 SetValue(prev, next);
3719 SetValue(next, end);
3720 if (Evaluate(/*commit=*/true).has_value()) {
3721 prev = next;
3722 }
3723 }
3724 }
3725 }
3727 return Evaluate(/*commit=*/true).has_value();
3728}
3729
3730// Sweep heuristic
3731// TODO(user): Clean up to match other first solution strategies.
3732
3733namespace {
3734struct SweepIndex {
3735 SweepIndex(const int64_t index, const double angle, const double distance)
3737 ~SweepIndex() {}
3738
3739 int64_t index;
3740 double angle;
3741 double distance;
3742};
3743
3744struct SweepIndexSortAngle {
3745 bool operator()(const SweepIndex& node1, const SweepIndex& node2) const {
3746 return (node1.angle < node2.angle);
3747 }
3748} SweepIndexAngleComparator;
3749
3750struct SweepIndexSortDistance {
3751 bool operator()(const SweepIndex& node1, const SweepIndex& node2) const {
3752 return (node1.distance < node2.distance);
3753 }
3754} SweepIndexDistanceComparator;
3755} // namespace
3756
3758 const std::vector<std::pair<int64_t, int64_t>>& points)
3759 : coordinates_(2 * points.size(), 0), sectors_(1) {
3760 for (int64_t i = 0; i < points.size(); ++i) {
3761 coordinates_[2 * i] = points[i].first;
3762 coordinates_[2 * i + 1] = points[i].second;
3763 }
3764}
3765
3766// Splits the space of the indices into sectors and sorts the indices of each
3767// sector with ascending angle from the depot.
3768void SweepArranger::ArrangeIndices(std::vector<int64_t>* indices) {
3769 const double pi_rad = 3.14159265;
3770 // Suppose that the center is at x0, y0.
3771 const int x0 = coordinates_[0];
3772 const int y0 = coordinates_[1];
3773
3774 std::vector<SweepIndex> sweep_indices;
3775 for (int64_t index = 0; index < static_cast<int>(coordinates_.size()) / 2;
3776 ++index) {
3777 const int x = coordinates_[2 * index];
3778 const int y = coordinates_[2 * index + 1];
3779 const double x_delta = x - x0;
3780 const double y_delta = y - y0;
3781 double square_distance = x_delta * x_delta + y_delta * y_delta;
3782 double angle = square_distance == 0 ? 0 : std::atan2(y_delta, x_delta);
3783 angle = angle >= 0 ? angle : 2 * pi_rad + angle;
3784 SweepIndex sweep_index(index, angle, square_distance);
3785 sweep_indices.push_back(sweep_index);
3786 }
3787 std::sort(sweep_indices.begin(), sweep_indices.end(),
3788 SweepIndexDistanceComparator);
3789
3790 const int size = static_cast<int>(sweep_indices.size()) / sectors_;
3791 for (int sector = 0; sector < sectors_; ++sector) {
3792 std::vector<SweepIndex> cluster;
3793 std::vector<SweepIndex>::iterator begin =
3794 sweep_indices.begin() + sector * size;
3795 std::vector<SweepIndex>::iterator end =
3796 sector == sectors_ - 1 ? sweep_indices.end()
3797 : sweep_indices.begin() + (sector + 1) * size;
3798 std::sort(begin, end, SweepIndexAngleComparator);
3799 }
3800 for (const SweepIndex& sweep_index : sweep_indices) {
3801 indices->push_back(sweep_index.index);
3802 }
3803}
3804
3805namespace {
3806
3807struct Link {
3808 Link(std::pair<int, int> link, double value, int vehicle_class,
3809 int64_t start_depot, int64_t end_depot)
3810 : link(link),
3811 value(value),
3815 ~Link() {}
3816
3817 std::pair<int, int> link;
3818 int64_t value;
3821 int64_t end_depot;
3822};
3823
3824// The RouteConstructor creates the routes of a VRP instance subject to its
3825// constraints by iterating on a list of arcs appearing in descending order
3826// of priority.
3827// TODO(user): Use the dimension class in this class.
3828// TODO(user): Add support for vehicle-dependent dimension transits.
3829class RouteConstructor {
3830 public:
3831 RouteConstructor(Assignment* const assignment, RoutingModel* const model,
3832 bool check_assignment, int64_t num_indices,
3833 const std::vector<Link>& links_list)
3834 : assignment_(assignment),
3835 model_(model),
3836 check_assignment_(check_assignment),
3837 solver_(model_->solver()),
3838 num_indices_(num_indices),
3839 links_list_(links_list),
3840 nexts_(model_->Nexts()),
3841 in_route_(num_indices_, -1),
3842 final_routes_(),
3843 index_to_chain_index_(num_indices, -1),
3844 index_to_vehicle_class_index_(num_indices, -1) {
3845 {
3846 const std::vector<std::string> dimension_names =
3847 model_->GetAllDimensionNames();
3848 dimensions_.assign(dimension_names.size(), nullptr);
3849 for (int i = 0; i < dimension_names.size(); ++i) {
3850 dimensions_[i] = &model_->GetDimensionOrDie(dimension_names[i]);
3851 }
3852 }
3853 cumuls_.resize(dimensions_.size());
3854 for (std::vector<int64_t>& cumuls : cumuls_) {
3855 cumuls.resize(num_indices_);
3856 }
3857 new_possible_cumuls_.resize(dimensions_.size());
3858 }
3859
3860 ~RouteConstructor() {}
3861
3862 void Construct() {
3863 model_->solver()->TopPeriodicCheck();
3864 // Initial State: Each order is served by its own vehicle.
3865 for (int index = 0; index < num_indices_; ++index) {
3866 if (!model_->IsStart(index) && !model_->IsEnd(index)) {
3867 std::vector<int> route(1, index);
3868 routes_.push_back(route);
3869 in_route_[index] = routes_.size() - 1;
3870 }
3871 }
3872
3873 for (const Link& link : links_list_) {
3874 model_->solver()->TopPeriodicCheck();
3875 const int index1 = link.link.first;
3876 const int index2 = link.link.second;
3877 const int vehicle_class = link.vehicle_class;
3878 const int64_t start_depot = link.start_depot;
3879 const int64_t end_depot = link.end_depot;
3880
3881 // Initialisation of cumuls_ if the indices are encountered for first time
3882 if (index_to_vehicle_class_index_[index1] < 0) {
3883 for (int dimension_index = 0; dimension_index < dimensions_.size();
3884 ++dimension_index) {
3885 cumuls_[dimension_index][index1] =
3886 std::max(dimensions_[dimension_index]->GetTransitValue(
3887 start_depot, index1, 0),
3888 dimensions_[dimension_index]->CumulVar(index1)->Min());
3889 }
3890 }
3891 if (index_to_vehicle_class_index_[index2] < 0) {
3892 for (int dimension_index = 0; dimension_index < dimensions_.size();
3893 ++dimension_index) {
3894 cumuls_[dimension_index][index2] =
3895 std::max(dimensions_[dimension_index]->GetTransitValue(
3896 start_depot, index2, 0),
3897 dimensions_[dimension_index]->CumulVar(index2)->Min());
3898 }
3899 }
3900
3901 const int route_index1 = in_route_[index1];
3902 const int route_index2 = in_route_[index2];
3903 const bool merge =
3904 route_index1 >= 0 && route_index2 >= 0 &&
3905 FeasibleMerge(routes_[route_index1], routes_[route_index2], index1,
3906 index2, route_index1, route_index2, vehicle_class,
3908 if (Merge(merge, route_index1, route_index2)) {
3909 index_to_vehicle_class_index_[index1] = vehicle_class;
3910 index_to_vehicle_class_index_[index2] = vehicle_class;
3911 }
3912 }
3913
3914 model_->solver()->TopPeriodicCheck();
3915 // Beyond this point not checking limits anymore as the rest of the code is
3916 // linear and that given we managed to build a solution would be stupid to
3917 // drop it now.
3918 for (int chain_index = 0; chain_index < chains_.size(); ++chain_index) {
3919 if (!deleted_chains_.contains(chain_index)) {
3920 final_chains_.push_back(chains_[chain_index]);
3921 }
3922 }
3923 std::sort(final_chains_.begin(), final_chains_.end(), ChainComparator);
3924 for (int route_index = 0; route_index < routes_.size(); ++route_index) {
3925 if (!deleted_routes_.contains(route_index)) {
3926 final_routes_.push_back(routes_[route_index]);
3927 }
3928 }
3929 std::sort(final_routes_.begin(), final_routes_.end(), RouteComparator);
3930
3931 const int extra_vehicles = std::max(
3932 0, static_cast<int>(final_chains_.size()) - model_->vehicles());
3933 // Bind the Start and End of each chain
3934 int chain_index = 0;
3935 for (chain_index = extra_vehicles; chain_index < final_chains_.size();
3936 ++chain_index) {
3937 if (chain_index - extra_vehicles >= model_->vehicles()) {
3938 break;
3939 }
3940 const int start = final_chains_[chain_index].head;
3941 const int end = final_chains_[chain_index].tail;
3942 assignment_->Add(
3943 model_->NextVar(model_->Start(chain_index - extra_vehicles)));
3944 assignment_->SetValue(
3945 model_->NextVar(model_->Start(chain_index - extra_vehicles)), start);
3946 assignment_->Add(nexts_[end]);
3947 assignment_->SetValue(nexts_[end],
3948 model_->End(chain_index - extra_vehicles));
3949 }
3950
3951 // Create the single order routes
3952 for (int route_index = 0; route_index < final_routes_.size();
3953 ++route_index) {
3954 if (chain_index - extra_vehicles >= model_->vehicles()) {
3955 break;
3956 }
3957 DCHECK_LT(route_index, final_routes_.size());
3958 const int head = final_routes_[route_index].front();
3959 const int tail = final_routes_[route_index].back();
3960 if (head == tail && head < model_->Size()) {
3961 assignment_->Add(
3962 model_->NextVar(model_->Start(chain_index - extra_vehicles)));
3963 assignment_->SetValue(
3964 model_->NextVar(model_->Start(chain_index - extra_vehicles)), head);
3965 assignment_->Add(nexts_[tail]);
3966 assignment_->SetValue(nexts_[tail],
3967 model_->End(chain_index - extra_vehicles));
3968 ++chain_index;
3969 }
3970 }
3971
3972 // Unperformed
3973 for (int index = 0; index < model_->Size(); ++index) {
3974 IntVar* const next = nexts_[index];
3975 if (!assignment_->Contains(next)) {
3976 assignment_->Add(next);
3977 if (next->Contains(index)) {
3978 assignment_->SetValue(next, index);
3979 }
3980 }
3981 }
3982 }
3983
3984 private:
3985 enum MergeStatus { FIRST_SECOND, SECOND_FIRST, NO_MERGE };
3986
3987 struct RouteSort {
3988 bool operator()(const std::vector<int>& route1,
3989 const std::vector<int>& route2) const {
3990 return (route1.size() < route2.size());
3991 }
3992 } RouteComparator;
3993
3994 struct Chain {
3995 int head;
3996 int tail;
3998 };
3999
4000 struct ChainSort {
4001 bool operator()(const Chain& chain1, const Chain& chain2) const {
4002 return (chain1.nodes < chain2.nodes);
4003 }
4004 } ChainComparator;
4005
4006 bool Head(int node) const {
4007 return (node == routes_[in_route_[node]].front());
4008 }
4009
4010 bool Tail(int node) const {
4011 return (node == routes_[in_route_[node]].back());
4012 }
4013
4014 bool FeasibleRoute(const std::vector<int>& route, int64_t route_cumul,
4015 int dimension_index) {
4016 const RoutingDimension& dimension = *dimensions_[dimension_index];
4017 std::vector<int>::const_iterator it = route.begin();
4018 int64_t cumul = route_cumul;
4019 while (it != route.end()) {
4020 const int previous = *it;
4021 const int64_t cumul_previous = cumul;
4022 gtl::InsertOrDie(&(new_possible_cumuls_[dimension_index]), previous,
4023 cumul_previous);
4024 ++it;
4025 if (it == route.end()) {
4026 return true;
4027 }
4028 const int next = *it;
4029 int64_t available_from_previous =
4030 cumul_previous + dimension.GetTransitValue(previous, next, 0);
4031 int64_t available_cumul_next =
4032 std::max(cumuls_[dimension_index][next], available_from_previous);
4033
4034 const int64_t slack = available_cumul_next - available_from_previous;
4035 if (slack > dimension.SlackVar(previous)->Max()) {
4036 available_cumul_next =
4037 available_from_previous + dimension.SlackVar(previous)->Max();
4038 }
4039
4040 if (available_cumul_next > dimension.CumulVar(next)->Max()) {
4041 return false;
4042 }
4043 if (available_cumul_next <= cumuls_[dimension_index][next]) {
4044 return true;
4045 }
4046 cumul = available_cumul_next;
4047 }
4048 return true;
4049 }
4050
4051 bool CheckRouteConnection(const std::vector<int>& route1,
4052 const std::vector<int>& route2, int dimension_index,
4053 int64_t start_depot, int64_t end_depot) {
4054 const int tail1 = route1.back();
4055 const int head2 = route2.front();
4056 const int tail2 = route2.back();
4057 const RoutingDimension& dimension = *dimensions_[dimension_index];
4058 int non_depot_node = -1;
4059 for (int node = 0; node < num_indices_; ++node) {
4060 if (!model_->IsStart(node) && !model_->IsEnd(node)) {
4061 non_depot_node = node;
4062 break;
4063 }
4064 }
4065 CHECK_GE(non_depot_node, 0);
4066 const int64_t depot_threshold =
4067 std::max(dimension.SlackVar(non_depot_node)->Max(),
4068 dimension.CumulVar(non_depot_node)->Max());
4069
4070 int64_t available_from_tail1 = cumuls_[dimension_index][tail1] +
4071 dimension.GetTransitValue(tail1, head2, 0);
4072 int64_t new_available_cumul_head2 =
4073 std::max(cumuls_[dimension_index][head2], available_from_tail1);
4074
4075 const int64_t slack = new_available_cumul_head2 - available_from_tail1;
4076 if (slack > dimension.SlackVar(tail1)->Max()) {
4077 new_available_cumul_head2 =
4078 available_from_tail1 + dimension.SlackVar(tail1)->Max();
4079 }
4080
4081 bool feasible_route = true;
4082 if (new_available_cumul_head2 > dimension.CumulVar(head2)->Max()) {
4083 return false;
4084 }
4085 if (new_available_cumul_head2 <= cumuls_[dimension_index][head2]) {
4086 return true;
4087 }
4088
4089 feasible_route =
4090 FeasibleRoute(route2, new_available_cumul_head2, dimension_index);
4091 const int64_t new_possible_cumul_tail2 =
4092 new_possible_cumuls_[dimension_index].contains(tail2)
4093 ? new_possible_cumuls_[dimension_index][tail2]
4094 : cumuls_[dimension_index][tail2];
4095
4096 if (!feasible_route || (new_possible_cumul_tail2 +
4097 dimension.GetTransitValue(tail2, end_depot, 0) >
4098 depot_threshold)) {
4099 return false;
4100 }
4101 return true;
4102 }
4103
4104 bool FeasibleMerge(const std::vector<int>& route1,
4105 const std::vector<int>& route2, int node1, int node2,
4106 int route_index1, int route_index2, int vehicle_class,
4107 int64_t start_depot, int64_t end_depot) {
4108 if ((route_index1 == route_index2) || !(Tail(node1) && Head(node2))) {
4109 return false;
4110 }
4111
4112 // Vehicle Class Check
4113 if (!((index_to_vehicle_class_index_[node1] == -1 &&
4114 index_to_vehicle_class_index_[node2] == -1) ||
4115 (index_to_vehicle_class_index_[node1] == vehicle_class &&
4116 index_to_vehicle_class_index_[node2] == -1) ||
4117 (index_to_vehicle_class_index_[node1] == -1 &&
4118 index_to_vehicle_class_index_[node2] == vehicle_class) ||
4119 (index_to_vehicle_class_index_[node1] == vehicle_class &&
4120 index_to_vehicle_class_index_[node2] == vehicle_class))) {
4121 return false;
4122 }
4123
4124 // Check Route1 -> Route2 connection for every dimension
4125 bool merge = true;
4126 for (int dimension_index = 0; dimension_index < dimensions_.size();
4127 ++dimension_index) {
4128 new_possible_cumuls_[dimension_index].clear();
4129 merge = merge && CheckRouteConnection(route1, route2, dimension_index,
4131 if (!merge) {
4132 return false;
4133 }
4134 }
4135 return true;
4136 }
4137
4138 bool CheckTempAssignment(Assignment* const temp_assignment,
4139 int new_chain_index, int old_chain_index, int head1,
4140 int tail1, int head2, int tail2) {
4141 // TODO(user): If the chain index is greater than the number of vehicles,
4142 // use another vehicle instead.
4143 if (new_chain_index >= model_->vehicles()) return false;
4144 const int start = head1;
4145 temp_assignment->Add(model_->NextVar(model_->Start(new_chain_index)));
4146 temp_assignment->SetValue(model_->NextVar(model_->Start(new_chain_index)),
4147 start);
4148 temp_assignment->Add(nexts_[tail1]);
4149 temp_assignment->SetValue(nexts_[tail1], head2);
4150 temp_assignment->Add(nexts_[tail2]);
4151 temp_assignment->SetValue(nexts_[tail2], model_->End(new_chain_index));
4152 for (int chain_index = 0; chain_index < chains_.size(); ++chain_index) {
4153 if ((chain_index != new_chain_index) &&
4154 (chain_index != old_chain_index) &&
4155 (!deleted_chains_.contains(chain_index))) {
4156 const int start = chains_[chain_index].head;
4157 const int end = chains_[chain_index].tail;
4158 temp_assignment->Add(model_->NextVar(model_->Start(chain_index)));
4159 temp_assignment->SetValue(model_->NextVar(model_->Start(chain_index)),
4160 start);
4161 temp_assignment->Add(nexts_[end]);
4162 temp_assignment->SetValue(nexts_[end], model_->End(chain_index));
4163 }
4164 }
4165 return solver_->Solve(solver_->MakeRestoreAssignment(temp_assignment));
4166 }
4167
4168 bool UpdateAssignment(const std::vector<int>& route1,
4169 const std::vector<int>& route2) {
4170 bool feasible = true;
4171 const int head1 = route1.front();
4172 const int tail1 = route1.back();
4173 const int head2 = route2.front();
4174 const int tail2 = route2.back();
4175 const int chain_index1 = index_to_chain_index_[head1];
4176 const int chain_index2 = index_to_chain_index_[head2];
4177 if (chain_index1 < 0 && chain_index2 < 0) {
4178 const int chain_index = chains_.size();
4179 if (check_assignment_) {
4180 Assignment* const temp_assignment =
4181 solver_->MakeAssignment(assignment_);
4182 feasible = CheckTempAssignment(temp_assignment, chain_index, -1, head1,
4183 tail1, head2, tail2);
4184 }
4185 if (feasible) {
4186 Chain chain;
4187 chain.head = head1;
4188 chain.tail = tail2;
4189 chain.nodes = 2;
4190 index_to_chain_index_[head1] = chain_index;
4191 index_to_chain_index_[tail2] = chain_index;
4192 chains_.push_back(chain);
4193 }
4194 } else if (chain_index1 >= 0 && chain_index2 < 0) {
4195 if (check_assignment_) {
4196 Assignment* const temp_assignment =
4197 solver_->MakeAssignment(assignment_);
4198 feasible =
4199 CheckTempAssignment(temp_assignment, chain_index1, chain_index2,
4200 head1, tail1, head2, tail2);
4201 }
4202 if (feasible) {
4203 index_to_chain_index_[tail2] = chain_index1;
4204 chains_[chain_index1].head = head1;
4205 chains_[chain_index1].tail = tail2;
4206 ++chains_[chain_index1].nodes;
4207 }
4208 } else if (chain_index1 < 0 && chain_index2 >= 0) {
4209 if (check_assignment_) {
4210 Assignment* const temp_assignment =
4211 solver_->MakeAssignment(assignment_);
4212 feasible =
4213 CheckTempAssignment(temp_assignment, chain_index2, chain_index1,
4214 head1, tail1, head2, tail2);
4215 }
4216 if (feasible) {
4217 index_to_chain_index_[head1] = chain_index2;
4218 chains_[chain_index2].head = head1;
4219 chains_[chain_index2].tail = tail2;
4220 ++chains_[chain_index2].nodes;
4221 }
4222 } else {
4223 if (check_assignment_) {
4224 Assignment* const temp_assignment =
4225 solver_->MakeAssignment(assignment_);
4226 feasible =
4227 CheckTempAssignment(temp_assignment, chain_index1, chain_index2,
4228 head1, tail1, head2, tail2);
4229 }
4230 if (feasible) {
4231 index_to_chain_index_[tail2] = chain_index1;
4232 chains_[chain_index1].head = head1;
4233 chains_[chain_index1].tail = tail2;
4234 chains_[chain_index1].nodes += chains_[chain_index2].nodes;
4235 deleted_chains_.insert(chain_index2);
4236 }
4237 }
4238 if (feasible) {
4239 assignment_->Add(nexts_[tail1]);
4240 assignment_->SetValue(nexts_[tail1], head2);
4241 }
4242 return feasible;
4243 }
4244
4245 bool Merge(bool merge, int index1, int index2) {
4246 if (merge) {
4247 if (UpdateAssignment(routes_[index1], routes_[index2])) {
4248 // Connection Route1 -> Route2
4249 for (const int node : routes_[index2]) {
4250 in_route_[node] = index1;
4251 routes_[index1].push_back(node);
4252 }
4253 for (int dimension_index = 0; dimension_index < dimensions_.size();
4254 ++dimension_index) {
4255 for (const std::pair<int, int64_t> new_possible_cumul :
4256 new_possible_cumuls_[dimension_index]) {
4257 cumuls_[dimension_index][new_possible_cumul.first] =
4258 new_possible_cumul.second;
4259 }
4260 }
4261 deleted_routes_.insert(index2);
4262 return true;
4263 }
4264 }
4265 return false;
4266 }
4267
4268 Assignment* const assignment_;
4269 RoutingModel* const model_;
4270 const bool check_assignment_;
4271 Solver* const solver_;
4272 const int64_t num_indices_;
4273 const std::vector<Link> links_list_;
4274 std::vector<IntVar*> nexts_;
4275 std::vector<const RoutingDimension*> dimensions_; // Not owned.
4276 std::vector<std::vector<int64_t>> cumuls_;
4277 std::vector<absl::flat_hash_map<int, int64_t>> new_possible_cumuls_;
4278 std::vector<std::vector<int>> routes_;
4279 std::vector<int> in_route_;
4280 absl::flat_hash_set<int> deleted_routes_;
4281 std::vector<std::vector<int>> final_routes_;
4282 std::vector<Chain> chains_;
4283 absl::flat_hash_set<int> deleted_chains_;
4284 std::vector<Chain> final_chains_;
4285 std::vector<int> index_to_chain_index_;
4286 std::vector<int> index_to_vehicle_class_index_;
4287};
4288
4289// Decision Builder building a first solution based on Sweep heuristic for
4290// Vehicle Routing Problem.
4291// Suitable only when distance is considered as the cost.
4292class SweepBuilder : public DecisionBuilder {
4293 public:
4294 SweepBuilder(RoutingModel* const model, bool check_assignment)
4295 : model_(model), check_assignment_(check_assignment) {}
4296 ~SweepBuilder() override {}
4297
4298 Decision* Next(Solver* const solver) override {
4299 // Setup the model of the instance for the Sweep Algorithm
4300 ModelSetup();
4301
4302 // Build the assignment routes for the model
4303 Assignment* const assignment = solver->MakeAssignment();
4304 route_constructor_ = absl::make_unique<RouteConstructor>(
4305 assignment, model_, check_assignment_, num_indices_, links_);
4306 // This call might cause backtracking if the search limit is reached.
4307 route_constructor_->Construct();
4308 route_constructor_.reset(nullptr);
4309 // This call might cause backtracking if the solution is not feasible.
4310 assignment->Restore();
4311
4312 return nullptr;
4313 }
4314
4315 private:
4316 void ModelSetup() {
4317 const int depot = model_->GetDepot();
4318 num_indices_ = model_->Size() + model_->vehicles();
4319 if (absl::GetFlag(FLAGS_sweep_sectors) > 0 &&
4320 absl::GetFlag(FLAGS_sweep_sectors) < num_indices_) {
4321 model_->sweep_arranger()->SetSectors(absl::GetFlag(FLAGS_sweep_sectors));
4322 }
4323 std::vector<int64_t> indices;
4324 model_->sweep_arranger()->ArrangeIndices(&indices);
4325 for (int i = 0; i < indices.size() - 1; ++i) {
4326 const int64_t first = indices[i];
4327 const int64_t second = indices[i + 1];
4328 if ((model_->IsStart(first) || !model_->IsEnd(first)) &&
4329 (model_->IsStart(second) || !model_->IsEnd(second))) {
4330 if (first != depot && second != depot) {
4331 Link link(std::make_pair(first, second), 0, 0, depot, depot);
4332 links_.push_back(link);
4333 }
4334 }
4335 }
4336 }
4337
4338 RoutingModel* const model_;
4339 std::unique_ptr<RouteConstructor> route_constructor_;
4340 const bool check_assignment_;
4341 int64_t num_indices_;
4342 std::vector<Link> links_;
4343};
4344} // namespace
4345
4347 bool check_assignment) {
4348 return model->solver()->RevAlloc(new SweepBuilder(model, check_assignment));
4349}
4350
4351// AllUnperformed
4352
4353namespace {
4354// Decision builder to build a solution with all nodes inactive. It does no
4355// branching and may fail if some nodes cannot be made inactive.
4356
4357class AllUnperformed : public DecisionBuilder {
4358 public:
4359 // Does not take ownership of model.
4360 explicit AllUnperformed(RoutingModel* const model) : model_(model) {}
4361 ~AllUnperformed() override {}
4362 Decision* Next(Solver* const solver) override {
4363 // Solver::(Un)FreezeQueue is private, passing through the public API
4364 // on PropagationBaseObject.
4365 model_->CostVar()->FreezeQueue();
4366 for (int i = 0; i < model_->Size(); ++i) {
4367 if (!model_->IsStart(i)) {
4368 model_->ActiveVar(i)->SetValue(0);
4369 }
4370 }
4371 model_->CostVar()->UnfreezeQueue();
4372 return nullptr;
4373 }
4374
4375 private:
4376 RoutingModel* const model_;
4377};
4378} // namespace
4379
4381 return model->solver()->RevAlloc(new AllUnperformed(model));
4382}
4383
4384namespace {
4385// The description is in routing.h:MakeGuidedSlackFinalizer
4386class GuidedSlackFinalizer : public DecisionBuilder {
4387 public:
4388 GuidedSlackFinalizer(const RoutingDimension* dimension, RoutingModel* model,
4389 std::function<int64_t(int64_t)> initializer);
4390 Decision* Next(Solver* solver) override;
4391
4392 private:
4393 int64_t SelectValue(int64_t index);
4394 int64_t ChooseVariable();
4395
4396 const RoutingDimension* const dimension_;
4397 RoutingModel* const model_;
4398 const std::function<int64_t(int64_t)> initializer_;
4399 RevArray<bool> is_initialized_;
4400 std::vector<int64_t> initial_values_;
4401 Rev<int64_t> current_index_;
4402 Rev<int64_t> current_route_;
4403 RevArray<int64_t> last_delta_used_;
4404
4405 DISALLOW_COPY_AND_ASSIGN(GuidedSlackFinalizer);
4406};
4407
4408GuidedSlackFinalizer::GuidedSlackFinalizer(
4409 const RoutingDimension* dimension, RoutingModel* model,
4410 std::function<int64_t(int64_t)> initializer)
4411 : dimension_(ABSL_DIE_IF_NULL(dimension)),
4412 model_(ABSL_DIE_IF_NULL(model)),
4413 initializer_(std::move(initializer)),
4414 is_initialized_(dimension->slacks().size(), false),
4415 initial_values_(dimension->slacks().size(),
4416 std::numeric_limits<int64_t>::min()),
4417 current_index_(model_->Start(0)),
4418 current_route_(0),
4419 last_delta_used_(dimension->slacks().size(), 0) {}
4420
4421Decision* GuidedSlackFinalizer::Next(Solver* solver) {
4422 CHECK_EQ(solver, model_->solver());
4423 const int node_idx = ChooseVariable();
4424 CHECK(node_idx == -1 ||
4425 (node_idx >= 0 && node_idx < dimension_->slacks().size()));
4426 if (node_idx != -1) {
4427 if (!is_initialized_[node_idx]) {
4428 initial_values_[node_idx] = initializer_(node_idx);
4429 is_initialized_.SetValue(solver, node_idx, true);
4430 }
4431 const int64_t value = SelectValue(node_idx);
4432 IntVar* const slack_variable = dimension_->SlackVar(node_idx);
4433 return solver->MakeAssignVariableValue(slack_variable, value);
4434 }
4435 return nullptr;
4436}
4437
4438int64_t GuidedSlackFinalizer::SelectValue(int64_t index) {
4439 const IntVar* const slack_variable = dimension_->SlackVar(index);
4440 const int64_t center = initial_values_[index];
4441 const int64_t max_delta =
4442 std::max(center - slack_variable->Min(), slack_variable->Max() - center) +
4443 1;
4444 int64_t delta = last_delta_used_[index];
4445
4446 // The sequence of deltas is 0, 1, -1, 2, -2 ...
4447 // Only the values inside the domain of variable are returned.
4448 while (std::abs(delta) < max_delta &&
4449 !slack_variable->Contains(center + delta)) {
4450 if (delta > 0) {
4451 delta = -delta;
4452 } else {
4453 delta = -delta + 1;
4454 }
4455 }
4456 last_delta_used_.SetValue(model_->solver(), index, delta);
4457 return center + delta;
4458}
4459
4460int64_t GuidedSlackFinalizer::ChooseVariable() {
4461 int64_t int_current_node = current_index_.Value();
4462 int64_t int_current_route = current_route_.Value();
4463
4464 while (int_current_route < model_->vehicles()) {
4465 while (!model_->IsEnd(int_current_node) &&
4466 dimension_->SlackVar(int_current_node)->Bound()) {
4467 int_current_node = model_->NextVar(int_current_node)->Value();
4468 }
4469 if (!model_->IsEnd(int_current_node)) {
4470 break;
4471 }
4472 int_current_route += 1;
4473 if (int_current_route < model_->vehicles()) {
4474 int_current_node = model_->Start(int_current_route);
4475 }
4476 }
4477
4478 CHECK(int_current_route == model_->vehicles() ||
4479 !dimension_->SlackVar(int_current_node)->Bound());
4480 current_index_.SetValue(model_->solver(), int_current_node);
4481 current_route_.SetValue(model_->solver(), int_current_route);
4482 if (int_current_route < model_->vehicles()) {
4483 return int_current_node;
4484 } else {
4485 return -1;
4486 }
4487}
4488} // namespace
4489
4490DecisionBuilder* RoutingModel::MakeGuidedSlackFinalizer(
4491 const RoutingDimension* dimension,
4492 std::function<int64_t(int64_t)> initializer) {
4493 return solver_->RevAlloc(
4494 new GuidedSlackFinalizer(dimension, this, std::move(initializer)));
4495}
4496
4497int64_t RoutingDimension::ShortestTransitionSlack(int64_t node) const {
4498 CHECK_EQ(base_dimension_, this);
4499 CHECK(!model_->IsEnd(node));
4500 // Recall that the model is cumul[i+1] = cumul[i] + transit[i] + slack[i]. Our
4501 // aim is to find a value for slack[i] such that cumul[i+1] + transit[i+1] is
4502 // minimized.
4503 const int64_t next = model_->NextVar(node)->Value();
4504 if (model_->IsEnd(next)) {
4505 return SlackVar(node)->Min();
4506 }
4507 const int64_t next_next = model_->NextVar(next)->Value();
4508 const int64_t serving_vehicle = model_->VehicleVar(node)->Value();
4509 CHECK_EQ(serving_vehicle, model_->VehicleVar(next)->Value());
4510 const RoutingModel::StateDependentTransit transit_from_next =
4511 model_->StateDependentTransitCallback(
4512 state_dependent_class_evaluators_
4513 [state_dependent_vehicle_to_class_[serving_vehicle]])(next,
4514 next_next);
4515 // We have that transit[i+1] is a function of cumul[i+1].
4516 const int64_t next_cumul_min = CumulVar(next)->Min();
4517 const int64_t next_cumul_max = CumulVar(next)->Max();
4518 const int64_t optimal_next_cumul =
4519 transit_from_next.transit_plus_identity->RangeMinArgument(
4520 next_cumul_min, next_cumul_max + 1);
4521 // A few checks to make sure we're on the same page.
4522 DCHECK_LE(next_cumul_min, optimal_next_cumul);
4523 DCHECK_LE(optimal_next_cumul, next_cumul_max);
4524 // optimal_next_cumul = cumul + transit + optimal_slack, so
4525 // optimal_slack = optimal_next_cumul - cumul - transit.
4526 // In the current implementation TransitVar(i) = transit[i] + slack[i], so we
4527 // have to find the transit from the evaluators.
4528 const int64_t current_cumul = CumulVar(node)->Value();
4529 const int64_t current_state_independent_transit = model_->TransitCallback(
4530 class_evaluators_[vehicle_to_class_[serving_vehicle]])(node, next);
4531 const int64_t current_state_dependent_transit =
4532 model_
4533 ->StateDependentTransitCallback(
4534 state_dependent_class_evaluators_
4535 [state_dependent_vehicle_to_class_[serving_vehicle]])(node,
4536 next)
4537 .transit->Query(current_cumul);
4538 const int64_t optimal_slack = optimal_next_cumul - current_cumul -
4539 current_state_independent_transit -
4540 current_state_dependent_transit;
4541 CHECK_LE(SlackVar(node)->Min(), optimal_slack);
4542 CHECK_LE(optimal_slack, SlackVar(node)->Max());
4543 return optimal_slack;
4544}
4545
4546namespace {
4547class GreedyDescentLSOperator : public LocalSearchOperator {
4548 public:
4549 explicit GreedyDescentLSOperator(std::vector<IntVar*> variables);
4550
4551 bool MakeNextNeighbor(Assignment* delta, Assignment* deltadelta) override;
4552 void Start(const Assignment* assignment) override;
4553
4554 private:
4555 int64_t FindMaxDistanceToDomain(const Assignment* assignment);
4556
4557 const std::vector<IntVar*> variables_;
4558 const Assignment* center_;
4559 int64_t current_step_;
4560 // The deltas are returned in this order:
4561 // (current_step_, 0, ... 0), (-current_step_, 0, ... 0),
4562 // (0, current_step_, ... 0), (0, -current_step_, ... 0),
4563 // ...
4564 // (0, ... 0, current_step_), (0, ... 0, -current_step_).
4565 // current_direction_ keeps track what was the last returned delta.
4566 int64_t current_direction_;
4567
4568 DISALLOW_COPY_AND_ASSIGN(GreedyDescentLSOperator);
4569};
4570
4571GreedyDescentLSOperator::GreedyDescentLSOperator(std::vector<IntVar*> variables)
4572 : variables_(std::move(variables)),
4573 center_(nullptr),
4574 current_step_(0),
4575 current_direction_(0) {}
4576
4577bool GreedyDescentLSOperator::MakeNextNeighbor(Assignment* delta,
4578 Assignment* /*deltadelta*/) {
4579 static const int64_t sings[] = {1, -1};
4580 for (; 1 <= current_step_; current_step_ /= 2) {
4581 for (; current_direction_ < 2 * variables_.size();) {
4582 const int64_t variable_idx = current_direction_ / 2;
4583 IntVar* const variable = variables_[variable_idx];
4584 const int64_t sign_index = current_direction_ % 2;
4585 const int64_t sign = sings[sign_index];
4586 const int64_t offset = sign * current_step_;
4587 const int64_t new_value = center_->Value(variable) + offset;
4588 ++current_direction_;
4589 if (variable->Contains(new_value)) {
4590 delta->Add(variable);
4591 delta->SetValue(variable, new_value);
4592 return true;
4593 }
4594 }
4595 current_direction_ = 0;
4596 }
4597 return false;
4598}
4599
4600void GreedyDescentLSOperator::Start(const Assignment* assignment) {
4601 CHECK(assignment != nullptr);
4602 current_step_ = FindMaxDistanceToDomain(assignment);
4603 center_ = assignment;
4604}
4605
4606int64_t GreedyDescentLSOperator::FindMaxDistanceToDomain(
4607 const Assignment* assignment) {
4608 int64_t result = std::numeric_limits<int64_t>::min();
4609 for (const IntVar* const var : variables_) {
4610 result = std::max(result, std::abs(var->Max() - assignment->Value(var)));
4611 result = std::max(result, std::abs(var->Min() - assignment->Value(var)));
4612 }
4613 return result;
4614}
4615} // namespace
4616
4617std::unique_ptr<LocalSearchOperator> RoutingModel::MakeGreedyDescentLSOperator(
4618 std::vector<IntVar*> variables) {
4619 return std::unique_ptr<LocalSearchOperator>(
4620 new GreedyDescentLSOperator(std::move(variables)));
4621}
4622
4624 const RoutingDimension* dimension) {
4625 CHECK(dimension != nullptr);
4626 CHECK(dimension->base_dimension() == dimension);
4627 std::function<int64_t(int64_t)> slack_guide = [dimension](int64_t index) {
4628 return dimension->ShortestTransitionSlack(index);
4629 };
4630 DecisionBuilder* const guided_finalizer =
4631 MakeGuidedSlackFinalizer(dimension, slack_guide);
4632 DecisionBuilder* const slacks_finalizer =
4633 solver_->MakeSolveOnce(guided_finalizer);
4634 std::vector<IntVar*> start_cumuls(vehicles_, nullptr);
4635 for (int64_t vehicle_idx = 0; vehicle_idx < vehicles_; ++vehicle_idx) {
4636 start_cumuls[vehicle_idx] = dimension->CumulVar(starts_[vehicle_idx]);
4637 }
4638 LocalSearchOperator* const hill_climber =
4639 solver_->RevAlloc(new GreedyDescentLSOperator(start_cumuls));
4641 solver_->MakeLocalSearchPhaseParameters(CostVar(), hill_climber,
4642 slacks_finalizer);
4643 Assignment* const first_solution = solver_->MakeAssignment();
4644 first_solution->Add(start_cumuls);
4645 for (IntVar* const cumul : start_cumuls) {
4646 first_solution->SetValue(cumul, cumul->Min());
4647 }
4648 DecisionBuilder* const finalizer =
4649 solver_->MakeLocalSearchPhase(first_solution, parameters);
4650 return finalizer;
4651}
4652} // namespace operations_research
const std::vector< IntVar * > vars_
Definition: alldiff_cst.cc:44
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define CHECK(condition)
Definition: base/logging.h:495
#define DCHECK_LE(val1, val2)
Definition: base/logging.h:893
#define DCHECK_NE(val1, val2)
Definition: base/logging.h:892
#define CHECK_LT(val1, val2)
Definition: base/logging.h:706
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:703
#define CHECK_GE(val1, val2)
Definition: base/logging.h:707
#define CHECK_GT(val1, val2)
Definition: base/logging.h:708
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:895
#define DCHECK_GT(val1, val2)
Definition: base/logging.h:896
#define DCHECK_LT(val1, val2)
Definition: base/logging.h:894
#define DCHECK(condition)
Definition: base/logging.h:890
#define CHECK_LE(val1, val2)
Definition: base/logging.h:705
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:891
#define ABSL_DIE_IF_NULL
Definition: base/logging.h:43
#define VLOG(verboselevel)
Definition: base/logging.h:984
bool Contains(const T *val) const
const E & Element(const V *const var) const
E * AddAtPosition(V *var, int position)
Advanced usage: Adds element at a given position; position has to have been allocated with Assignment...
void Resize(size_t size)
Advanced usage: Resizes the container, potentially adding elements with null variables.
An Assignment is a variable -> domains mapping, used to report solutions to the user.
void SetValue(const IntVar *const var, int64_t value)
int64_t Value(const IntVar *const var) const
IntVarElement * Add(IntVar *const var)
const IntContainer & IntVarContainer() const
Filtered-base decision builder based on the addition heuristic, extending a path from its start node ...
bool BuildSolutionInternal() override
Virtual method to redefine how to build a solution.
CheapestAdditionFilteredHeuristic(RoutingModel *model, LocalSearchFilterManager *filter_manager)
std::vector< std::vector< StartEndValue > > ComputeStartEndDistanceForVehicles(const std::vector< int > &vehicles)
Computes and returns the distance of each uninserted node to every vehicle in "vehicles" as a std::ve...
std::function< int64_t(int64_t, int64_t, int64_t)> evaluator_
void InitializePriorityQueue(std::vector< std::vector< StartEndValue > > *start_end_distances_per_node, Queue *priority_queue)
Initializes the priority_queue by inserting the best entry corresponding to each node,...
int64_t GetInsertionCostForNodeAtPosition(int64_t node_to_insert, int64_t insert_after, int64_t insert_before, int vehicle) const
Returns the cost of inserting 'node_to_insert' between 'insert_after' and 'insert_before' on the 'veh...
int64_t GetUnperformedValue(int64_t node_to_insert) const
Returns the cost of unperforming node 'node_to_insert'.
CheapestInsertionFilteredHeuristic(RoutingModel *model, std::function< int64_t(int64_t, int64_t, int64_t)> evaluator, std::function< int64_t(int64_t)> penalty_evaluator, LocalSearchFilterManager *filter_manager)
Takes ownership of evaluator.
void InsertBetween(int64_t node, int64_t predecessor, int64_t successor, int vehicle=-1)
Inserts 'node' just after 'predecessor', and just before 'successor' on the route of 'vehicle',...
void AppendInsertionPositionsAfter(int64_t node_to_insert, int64_t start, int64_t next_after_start, int vehicle, std::vector< NodeInsertion > *node_insertions)
Helper method to the ComputeEvaluatorSortedPositions* methods.
bool BuildSolutionInternal() override
Virtual method to redefine how to build a solution.
ChristofidesFilteredHeuristic(RoutingModel *model, LocalSearchFilterManager *filter_manager, bool use_minimum_matching)
std::vector< NodeIndex > TravelingSalesmanPath()
Definition: christofides.h:241
void SetMatchingAlgorithm(MatchingAlgorithm matching)
Definition: christofides.h:69
ComparatorCheapestAdditionFilteredHeuristic(RoutingModel *model, Solver::VariableValueComparator comparator, LocalSearchFilterManager *filter_manager)
Takes ownership of evaluator.
A DecisionBuilder is responsible for creating the search tree.
A Decision represents a choice point in the search tree.
EvaluatorCheapestAdditionFilteredHeuristic(RoutingModel *model, std::function< int64_t(int64_t, int64_t)> evaluator, LocalSearchFilterManager *filter_manager)
Takes ownership of evaluator.
bool operator<(const NodeEntry &other) const
int vehicle() const
void set_vehicle(int vehicle)
int insert_after() const
int GetHeapIndex() const
NodeEntry(int node_to_insert, int insert_after, int vehicle, int64_t bucket)
void set_insert_after(int insert_after)
void SetHeapIndex(int h)
void set_value(int64_t value)
int node_to_insert() const
bool operator<(const PairEntry &other) const
int vehicle() const
void set_vehicle(int vehicle)
int GetHeapIndex() const
PairEntry(int pickup_to_insert, int pickup_insert_after, int delivery_to_insert, int delivery_insert_after, int vehicle, int64_t bucket)
int pickup_insert_after() const
void set_pickup_insert_after(int pickup_insert_after)
int delivery_insert_after() const
void SetHeapIndex(int h)
int delivery_to_insert() const
void set_value(int64_t value)
int pickup_to_insert() const
bool BuildSolutionInternal() override
Virtual method to redefine how to build a solution.
GlobalCheapestInsertionFilteredHeuristic(RoutingModel *model, std::function< int64_t(int64_t, int64_t, int64_t)> evaluator, std::function< int64_t(int64_t)> penalty_evaluator, LocalSearchFilterManager *filter_manager, GlobalCheapestInsertionParameters parameters)
Takes ownership of evaluators.
Utility class to encapsulate an IntVarIterator and use it in a range-based loop.
virtual int64_t Min() const =0
virtual int64_t Max() const =0
Decision * Next(Solver *solver) override
This is the main method of the decision builder class.
int64_t number_of_decisions() const
Returns statistics from its underlying heuristic.
IntVarFilteredDecisionBuilder(std::unique_ptr< IntVarFilteredHeuristic > heuristic)
Generic filter-based heuristic applied to IntVars.
void SetValue(int64_t index, int64_t value)
Modifies the current solution by setting the variable of index 'index' to value 'value'.
virtual bool BuildSolutionInternal()=0
Virtual method to redefine how to build a solution.
int64_t SecondaryVarIndex(int64_t index) const
Returns the index of a secondary var.
int Size() const
Returns the number of variables the decision builder is trying to instantiate.
bool Contains(int64_t index) const
Returns true if the variable of index 'index' is in the current solution.
void ResetSolution()
Resets the data members for a new solution.
void SynchronizeFilters()
Synchronizes filters with an assignment (the current solution).
bool HasSecondaryVars() const
Returns true if there are secondary variables.
virtual bool InitializeSolution()
Virtual method to initialize the solution.
IntVar * Var(int64_t index) const
Returns the variable of index 'index'.
int64_t Value(int64_t index) const
Returns the value of the variable of index 'index' in the last committed solution.
bool IsSecondaryVar(int64_t index) const
Returns true if 'index' is a secondary variable index.
std::optional< int64_t > Evaluate(bool commit)
Evaluates the modifications to the current solution.
Assignment *const BuildSolution()
Builds a solution.
IntVarFilteredHeuristic(Solver *solver, const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, LocalSearchFilterManager *filter_manager)
The class IntVar is a subset of IntExpr.
virtual bool Contains(int64_t v) const =0
This method returns whether the value 'v' is in the domain of the variable.
virtual uint64_t Size() const =0
This method returns the number of values in the domain of the variable.
virtual IntVarIterator * MakeDomainIterator(bool reversible) const =0
Creates a domain iterator.
bool BuildSolutionInternal() override
Virtual method to redefine how to build a solution.
Filter manager: when a move is made, filters are executed to decide whether the solution is feasible ...
bool Accept(LocalSearchMonitor *const monitor, const Assignment *delta, const Assignment *deltadelta, int64_t objective_min, int64_t objective_max)
Returns true iff all filters return true, and the sum of their accepted objectives is between objecti...
void Synchronize(const Assignment *assignment, const Assignment *delta)
Synchronizes all filters to assignment.
The base class for all local search operators.
virtual bool MakeNextNeighbor(Assignment *delta, Assignment *deltadelta)=0
virtual void Start(const Assignment *assignment)=0
virtual int64_t RangeMinArgument(int64_t from, int64_t to) const =0
Dimensions represent quantities accumulated at nodes along the routes.
Definition: routing.h:2590
IntVar * CumulVar(int64_t index) const
Get the cumul, transit and slack variables for the given node (given as int64_t var index).
Definition: routing.h:2609
int64_t ShortestTransitionSlack(int64_t node) const
It makes sense to use the function only for self-dependent dimension.
const RoutingDimension * base_dimension() const
Returns the parent in the dependency tree if any or nullptr otherwise.
Definition: routing.h:2843
Filter-based heuristic dedicated to routing.
int GetStartChainEnd(int vehicle) const
Returns the end of the start chain of vehicle,.
virtual void SetVehicleIndex(int64_t node, int vehicle)
int GetEndChainStart(int vehicle) const
Returns the start of the end chain of vehicle,.
RoutingFilteredHeuristic(RoutingModel *model, LocalSearchFilterManager *filter_manager, bool omit_secondary_vars=true)
void MakeDisjunctionNodesUnperformed(int64_t node)
Make nodes in the same disjunction as 'node' unperformed.
bool StopSearch() override
Returns true if the search must be stopped.
void MakeUnassignedNodesUnperformed()
Make all unassigned nodes unperformed.
void MakePartiallyPerformedPairsUnperformed()
Make all partially performed pickup and delivery pairs unperformed.
const Assignment * BuildSolutionFromRoutes(const std::function< int64_t(int64_t)> &next_accessor)
Builds a solution starting from the routes formed by the next accessor.
void ForEachNodeInDisjunctionWithMaxCardinalityFromIndex(int64_t index, int64_t max_cardinality, F f) const
Calls f for each variable index of indices in the same disjunctions as the node corresponding to the ...
Definition: routing.h:770
RoutingIndexPair IndexPair
Definition: routing.h:246
const IndexPairs & GetPickupAndDeliveryPairs() const
Returns pickup and delivery pairs currently in the model.
Definition: routing.h:883
int64_t GetFixedCostOfVehicle(int vehicle) const
Returns the route fixed cost taken into account if the route of the vehicle is not empty,...
Definition: routing.cc:1541
const std::vector< std::pair< int, int > > & GetDeliveryIndexPairs(int64_t node_index) const
Same as above for deliveries.
Definition: routing.cc:2072
static std::unique_ptr< LocalSearchOperator > MakeGreedyDescentLSOperator(std::vector< IntVar * > variables)
Perhaps move it to constraint_solver.h.
IntVar * VehicleVar(int64_t index) const
Returns the vehicle variable of the node corresponding to index.
Definition: routing.h:1382
int64_t Size() const
Returns the number of next variables in the model.
Definition: routing.h:1531
RoutingIndexPairs IndexPairs
Definition: routing.h:247
int64_t Start(int vehicle) const
Model inspection.
Definition: routing.h:1333
int vehicles() const
Returns the number of vehicle routes in the model.
Definition: routing.h:1529
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.
IntVar * CostVar() const
Returns the global cost variable which is being minimized.
Definition: routing.h:1392
int64_t GetArcCostForClass(int64_t from_index, int64_t to_index, int64_t cost_class_index) const
Returns the cost of the segment between two nodes for a given cost class.
Definition: routing.cc:3971
const std::vector< std::pair< int, int > > & GetPickupIndexPairs(int64_t node_index) const
Returns pairs for which the node is a pickup; the first element of each pair is the index in the pick...
Definition: routing.cc:2066
DecisionBuilder * MakeSelfDependentDimensionFinalizer(const RoutingDimension *dimension)
SWIG
bool IsEnd(int64_t index) const
Returns true if 'index' represents the last node of a route.
Definition: routing.h:1339
int GetCostClassesCount() const
Returns the number of different cost classes in the model.
Definition: routing.h:1437
CostClassIndex GetCostClassIndexOfVehicle(int64_t vehicle) const
Get the cost class index of the given vehicle.
Definition: routing.h:1420
int64_t End(int vehicle) const
Returns the variable index of the ending node of a vehicle route.
Definition: routing.h:1335
const std::vector< Saving > & GetSortedSavingsForVehicleType(int type)
SavingsContainer(const SavingsFilteredHeuristic *savings_db, int vehicle_types)
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)
Filter-based decision builder which builds a solution by using Clarke & Wright's Savings heuristic.
SavingsFilteredHeuristic(RoutingModel *model, SavingsParameters parameters, LocalSearchFilterManager *filter_manager)
int64_t GetVehicleTypeFromSaving(const Saving &saving) const
Returns the cost class from a saving.
std::unique_ptr< VehicleTypeCurator > vehicle_type_curator_
bool BuildSolutionInternal() override
Virtual method to redefine how to build a solution.
int64_t GetAfterNodeFromSaving(const Saving &saving) const
Returns the "after node" from a saving.
int64_t GetSavingValue(const Saving &saving) const
Returns the saving value from a saving.
std::unique_ptr< SavingsContainer< Saving > > savings_container_
virtual double ExtraSavingsMemoryMultiplicativeFactor() const =0
int64_t GetBeforeNodeFromSaving(const Saving &saving) const
Returns the "before node" from a saving.
int StartNewRouteWithBestVehicleOfType(int type, int64_t before_node, int64_t after_node)
Finds the best available vehicle of type "type" to start a new route to serve the arc before_node-->a...
LocalSearchMonitor * GetLocalSearchMonitor() const
Returns the local search monitor.
void Fail()
Abandon the current branch in the search tree. A backtrack will follow.
std::function< bool(int64_t, int64_t, int64_t)> VariableValueComparator
SweepArranger(const std::vector< std::pair< int64_t, int64_t > > &points)
void ArrangeIndices(std::vector< int64_t > *indices)
void Update(const std::function< bool(int)> &remove_vehicle)
Goes through all the currently stored vehicles and removes vehicles for which remove_vehicle() return...
bool HasCompatibleVehicleOfType(int type, const std::function< bool(int)> &vehicle_is_compatible) const
Searches a compatible vehicle of the given type; returns false if none was found.
std::pair< int, int > GetCompatibleVehicleOfType(int type, std::function< bool(int)> vehicle_is_compatible, std::function< bool(int)> stop_and_return_vehicle)
Searches for the best compatible vehicle of the given type, i.e.
void Reset(const std::function< bool(int)> &store_vehicle)
Resets the vehicles stored, storing only vehicles from the vehicle_type_container_ for which store_ve...
Block * next
SatParameters parameters
IntVar * var
Definition: expr_array.cc:1874
const std::vector< IntVar * > cumuls_
GRBmodel * model
#define DISALLOW_COPY_AND_ASSIGN(TypeName)
Definition: macros.h:29
void InsertOrDie(Collection *const collection, const typename Collection::value_type &value)
Definition: map_util.h:154
void STLClearObject(T *obj)
Definition: stl_util.h:123
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:262
std::function< int64_t(const Model &)> Value(IntegerVariable v)
Definition: integer.h:1683
Collection of objects used to extend the Constraint Solver library.
int64_t CapAdd(int64_t x, int64_t y)
DecisionBuilder * MakeAllUnperformed(RoutingModel *model)
int64_t CapSub(int64_t x, int64_t y)
FirstSolutionStrategy::Value AutomaticFirstSolutionStrategy(bool has_pickup_deliveries, bool has_node_precedences, bool has_single_vehicle_node)
Returns the best value for the automatic first solution strategy, based on the given model parameters...
DecisionBuilder * MakeSweepDecisionBuilder(RoutingModel *model, bool check_assignment)
static const int kUnassigned
Definition: routing.cc:878
STL namespace.
int64_t delta
Definition: resource.cc:1694
int64_t cost
int vehicle_class
int head
std::pair< int, int > link
int nodes
double distance
double angle
int64_t start_depot
int64_t index
int64_t value
ABSL_FLAG(bool, routing_shift_insertion_cost_by_penalty, true, "Shift insertion costs by the penalty of the inserted node(s).")
int64_t end_depot
int tail
std::function< int64_t(int64_t, int64_t)> evaluator_
Definition: search.cc:1368
std::optional< int64_t > end
int64_t start
double neighbors_ratio
If neighbors_ratio < 1 then for each node only this ratio of its neighbors leading to the smallest ar...
bool is_sequential
Whether the routes are constructed sequentially or in parallel.
bool use_neighbors_ratio_for_initialization
If true, only closest neighbors (see neighbors_ratio and min_neighbors) are considered as insertion p...
What follows is relevant for models with time/state dependent transits.
Definition: routing.h:263
RangeMinMaxIndexFunction * transit_plus_identity
f(x)
Definition: routing.h:265
Definition: routing.h:362
std::vector< std::set< VehicleClassEntry > > sorted_vehicle_classes_per_type
Definition: routing.h:381
std::vector< std::deque< int > > vehicles_per_vehicle_class
Definition: routing.h:382
double neighbors_ratio
If neighbors_ratio < 1 then for each node only this ratio of its neighbors leading to the smallest ar...
double arc_coefficient
arc_coefficient is a strictly positive parameter indicating the coefficient of the arc being consider...
double max_memory_usage_bytes
The number of neighbors considered for each node is also adapted so that the stored Savings don't use...
bool add_reverse_arcs
If add_reverse_arcs is true, the neighborhood relationships are considered symmetrically.