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