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