OR-Tools  9.6
routing_filters.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 local search filters for routing models.
15 
17 
18 #include <stddef.h>
19 
20 #include <algorithm>
21 #include <cstdint>
22 #include <deque>
23 #include <functional>
24 #include <iterator>
25 #include <limits>
26 #include <map>
27 #include <memory>
28 #include <numeric>
29 #include <set>
30 #include <string>
31 #include <utility>
32 #include <vector>
33 
34 #include "absl/container/btree_set.h"
35 #include "absl/container/flat_hash_map.h"
36 #include "absl/container/flat_hash_set.h"
37 #include "absl/flags/flag.h"
38 #include "absl/strings/string_view.h"
40 #include "ortools/base/logging.h"
41 #include "ortools/base/small_map.h"
47 #include "ortools/constraint_solver/routing_parameters.pb.h"
48 #include "ortools/util/bitset.h"
52 
53 ABSL_FLAG(bool, routing_strong_debug_checks, false,
54  "Run stronger checks in debug; these stronger tests might change "
55  "the complexity of the code in particular.");
56 
57 namespace operations_research {
58 
59 namespace {
60 
61 // Max active vehicles filter.
62 
63 class MaxActiveVehiclesFilter : public IntVarLocalSearchFilter {
64  public:
65  explicit MaxActiveVehiclesFilter(const RoutingModel& routing_model)
66  : IntVarLocalSearchFilter(routing_model.Nexts()),
67  routing_model_(routing_model),
68  is_active_(routing_model.vehicles(), false),
69  active_vehicles_(0) {}
70  bool Accept(const Assignment* delta, const Assignment* /*deltadelta*/,
71  int64_t /*objective_min*/, int64_t /*objective_max*/) override {
72  const int64_t kUnassigned = -1;
73  const Assignment::IntContainer& container = delta->IntVarContainer();
74  const int delta_size = container.Size();
75  int current_active_vehicles = active_vehicles_;
76  for (int i = 0; i < delta_size; ++i) {
77  const IntVarElement& new_element = container.Element(i);
78  IntVar* const var = new_element.Var();
79  int64_t index = kUnassigned;
80  if (FindIndex(var, &index) && routing_model_.IsStart(index)) {
81  if (new_element.Min() != new_element.Max()) {
82  // LNS detected.
83  return true;
84  }
85  const int vehicle = routing_model_.VehicleIndex(index);
86  const bool is_active =
87  (new_element.Min() != routing_model_.End(vehicle));
88  if (is_active && !is_active_[vehicle]) {
89  ++current_active_vehicles;
90  } else if (!is_active && is_active_[vehicle]) {
91  --current_active_vehicles;
92  }
93  }
94  }
95  return current_active_vehicles <=
96  routing_model_.GetMaximumNumberOfActiveVehicles();
97  }
98 
99  private:
100  void OnSynchronize(const Assignment* /*delta*/) override {
101  active_vehicles_ = 0;
102  for (int i = 0; i < routing_model_.vehicles(); ++i) {
103  const int index = routing_model_.Start(i);
104  if (IsVarSynced(index) && Value(index) != routing_model_.End(i)) {
105  is_active_[i] = true;
106  ++active_vehicles_;
107  } else {
108  is_active_[i] = false;
109  }
110  }
111  }
112 
113  const RoutingModel& routing_model_;
114  std::vector<bool> is_active_;
115  int active_vehicles_;
116 };
117 } // namespace
118 
120  const RoutingModel& routing_model) {
121  return routing_model.solver()->RevAlloc(
122  new MaxActiveVehiclesFilter(routing_model));
123 }
124 
125 namespace {
126 
127 // Node disjunction filter class.
128 class NodeDisjunctionFilter : public IntVarLocalSearchFilter {
129  public:
130  explicit NodeDisjunctionFilter(const RoutingModel& routing_model,
131  bool filter_cost)
132  : IntVarLocalSearchFilter(routing_model.Nexts()),
133  routing_model_(routing_model),
134  active_per_disjunction_(routing_model.GetNumberOfDisjunctions(), 0),
135  inactive_per_disjunction_(routing_model.GetNumberOfDisjunctions(), 0),
136  synchronized_objective_value_(std::numeric_limits<int64_t>::min()),
137  accepted_objective_value_(std::numeric_limits<int64_t>::min()),
138  filter_cost_(filter_cost),
139  has_mandatory_disjunctions_(routing_model.HasMandatoryDisjunctions()) {}
140 
141  bool Accept(const Assignment* delta, const Assignment* /*deltadelta*/,
142  int64_t /*objective_min*/, int64_t objective_max) override {
143  const int64_t kUnassigned = -1;
144  const Assignment::IntContainer& container = delta->IntVarContainer();
145  const int delta_size = container.Size();
147  disjunction_active_deltas;
149  disjunction_inactive_deltas;
150  bool lns_detected = false;
151  // Update active/inactive count per disjunction for each element of delta.
152  for (int i = 0; i < delta_size; ++i) {
153  const IntVarElement& new_element = container.Element(i);
154  IntVar* const var = new_element.Var();
155  int64_t index = kUnassigned;
156  if (FindIndex(var, &index)) {
157  const bool is_inactive =
158  (new_element.Min() <= index && new_element.Max() >= index);
159  if (new_element.Min() != new_element.Max()) {
160  lns_detected = true;
161  }
162  for (const RoutingModel::DisjunctionIndex disjunction_index :
163  routing_model_.GetDisjunctionIndices(index)) {
164  const bool is_var_synced = IsVarSynced(index);
165  if (!is_var_synced || (Value(index) == index) != is_inactive) {
166  ++gtl::LookupOrInsert(is_inactive ? &disjunction_inactive_deltas
167  : &disjunction_active_deltas,
168  disjunction_index, 0);
169  if (is_var_synced) {
170  --gtl::LookupOrInsert(is_inactive ? &disjunction_active_deltas
171  : &disjunction_inactive_deltas,
172  disjunction_index, 0);
173  }
174  }
175  }
176  }
177  }
178  // Check if any disjunction has too many active nodes.
179  for (const auto [disjunction_index, active_nodes] :
180  disjunction_active_deltas) {
181  // Too many active nodes.
182  if (active_per_disjunction_[disjunction_index] + active_nodes >
183  routing_model_.GetDisjunctionMaxCardinality(disjunction_index)) {
184  return false;
185  }
186  }
187  if (lns_detected || (!filter_cost_ && !has_mandatory_disjunctions_)) {
188  accepted_objective_value_ = 0;
189  return true;
190  }
191  // Update penalty costs for disjunctions.
192  accepted_objective_value_ = synchronized_objective_value_;
193  for (const auto [disjunction_index, inactive_nodes] :
194  disjunction_inactive_deltas) {
195  const int64_t penalty =
196  routing_model_.GetDisjunctionPenalty(disjunction_index);
197  if (penalty == 0) continue;
198  const int current_inactive_nodes =
199  inactive_per_disjunction_[disjunction_index];
200  const int max_inactive_cardinality =
201  routing_model_.GetDisjunctionNodeIndices(disjunction_index).size() -
202  routing_model_.GetDisjunctionMaxCardinality(disjunction_index);
203  // Too many inactive nodes.
204  if (current_inactive_nodes + inactive_nodes > max_inactive_cardinality) {
205  if (penalty < 0) {
206  // Nodes are mandatory, i.e. exactly max_cardinality nodes must be
207  // performed, so the move is not acceptable.
208  return false;
209  } else if (current_inactive_nodes <= max_inactive_cardinality) {
210  // Add penalty if there were not too many inactive nodes before the
211  // move.
212  accepted_objective_value_ =
213  CapAdd(accepted_objective_value_, penalty);
214  }
215  } else if (current_inactive_nodes > max_inactive_cardinality) {
216  // Remove penalty if there were too many inactive nodes before the
217  // move and there are not too many after the move.
218  accepted_objective_value_ = CapSub(accepted_objective_value_, penalty);
219  }
220  }
221  // Only compare to max as a cost lower bound is computed.
222  return accepted_objective_value_ <= objective_max;
223  }
224  std::string DebugString() const override { return "NodeDisjunctionFilter"; }
225  int64_t GetSynchronizedObjectiveValue() const override {
226  return synchronized_objective_value_;
227  }
228  int64_t GetAcceptedObjectiveValue() const override {
229  return accepted_objective_value_;
230  }
231 
232  private:
233  void OnSynchronize(const Assignment* /*delta*/) override {
234  synchronized_objective_value_ = 0;
236  i < active_per_disjunction_.size(); ++i) {
237  active_per_disjunction_[i] = 0;
238  inactive_per_disjunction_[i] = 0;
239  const std::vector<int64_t>& disjunction_indices =
240  routing_model_.GetDisjunctionNodeIndices(i);
241  for (const int64_t index : disjunction_indices) {
242  if (IsVarSynced(index)) {
243  if (Value(index) != index) {
244  ++active_per_disjunction_[i];
245  } else {
246  ++inactive_per_disjunction_[i];
247  }
248  }
249  }
250  if (!filter_cost_) continue;
251  const int64_t penalty = routing_model_.GetDisjunctionPenalty(i);
252  const int max_cardinality =
253  routing_model_.GetDisjunctionMaxCardinality(i);
254  if (inactive_per_disjunction_[i] >
255  disjunction_indices.size() - max_cardinality &&
256  penalty > 0) {
257  synchronized_objective_value_ =
258  CapAdd(synchronized_objective_value_, penalty);
259  }
260  }
261  }
262 
263  const RoutingModel& routing_model_;
264 
266  active_per_disjunction_;
268  inactive_per_disjunction_;
269  int64_t synchronized_objective_value_;
270  int64_t accepted_objective_value_;
271  const bool filter_cost_;
272  const bool has_mandatory_disjunctions_;
273 };
274 } // namespace
275 
277  const RoutingModel& routing_model, bool filter_cost) {
278  return routing_model.solver()->RevAlloc(
279  new NodeDisjunctionFilter(routing_model, filter_cost));
280 }
281 
282 const int64_t BasePathFilter::kUnassigned = -1;
283 
284 BasePathFilter::BasePathFilter(const std::vector<IntVar*>& nexts,
285  int next_domain_size)
286  : IntVarLocalSearchFilter(nexts),
287  node_path_starts_(next_domain_size, kUnassigned),
288  paths_(nexts.size(), -1),
289  new_synchronized_unperformed_nodes_(nexts.size()),
290  new_nexts_(nexts.size(), kUnassigned),
291  touched_paths_(nexts.size()),
292  touched_path_chain_start_ends_(nexts.size(), {kUnassigned, kUnassigned}),
293  ranks_(next_domain_size, -1),
294  status_(BasePathFilter::UNKNOWN),
295  lns_detected_(false) {}
296 
298  const Assignment* /*deltadelta*/,
299  int64_t objective_min, int64_t objective_max) {
300  if (IsDisabled()) return true;
301  lns_detected_ = false;
302  for (const int touched : delta_touched_) {
303  new_nexts_[touched] = kUnassigned;
304  }
305  delta_touched_.clear();
306  const Assignment::IntContainer& container = delta->IntVarContainer();
307  const int delta_size = container.Size();
308  delta_touched_.reserve(delta_size);
309  // Determining touched paths and their touched chain start and ends (a node is
310  // touched if it corresponds to an element of delta or that an element of
311  // delta points to it).
312  // The start and end of a touched path subchain will have remained on the same
313  // path and will correspond to the min and max ranks of touched nodes in the
314  // current assignment.
315  for (int64_t touched_path : touched_paths_.PositionsSetAtLeastOnce()) {
316  touched_path_chain_start_ends_[touched_path] = {kUnassigned, kUnassigned};
317  }
318  touched_paths_.SparseClearAll();
319 
320  const auto update_touched_path_chain_start_end = [this](int64_t index) {
321  const int64_t start = node_path_starts_[index];
322  if (start == kUnassigned) return;
323  touched_paths_.Set(start);
324 
325  int64_t& chain_start = touched_path_chain_start_ends_[start].first;
326  if (chain_start == kUnassigned || ranks_[index] < ranks_[chain_start]) {
327  chain_start = index;
328  }
329 
330  int64_t& chain_end = touched_path_chain_start_ends_[start].second;
331  if (chain_end == kUnassigned || ranks_[index] > ranks_[chain_end]) {
332  chain_end = index;
333  }
334  };
335 
336  for (int i = 0; i < delta_size; ++i) {
337  const IntVarElement& new_element = container.Element(i);
338  IntVar* const var = new_element.Var();
339  int64_t index = kUnassigned;
340  if (FindIndex(var, &index)) {
341  if (!new_element.Bound()) {
342  // LNS detected
343  lns_detected_ = true;
344  return true;
345  }
346  new_nexts_[index] = new_element.Value();
347  delta_touched_.push_back(index);
348  update_touched_path_chain_start_end(index);
349  update_touched_path_chain_start_end(new_nexts_[index]);
350  }
351  }
352  // Checking feasibility of touched paths.
353  if (!InitializeAcceptPath()) return false;
354  for (const int64_t touched_start : touched_paths_.PositionsSetAtLeastOnce()) {
355  const std::pair<int64_t, int64_t> start_end =
356  touched_path_chain_start_ends_[touched_start];
357  if (!AcceptPath(touched_start, start_end.first, start_end.second)) {
358  return false;
359  }
360  }
361  // NOTE: FinalizeAcceptPath() is only called if InitializeAcceptPath() is true
362  // and all paths are accepted.
363  return FinalizeAcceptPath(objective_min, objective_max);
364 }
365 
366 void BasePathFilter::ComputePathStarts(std::vector<int64_t>* path_starts,
367  std::vector<int>* index_to_path) {
368  path_starts->clear();
369  const int nexts_size = Size();
370  index_to_path->assign(nexts_size, kUnassigned);
371  Bitset64<> has_prevs(nexts_size);
372  for (int i = 0; i < nexts_size; ++i) {
373  if (!IsVarSynced(i)) {
374  has_prevs.Set(i);
375  } else {
376  const int next = Value(i);
377  if (next < nexts_size) {
378  has_prevs.Set(next);
379  }
380  }
381  }
382  for (int i = 0; i < nexts_size; ++i) {
383  if (!has_prevs[i]) {
384  (*index_to_path)[i] = path_starts->size();
385  path_starts->push_back(i);
386  }
387  }
388 }
389 
390 bool BasePathFilter::HavePathsChanged() {
391  std::vector<int64_t> path_starts;
392  std::vector<int> index_to_path(Size(), kUnassigned);
393  ComputePathStarts(&path_starts, &index_to_path);
394  if (path_starts.size() != starts_.size()) {
395  return true;
396  }
397  for (int i = 0; i < path_starts.size(); ++i) {
398  if (path_starts[i] != starts_[i]) {
399  return true;
400  }
401  }
402  for (int i = 0; i < Size(); ++i) {
403  if (index_to_path[i] != paths_[i]) {
404  return true;
405  }
406  }
407  return false;
408 }
409 
410 void BasePathFilter::SynchronizeFullAssignment() {
411  // Subclasses of BasePathFilter might not propagate injected objective values
412  // so making sure it is done here (can be done again by the subclass if
413  // needed).
414  ComputePathStarts(&starts_, &paths_);
415  for (int64_t index = 0; index < Size(); index++) {
416  if (IsVarSynced(index) && Value(index) == index &&
417  node_path_starts_[index] != kUnassigned) {
418  // index was performed before and is now unperformed.
419  new_synchronized_unperformed_nodes_.Set(index);
420  }
421  }
422  // Marking unactive nodes (which are not on a path).
423  node_path_starts_.assign(node_path_starts_.size(), kUnassigned);
424  // Marking nodes on a path and storing next values.
425  const int nexts_size = Size();
426  for (const int64_t start : starts_) {
427  int node = start;
428  node_path_starts_[node] = start;
429  DCHECK(IsVarSynced(node));
430  int next = Value(node);
431  while (next < nexts_size) {
432  node = next;
433  node_path_starts_[node] = start;
434  DCHECK(IsVarSynced(node));
435  next = Value(node);
436  }
437  node_path_starts_[next] = start;
438  }
439  OnBeforeSynchronizePaths();
440  UpdateAllRanks();
441  OnAfterSynchronizePaths();
442 }
443 
445  if (status_ == BasePathFilter::UNKNOWN) {
446  status_ =
447  DisableFiltering() ? BasePathFilter::DISABLED : BasePathFilter::ENABLED;
448  }
449  if (IsDisabled()) return;
450  new_synchronized_unperformed_nodes_.ClearAll();
451  if (delta == nullptr || delta->Empty() || starts_.empty()) {
452  SynchronizeFullAssignment();
453  return;
454  }
455  // Subclasses of BasePathFilter might not propagate injected objective values
456  // so making sure it is done here (can be done again by the subclass if
457  // needed).
458  // This code supposes that path starts didn't change.
459  DCHECK(!absl::GetFlag(FLAGS_routing_strong_debug_checks) ||
460  !HavePathsChanged());
461  const Assignment::IntContainer& container = delta->IntVarContainer();
462  touched_paths_.SparseClearAll();
463  for (int i = 0; i < container.Size(); ++i) {
464  const IntVarElement& new_element = container.Element(i);
465  int64_t index = kUnassigned;
466  if (FindIndex(new_element.Var(), &index)) {
467  const int64_t start = node_path_starts_[index];
468  if (start != kUnassigned) {
469  touched_paths_.Set(start);
470  if (Value(index) == index) {
471  // New unperformed node (its previous start isn't unassigned).
472  DCHECK_LT(index, new_nexts_.size());
473  new_synchronized_unperformed_nodes_.Set(index);
474  node_path_starts_[index] = kUnassigned;
475  }
476  }
477  }
478  }
479  OnBeforeSynchronizePaths();
480  for (const int64_t touched_start : touched_paths_.PositionsSetAtLeastOnce()) {
481  int64_t node = touched_start;
482  while (node < Size()) {
483  node_path_starts_[node] = touched_start;
484  node = Value(node);
485  }
486  node_path_starts_[node] = touched_start;
487  UpdatePathRanksFromStart(touched_start);
488  OnSynchronizePathFromStart(touched_start);
489  }
490  OnAfterSynchronizePaths();
491 }
492 
493 void BasePathFilter::UpdateAllRanks() {
494  for (int i = 0; i < ranks_.size(); ++i) {
495  ranks_[i] = kUnassigned;
496  }
497  for (int r = 0; r < NumPaths(); ++r) {
498  UpdatePathRanksFromStart(Start(r));
499  OnSynchronizePathFromStart(Start(r));
500  }
501 }
502 
503 void BasePathFilter::UpdatePathRanksFromStart(int start) {
504  int rank = 0;
505  int64_t node = start;
506  while (node < Size()) {
507  ranks_[node] = rank;
508  rank++;
509  node = Value(node);
510  }
511  ranks_[node] = rank;
512 }
513 
514 namespace {
515 
516 class VehicleAmortizedCostFilter : public BasePathFilter {
517  public:
518  explicit VehicleAmortizedCostFilter(const RoutingModel& routing_model);
519  ~VehicleAmortizedCostFilter() override {}
520  std::string DebugString() const override {
521  return "VehicleAmortizedCostFilter";
522  }
523  int64_t GetSynchronizedObjectiveValue() const override {
524  return current_vehicle_cost_;
525  }
526  int64_t GetAcceptedObjectiveValue() const override {
527  return lns_detected() ? 0 : delta_vehicle_cost_;
528  }
529 
530  private:
531  void OnSynchronizePathFromStart(int64_t start) override;
532  void OnAfterSynchronizePaths() override;
533  bool InitializeAcceptPath() override;
534  bool AcceptPath(int64_t path_start, int64_t chain_start,
535  int64_t chain_end) override;
536  bool FinalizeAcceptPath(int64_t objective_min,
537  int64_t objective_max) override;
538 
539  int64_t current_vehicle_cost_;
540  int64_t delta_vehicle_cost_;
541  std::vector<int> current_route_lengths_;
542  std::vector<int64_t> start_to_end_;
543  std::vector<int> start_to_vehicle_;
544  std::vector<int64_t> vehicle_to_start_;
545  const std::vector<int64_t>& linear_cost_factor_of_vehicle_;
546  const std::vector<int64_t>& quadratic_cost_factor_of_vehicle_;
547 };
548 
549 VehicleAmortizedCostFilter::VehicleAmortizedCostFilter(
550  const RoutingModel& routing_model)
551  : BasePathFilter(routing_model.Nexts(),
552  routing_model.Size() + routing_model.vehicles()),
553  current_vehicle_cost_(0),
554  delta_vehicle_cost_(0),
555  current_route_lengths_(Size(), -1),
556  linear_cost_factor_of_vehicle_(
557  routing_model.GetAmortizedLinearCostFactorOfVehicles()),
558  quadratic_cost_factor_of_vehicle_(
559  routing_model.GetAmortizedQuadraticCostFactorOfVehicles()) {
560  start_to_end_.resize(Size(), -1);
561  start_to_vehicle_.resize(Size(), -1);
562  vehicle_to_start_.resize(routing_model.vehicles());
563  for (int v = 0; v < routing_model.vehicles(); v++) {
564  const int64_t start = routing_model.Start(v);
565  start_to_vehicle_[start] = v;
566  start_to_end_[start] = routing_model.End(v);
567  vehicle_to_start_[v] = start;
568  }
569 }
570 
571 void VehicleAmortizedCostFilter::OnSynchronizePathFromStart(int64_t start) {
572  const int64_t end = start_to_end_[start];
573  CHECK_GE(end, 0);
574  const int route_length = Rank(end) - 1;
575  CHECK_GE(route_length, 0);
576  current_route_lengths_[start] = route_length;
577 }
578 
579 void VehicleAmortizedCostFilter::OnAfterSynchronizePaths() {
580  current_vehicle_cost_ = 0;
581  for (int vehicle = 0; vehicle < vehicle_to_start_.size(); vehicle++) {
582  const int64_t start = vehicle_to_start_[vehicle];
583  DCHECK_EQ(vehicle, start_to_vehicle_[start]);
584 
585  const int route_length = current_route_lengths_[start];
586  DCHECK_GE(route_length, 0);
587 
588  if (route_length == 0) {
589  // The path is empty.
590  continue;
591  }
592 
593  const int64_t linear_cost_factor = linear_cost_factor_of_vehicle_[vehicle];
594  const int64_t route_length_cost =
595  CapProd(quadratic_cost_factor_of_vehicle_[vehicle],
596  route_length * route_length);
597 
598  current_vehicle_cost_ = CapAdd(
599  current_vehicle_cost_, CapSub(linear_cost_factor, route_length_cost));
600  }
601 }
602 
603 bool VehicleAmortizedCostFilter::InitializeAcceptPath() {
604  delta_vehicle_cost_ = current_vehicle_cost_;
605  return true;
606 }
607 
608 bool VehicleAmortizedCostFilter::AcceptPath(int64_t path_start,
609  int64_t chain_start,
610  int64_t chain_end) {
611  // Number of nodes previously between chain_start and chain_end
612  const int previous_chain_nodes = Rank(chain_end) - 1 - Rank(chain_start);
613  CHECK_GE(previous_chain_nodes, 0);
614  int new_chain_nodes = 0;
615  int64_t node = GetNext(chain_start);
616  while (node != chain_end) {
617  new_chain_nodes++;
618  node = GetNext(node);
619  }
620 
621  const int previous_route_length = current_route_lengths_[path_start];
622  CHECK_GE(previous_route_length, 0);
623  const int new_route_length =
624  previous_route_length - previous_chain_nodes + new_chain_nodes;
625 
626  const int vehicle = start_to_vehicle_[path_start];
627  CHECK_GE(vehicle, 0);
628  DCHECK_EQ(path_start, vehicle_to_start_[vehicle]);
629 
630  // Update the cost related to used vehicles.
631  // TODO(user): Handle possible overflows.
632  if (previous_route_length == 0) {
633  // The route was empty before, it is no longer the case (changed path).
634  CHECK_GT(new_route_length, 0);
635  delta_vehicle_cost_ =
636  CapAdd(delta_vehicle_cost_, linear_cost_factor_of_vehicle_[vehicle]);
637  } else if (new_route_length == 0) {
638  // The route is now empty.
639  delta_vehicle_cost_ =
640  CapSub(delta_vehicle_cost_, linear_cost_factor_of_vehicle_[vehicle]);
641  }
642 
643  // Update the cost related to the sum of the squares of the route lengths.
644  const int64_t quadratic_cost_factor =
645  quadratic_cost_factor_of_vehicle_[vehicle];
646  delta_vehicle_cost_ =
647  CapAdd(delta_vehicle_cost_,
648  CapProd(quadratic_cost_factor,
649  previous_route_length * previous_route_length));
650  delta_vehicle_cost_ = CapSub(
651  delta_vehicle_cost_,
652  CapProd(quadratic_cost_factor, new_route_length * new_route_length));
653 
654  return true;
655 }
656 
657 bool VehicleAmortizedCostFilter::FinalizeAcceptPath(int64_t /*objective_min*/,
658  int64_t objective_max) {
659  return delta_vehicle_cost_ <= objective_max;
660 }
661 
662 } // namespace
663 
665  const RoutingModel& routing_model) {
666  return routing_model.solver()->RevAlloc(
667  new VehicleAmortizedCostFilter(routing_model));
668 }
669 
670 namespace {
671 
672 class TypeRegulationsFilter : public BasePathFilter {
673  public:
674  explicit TypeRegulationsFilter(const RoutingModel& model);
675  ~TypeRegulationsFilter() override {}
676  std::string DebugString() const override { return "TypeRegulationsFilter"; }
677 
678  private:
679  void OnSynchronizePathFromStart(int64_t start) override;
680  bool AcceptPath(int64_t path_start, int64_t chain_start,
681  int64_t chain_end) override;
682 
683  bool HardIncompatibilitiesRespected(int vehicle, int64_t chain_start,
684  int64_t chain_end);
685 
686  const RoutingModel& routing_model_;
687  std::vector<int> start_to_vehicle_;
688  // The following vector is used to keep track of the type counts for hard
689  // incompatibilities.
690  std::vector<std::vector<int>> hard_incompatibility_type_counts_per_vehicle_;
691  // Used to verify the temporal incompatibilities and requirements.
692  TypeIncompatibilityChecker temporal_incompatibility_checker_;
693  TypeRequirementChecker requirement_checker_;
694 };
695 
696 TypeRegulationsFilter::TypeRegulationsFilter(const RoutingModel& model)
697  : BasePathFilter(model.Nexts(), model.Size() + model.vehicles()),
698  routing_model_(model),
699  start_to_vehicle_(model.Size(), -1),
700  temporal_incompatibility_checker_(model,
701  /*check_hard_incompatibilities*/ false),
702  requirement_checker_(model) {
703  const int num_vehicles = model.vehicles();
704  const bool has_hard_type_incompatibilities =
705  model.HasHardTypeIncompatibilities();
706  if (has_hard_type_incompatibilities) {
707  hard_incompatibility_type_counts_per_vehicle_.resize(num_vehicles);
708  }
709  const int num_visit_types = model.GetNumberOfVisitTypes();
710  for (int vehicle = 0; vehicle < num_vehicles; vehicle++) {
711  const int64_t start = model.Start(vehicle);
712  start_to_vehicle_[start] = vehicle;
713  if (has_hard_type_incompatibilities) {
714  hard_incompatibility_type_counts_per_vehicle_[vehicle].resize(
715  num_visit_types, 0);
716  }
717  }
718 }
719 
720 void TypeRegulationsFilter::OnSynchronizePathFromStart(int64_t start) {
721  if (!routing_model_.HasHardTypeIncompatibilities()) return;
722 
723  const int vehicle = start_to_vehicle_[start];
724  CHECK_GE(vehicle, 0);
725  std::vector<int>& type_counts =
726  hard_incompatibility_type_counts_per_vehicle_[vehicle];
727  std::fill(type_counts.begin(), type_counts.end(), 0);
728  const int num_types = type_counts.size();
729 
730  int64_t node = start;
731  while (node < Size()) {
732  DCHECK(IsVarSynced(node));
733  const int type = routing_model_.GetVisitType(node);
734  if (type >= 0 && routing_model_.GetVisitTypePolicy(node) !=
735  RoutingModel::ADDED_TYPE_REMOVED_FROM_VEHICLE) {
736  CHECK_LT(type, num_types);
737  type_counts[type]++;
738  }
739  node = Value(node);
740  }
741 }
742 
743 bool TypeRegulationsFilter::HardIncompatibilitiesRespected(int vehicle,
744  int64_t chain_start,
745  int64_t chain_end) {
746  if (!routing_model_.HasHardTypeIncompatibilities()) return true;
747 
748  const std::vector<int>& previous_type_counts =
749  hard_incompatibility_type_counts_per_vehicle_[vehicle];
750 
751  absl::flat_hash_map</*type*/ int, /*new_count*/ int> new_type_counts;
752  absl::flat_hash_set<int> types_to_check;
753 
754  // Go through the new nodes on the path and increment their type counts.
755  int64_t node = GetNext(chain_start);
756  while (node != chain_end) {
757  const int type = routing_model_.GetVisitType(node);
758  if (type >= 0 && routing_model_.GetVisitTypePolicy(node) !=
759  RoutingModel::ADDED_TYPE_REMOVED_FROM_VEHICLE) {
760  DCHECK_LT(type, previous_type_counts.size());
761  int& type_count = gtl::LookupOrInsert(&new_type_counts, type,
762  previous_type_counts[type]);
763  if (type_count++ == 0) {
764  // New type on the route, mark to check its incompatibilities.
765  types_to_check.insert(type);
766  }
767  }
768  node = GetNext(node);
769  }
770 
771  // Update new_type_counts by decrementing the occurrence of the types of the
772  // nodes no longer on the route.
773  node = Value(chain_start);
774  while (node != chain_end) {
775  const int type = routing_model_.GetVisitType(node);
776  if (type >= 0 && routing_model_.GetVisitTypePolicy(node) !=
777  RoutingModel::ADDED_TYPE_REMOVED_FROM_VEHICLE) {
778  DCHECK_LT(type, previous_type_counts.size());
779  int& type_count = gtl::LookupOrInsert(&new_type_counts, type,
780  previous_type_counts[type]);
781  CHECK_GE(type_count, 1);
782  type_count--;
783  }
784  node = Value(node);
785  }
786 
787  // Check the incompatibilities for types in types_to_check.
788  for (int type : types_to_check) {
789  for (int incompatible_type :
790  routing_model_.GetHardTypeIncompatibilitiesOfType(type)) {
791  if (gtl::FindWithDefault(new_type_counts, incompatible_type,
792  previous_type_counts[incompatible_type]) > 0) {
793  return false;
794  }
795  }
796  }
797  return true;
798 }
799 
800 bool TypeRegulationsFilter::AcceptPath(int64_t path_start, int64_t chain_start,
801  int64_t chain_end) {
802  const int vehicle = start_to_vehicle_[path_start];
803  CHECK_GE(vehicle, 0);
804  const auto next_accessor = [this](int64_t node) { return GetNext(node); };
805  return HardIncompatibilitiesRespected(vehicle, chain_start, chain_end) &&
806  temporal_incompatibility_checker_.CheckVehicle(vehicle,
807  next_accessor) &&
808  requirement_checker_.CheckVehicle(vehicle, next_accessor);
809 }
810 
811 } // namespace
812 
814  const RoutingModel& routing_model) {
815  return routing_model.solver()->RevAlloc(
816  new TypeRegulationsFilter(routing_model));
817 }
818 
819 namespace {
820 
821 // ChainCumul filter. Version of dimension path filter which is O(delta) rather
822 // than O(length of touched paths). Currently only supports dimensions without
823 // costs (global and local span cost, soft bounds) and with unconstrained
824 // cumul variables except overall capacity and cumul variables of path ends.
825 
826 class ChainCumulFilter : public BasePathFilter {
827  public:
828  ChainCumulFilter(const RoutingModel& routing_model,
829  const RoutingDimension& dimension);
830  ~ChainCumulFilter() override {}
831  std::string DebugString() const override {
832  return "ChainCumulFilter(" + name_ + ")";
833  }
834 
835  private:
836  void OnSynchronizePathFromStart(int64_t start) override;
837  bool AcceptPath(int64_t path_start, int64_t chain_start,
838  int64_t chain_end) override;
839 
840  const std::vector<IntVar*> cumuls_;
841  std::vector<int64_t> start_to_vehicle_;
842  std::vector<int64_t> start_to_end_;
843  std::vector<const RoutingModel::TransitCallback2*> evaluators_;
844  const std::vector<int64_t> vehicle_capacities_;
845  std::vector<int64_t> current_path_cumul_mins_;
846  std::vector<int64_t> current_max_of_path_end_cumul_mins_;
847  std::vector<int64_t> old_nexts_;
848  std::vector<int> old_vehicles_;
849  std::vector<int64_t> current_transits_;
850  const std::string name_;
851 };
852 
853 ChainCumulFilter::ChainCumulFilter(const RoutingModel& routing_model,
854  const RoutingDimension& dimension)
855  : BasePathFilter(routing_model.Nexts(), dimension.cumuls().size()),
856  cumuls_(dimension.cumuls()),
857  evaluators_(routing_model.vehicles(), nullptr),
858  vehicle_capacities_(dimension.vehicle_capacities()),
859  current_path_cumul_mins_(dimension.cumuls().size(), 0),
860  current_max_of_path_end_cumul_mins_(dimension.cumuls().size(), 0),
861  old_nexts_(routing_model.Size(), kUnassigned),
862  old_vehicles_(routing_model.Size(), kUnassigned),
863  current_transits_(routing_model.Size(), 0),
864  name_(dimension.name()) {
865  start_to_vehicle_.resize(Size(), -1);
866  start_to_end_.resize(Size(), -1);
867  for (int i = 0; i < routing_model.vehicles(); ++i) {
868  start_to_vehicle_[routing_model.Start(i)] = i;
869  start_to_end_[routing_model.Start(i)] = routing_model.End(i);
870  evaluators_[i] = &dimension.transit_evaluator(i);
871  }
872 }
873 
874 // On synchronization, maintain "propagated" cumul mins and max level of cumul
875 // from each node to the end of the path; to be used by AcceptPath to
876 // incrementally check feasibility.
877 void ChainCumulFilter::OnSynchronizePathFromStart(int64_t start) {
878  const int vehicle = start_to_vehicle_[start];
879  std::vector<int64_t> path_nodes;
880  int64_t node = start;
881  int64_t cumul = cumuls_[node]->Min();
882  while (node < Size()) {
883  path_nodes.push_back(node);
884  current_path_cumul_mins_[node] = cumul;
885  const int64_t next = Value(node);
886  if (next != old_nexts_[node] || vehicle != old_vehicles_[node]) {
887  old_nexts_[node] = next;
888  old_vehicles_[node] = vehicle;
889  current_transits_[node] = (*evaluators_[vehicle])(node, next);
890  }
891  cumul = CapAdd(cumul, current_transits_[node]);
892  cumul = std::max(cumuls_[next]->Min(), cumul);
893  node = next;
894  }
895  path_nodes.push_back(node);
896  current_path_cumul_mins_[node] = cumul;
897  int64_t max_cumuls = cumul;
898  for (int i = path_nodes.size() - 1; i >= 0; --i) {
899  const int64_t node = path_nodes[i];
900  max_cumuls = std::max(max_cumuls, current_path_cumul_mins_[node]);
901  current_max_of_path_end_cumul_mins_[node] = max_cumuls;
902  }
903 }
904 
905 // The complexity of the method is O(size of chain (chain_start...chain_end).
906 bool ChainCumulFilter::AcceptPath(int64_t path_start, int64_t chain_start,
907  int64_t chain_end) {
908  const int vehicle = start_to_vehicle_[path_start];
909  const int64_t capacity = vehicle_capacities_[vehicle];
910  int64_t node = chain_start;
911  int64_t cumul = current_path_cumul_mins_[node];
912  while (node != chain_end) {
913  const int64_t next = GetNext(node);
914  if (IsVarSynced(node) && next == Value(node) &&
915  vehicle == old_vehicles_[node]) {
916  cumul = CapAdd(cumul, current_transits_[node]);
917  } else {
918  cumul = CapAdd(cumul, (*evaluators_[vehicle])(node, next));
919  }
920  cumul = std::max(cumuls_[next]->Min(), cumul);
921  if (cumul > capacity) return false;
922  node = next;
923  }
924  const int64_t end = start_to_end_[path_start];
925  const int64_t end_cumul_delta =
926  CapSub(current_path_cumul_mins_[end], current_path_cumul_mins_[node]);
927  const int64_t after_chain_cumul_delta =
928  CapSub(current_max_of_path_end_cumul_mins_[node],
929  current_path_cumul_mins_[node]);
930  return CapAdd(cumul, after_chain_cumul_delta) <= capacity &&
931  CapAdd(cumul, end_cumul_delta) <= cumuls_[end]->Max();
932 }
933 
934 // PathCumul filter.
935 
936 class PathCumulFilter : public BasePathFilter {
937  public:
938  PathCumulFilter(const RoutingModel& routing_model,
939  const RoutingDimension& dimension,
940  bool propagate_own_objective_value,
941  bool filter_objective_cost, bool can_use_lp);
942  ~PathCumulFilter() override {}
943  std::string DebugString() const override {
944  return "PathCumulFilter(" + name_ + ")";
945  }
946  int64_t GetSynchronizedObjectiveValue() const override {
947  return propagate_own_objective_value_ ? synchronized_objective_value_ : 0;
948  }
949  int64_t GetAcceptedObjectiveValue() const override {
950  return lns_detected() || !propagate_own_objective_value_
951  ? 0
952  : accepted_objective_value_;
953  }
954 
955  private:
956  // This structure stores the "best" path cumul value for a solution, the path
957  // supporting this value, and the corresponding path cumul values for all
958  // paths.
959  struct SupportedPathCumul {
960  SupportedPathCumul() : cumul_value(0), cumul_value_support(0) {}
961  int64_t cumul_value;
963  std::vector<int64_t> path_values;
964  };
965 
966  struct SoftBound {
967  SoftBound() : bound(-1), coefficient(0) {}
968  int64_t bound;
969  int64_t coefficient;
970  };
971 
972  // This class caches transit values between nodes of paths. Transit and path
973  // nodes are to be added in the order in which they appear on a path.
974  class PathTransits {
975  public:
976  void Clear() {
977  paths_.clear();
978  transits_.clear();
979  }
980  void ClearPath(int path) {
981  paths_[path].clear();
982  transits_[path].clear();
983  }
984  int AddPaths(int num_paths) {
985  const int first_path = paths_.size();
986  paths_.resize(first_path + num_paths);
987  transits_.resize(first_path + num_paths);
988  return first_path;
989  }
990  void ReserveTransits(int path, int number_of_route_arcs) {
991  transits_[path].reserve(number_of_route_arcs);
992  paths_[path].reserve(number_of_route_arcs + 1);
993  }
994  // Stores the transit between node and next on path. For a given non-empty
995  // path, node must correspond to next in the previous call to PushTransit.
996  void PushTransit(int path, int node, int next, int64_t transit) {
997  transits_[path].push_back(transit);
998  if (paths_[path].empty()) {
999  paths_[path].push_back(node);
1000  }
1001  DCHECK_EQ(paths_[path].back(), node);
1002  paths_[path].push_back(next);
1003  }
1004  int NumPaths() const { return paths_.size(); }
1005  int PathSize(int path) const { return paths_[path].size(); }
1006  int Node(int path, int position) const { return paths_[path][position]; }
1007  int64_t Transit(int path, int position) const {
1008  return transits_[path][position];
1009  }
1010 
1011  private:
1012  // paths_[r][i] is the ith node on path r.
1013  std::vector<std::vector<int64_t>> paths_;
1014  // transits_[r][i] is the transit value between nodes path_[i] and
1015  // path_[i+1] on path r.
1016  std::vector<std::vector<int64_t>> transits_;
1017  };
1018 
1019  bool InitializeAcceptPath() override {
1020  cumul_cost_delta_ = total_current_cumul_cost_value_;
1021  node_with_precedence_to_delta_min_max_cumuls_.clear();
1022  // Cleaning up for the new delta.
1023  delta_max_end_cumul_ = std::numeric_limits<int64_t>::min();
1024  delta_paths_.clear();
1025  delta_path_transits_.Clear();
1026  delta_nodes_with_precedences_and_changed_cumul_.ClearAll();
1027  return true;
1028  }
1029  bool AcceptPath(int64_t path_start, int64_t chain_start,
1030  int64_t chain_end) override;
1031  bool FinalizeAcceptPath(int64_t objective_min,
1032  int64_t objective_max) override;
1033  void OnBeforeSynchronizePaths() override;
1034 
1035  bool FilterSpanCost() const { return global_span_cost_coefficient_ != 0; }
1036 
1037  bool FilterSlackCost() const {
1038  return has_nonzero_vehicle_span_cost_coefficients_ ||
1039  has_vehicle_span_upper_bounds_;
1040  }
1041 
1042  bool FilterBreakCost(int vehicle) const {
1043  return dimension_.HasBreakConstraints() &&
1044  !dimension_.GetBreakIntervalsOfVehicle(vehicle).empty();
1045  }
1046 
1047  bool FilterCumulSoftBounds() const { return !cumul_soft_bounds_.empty(); }
1048 
1049  int64_t GetCumulSoftCost(int64_t node, int64_t cumul_value) const;
1050 
1051  bool FilterCumulPiecewiseLinearCosts() const {
1052  return !cumul_piecewise_linear_costs_.empty();
1053  }
1054 
1055  bool FilterWithDimensionCumulOptimizerForVehicle(int vehicle) const {
1056  if (!can_use_lp_ || FilterCumulPiecewiseLinearCosts()) {
1057  return false;
1058  }
1059 
1060  int num_linear_constraints = 0;
1061  if (dimension_.GetSpanCostCoefficientForVehicle(vehicle) > 0)
1062  ++num_linear_constraints;
1063  if (FilterSoftSpanCost(vehicle)) ++num_linear_constraints;
1064  if (FilterCumulSoftLowerBounds()) ++num_linear_constraints;
1065  if (FilterCumulSoftBounds()) ++num_linear_constraints;
1066  if (vehicle_span_upper_bounds_[vehicle] <
1068  ++num_linear_constraints;
1069  }
1070  const bool has_breaks = FilterBreakCost(vehicle);
1071  if (has_breaks) ++num_linear_constraints;
1072 
1073  // The DimensionCumulOptimizer is used to compute a more precise value of
1074  // the cost related to the cumul values (soft bounds and span costs).
1075  // It is also used to guarantee feasibility with complex mixes of
1076  // constraints and in particular in the presence of break requests along
1077  // other constraints. Therefore, without breaks, we only use the optimizer
1078  // when the costs are actually used to filter the solutions, i.e. when
1079  // filter_objective_cost_ is true.
1080  return num_linear_constraints >= 2 &&
1081  (has_breaks || filter_objective_cost_);
1082  }
1083 
1084  bool FilterDimensionForbiddenIntervals() const {
1085  for (const SortedDisjointIntervalList& intervals :
1086  dimension_.forbidden_intervals()) {
1087  // TODO(user): Change the following test to check intervals within
1088  // the domain of the corresponding variables.
1089  if (intervals.NumIntervals() > 0) {
1090  return true;
1091  }
1092  }
1093  return false;
1094  }
1095 
1096  int64_t GetCumulPiecewiseLinearCost(int64_t node, int64_t cumul_value) const;
1097 
1098  bool FilterCumulSoftLowerBounds() const {
1099  return !cumul_soft_lower_bounds_.empty();
1100  }
1101 
1102  bool FilterPrecedences() const { return !node_index_to_precedences_.empty(); }
1103 
1104  bool FilterSoftSpanCost() const {
1105  return dimension_.HasSoftSpanUpperBounds();
1106  }
1107  bool FilterSoftSpanCost(int vehicle) const {
1108  return dimension_.HasSoftSpanUpperBounds() &&
1109  dimension_.GetSoftSpanUpperBoundForVehicle(vehicle).cost > 0;
1110  }
1111  bool FilterSoftSpanQuadraticCost() const {
1112  return dimension_.HasQuadraticCostSoftSpanUpperBounds();
1113  }
1114  bool FilterSoftSpanQuadraticCost(int vehicle) const {
1115  return dimension_.HasQuadraticCostSoftSpanUpperBounds() &&
1116  dimension_.GetQuadraticCostSoftSpanUpperBoundForVehicle(vehicle)
1117  .cost > 0;
1118  }
1119 
1120  int64_t GetCumulSoftLowerBoundCost(int64_t node, int64_t cumul_value) const;
1121 
1122  int64_t GetPathCumulSoftLowerBoundCost(const PathTransits& path_transits,
1123  int path) const;
1124 
1125  void InitializeSupportedPathCumul(SupportedPathCumul* supported_cumul,
1126  int64_t default_value);
1127 
1128  // Given the vector of minimum cumuls on the path, determines if the pickup to
1129  // delivery limits for this dimension (if there are any) can be respected by
1130  // this path.
1131  // Returns true if for every pickup/delivery nodes visited on this path,
1132  // min_cumul_value(delivery) - max_cumul_value(pickup) is less than the limit
1133  // set for this pickup to delivery.
1134  // TODO(user): Verify if we should filter the pickup/delivery limits using
1135  // the LP, for a perfect filtering.
1136  bool PickupToDeliveryLimitsRespected(
1137  const PathTransits& path_transits, int path,
1138  const std::vector<int64_t>& min_path_cumuls) const;
1139 
1140  // Computes the maximum cumul value of nodes along the path using
1141  // [current|delta]_path_transits_, and stores the min/max cumul
1142  // related to each node in the corresponding vector
1143  // [current|delta]_[min|max]_node_cumuls_.
1144  // The boolean is_delta indicates if the computations should take place on the
1145  // "delta" or "current" members. When true, the nodes for which the min/max
1146  // cumul has changed from the current value are marked in
1147  // delta_nodes_with_precedences_and_changed_cumul_.
1148  void StoreMinMaxCumulOfNodesOnPath(
1149  int path, const std::vector<int64_t>& min_path_cumuls, bool is_delta);
1150 
1151  // Compute the max start cumul value for a given path and a given minimal end
1152  // cumul value.
1153  // NOTE: Since this function is used to compute a lower bound on the span of
1154  // the routes, we don't "jump" over the forbidden intervals with this min end
1155  // cumul value. We do however concurrently compute the max possible start
1156  // given the max end cumul, for which we can "jump" over forbidden intervals,
1157  // and return the minimum of the two.
1158  int64_t ComputePathMaxStartFromEndCumul(const PathTransits& path_transits,
1159  int path, int64_t path_start,
1160  int64_t min_end_cumul) const;
1161 
1162  const RoutingModel& routing_model_;
1163  const RoutingDimension& dimension_;
1164  const std::vector<IntVar*> cumuls_;
1165  const std::vector<IntVar*> slacks_;
1166  std::vector<int64_t> start_to_vehicle_;
1167  std::vector<const RoutingModel::TransitCallback2*> evaluators_;
1168  std::vector<int64_t> vehicle_span_upper_bounds_;
1169  bool has_vehicle_span_upper_bounds_;
1170  int64_t total_current_cumul_cost_value_;
1171  int64_t synchronized_objective_value_;
1172  int64_t accepted_objective_value_;
1173  // Map between paths and path soft cumul bound costs. The paths are indexed
1174  // by the index of the start node of the path.
1175  absl::flat_hash_map<int64_t, int64_t> current_cumul_cost_values_;
1176  int64_t cumul_cost_delta_;
1177  // Cumul cost values for paths in delta, indexed by vehicle.
1178  std::vector<int64_t> delta_path_cumul_cost_values_;
1179  const int64_t global_span_cost_coefficient_;
1180  std::vector<SoftBound> cumul_soft_bounds_;
1181  std::vector<SoftBound> cumul_soft_lower_bounds_;
1182  std::vector<const PiecewiseLinearFunction*> cumul_piecewise_linear_costs_;
1183  std::vector<int64_t> vehicle_span_cost_coefficients_;
1184  bool has_nonzero_vehicle_span_cost_coefficients_;
1185  const std::vector<int64_t> vehicle_capacities_;
1186  // node_index_to_precedences_[node_index] contains all NodePrecedence elements
1187  // with node_index as either "first_node" or "second_node".
1188  // This vector is empty if there are no precedences on the dimension_.
1189  std::vector<std::vector<RoutingDimension::NodePrecedence>>
1190  node_index_to_precedences_;
1191  // Data reflecting information on paths and cumul variables for the solution
1192  // to which the filter was synchronized.
1193  SupportedPathCumul current_min_start_;
1194  SupportedPathCumul current_max_end_;
1195  PathTransits current_path_transits_;
1196  // Current min/max cumul values, indexed by node.
1197  std::vector<std::pair<int64_t, int64_t>> current_min_max_node_cumuls_;
1198  // Data reflecting information on paths and cumul variables for the "delta"
1199  // solution (aka neighbor solution) being examined.
1200  PathTransits delta_path_transits_;
1201  int64_t delta_max_end_cumul_;
1202  SparseBitset<int64_t> delta_nodes_with_precedences_and_changed_cumul_;
1203  absl::flat_hash_map<int64_t, std::pair<int64_t, int64_t>>
1204  node_with_precedence_to_delta_min_max_cumuls_;
1205  absl::btree_set<int> delta_paths_;
1206  const std::string name_;
1207 
1208  LocalDimensionCumulOptimizer* optimizer_;
1209  LocalDimensionCumulOptimizer* mp_optimizer_;
1210  const bool filter_objective_cost_;
1211  // This boolean indicates if the LP optimizer can be used if necessary to
1212  // optimize the dimension cumuls.
1213  const bool can_use_lp_;
1214  const bool propagate_own_objective_value_;
1215 
1216  std::vector<int64_t> min_path_cumuls_;
1217 };
1218 
1219 PathCumulFilter::PathCumulFilter(const RoutingModel& routing_model,
1220  const RoutingDimension& dimension,
1221  bool propagate_own_objective_value,
1222  bool filter_objective_cost, bool can_use_lp)
1223  : BasePathFilter(routing_model.Nexts(), dimension.cumuls().size()),
1224  routing_model_(routing_model),
1225  dimension_(dimension),
1226  cumuls_(dimension.cumuls()),
1227  slacks_(dimension.slacks()),
1228  evaluators_(routing_model.vehicles(), nullptr),
1229  vehicle_span_upper_bounds_(dimension.vehicle_span_upper_bounds()),
1230  has_vehicle_span_upper_bounds_(false),
1231  total_current_cumul_cost_value_(0),
1232  synchronized_objective_value_(0),
1233  accepted_objective_value_(0),
1234  current_cumul_cost_values_(),
1235  cumul_cost_delta_(0),
1236  delta_path_cumul_cost_values_(routing_model.vehicles(),
1237  std::numeric_limits<int64_t>::min()),
1238  global_span_cost_coefficient_(dimension.global_span_cost_coefficient()),
1239  vehicle_span_cost_coefficients_(
1240  dimension.vehicle_span_cost_coefficients()),
1241  has_nonzero_vehicle_span_cost_coefficients_(false),
1242  vehicle_capacities_(dimension.vehicle_capacities()),
1243  delta_max_end_cumul_(0),
1244  delta_nodes_with_precedences_and_changed_cumul_(routing_model.Size()),
1245  name_(dimension.name()),
1246  optimizer_(routing_model.GetMutableLocalCumulLPOptimizer(dimension)),
1247  mp_optimizer_(routing_model.GetMutableLocalCumulMPOptimizer(dimension)),
1248  filter_objective_cost_(filter_objective_cost),
1249  can_use_lp_(can_use_lp),
1250  propagate_own_objective_value_(propagate_own_objective_value) {
1251  for (const int64_t upper_bound : vehicle_span_upper_bounds_) {
1253  has_vehicle_span_upper_bounds_ = true;
1254  break;
1255  }
1256  }
1257  for (const int64_t coefficient : vehicle_span_cost_coefficients_) {
1258  if (coefficient != 0) {
1259  has_nonzero_vehicle_span_cost_coefficients_ = true;
1260  break;
1261  }
1262  }
1263  cumul_soft_bounds_.resize(cumuls_.size());
1264  cumul_soft_lower_bounds_.resize(cumuls_.size());
1265  cumul_piecewise_linear_costs_.resize(cumuls_.size());
1266  bool has_cumul_soft_bounds = false;
1267  bool has_cumul_soft_lower_bounds = false;
1268  bool has_cumul_piecewise_linear_costs = false;
1269  bool has_cumul_hard_bounds = false;
1270  for (const IntVar* const slack : slacks_) {
1271  if (slack->Min() > 0) {
1272  has_cumul_hard_bounds = true;
1273  break;
1274  }
1275  }
1276  for (int i = 0; i < cumuls_.size(); ++i) {
1277  if (dimension.HasCumulVarSoftUpperBound(i)) {
1278  has_cumul_soft_bounds = true;
1279  cumul_soft_bounds_[i].bound = dimension.GetCumulVarSoftUpperBound(i);
1280  cumul_soft_bounds_[i].coefficient =
1281  dimension.GetCumulVarSoftUpperBoundCoefficient(i);
1282  }
1283  if (dimension.HasCumulVarSoftLowerBound(i)) {
1284  has_cumul_soft_lower_bounds = true;
1285  cumul_soft_lower_bounds_[i].bound =
1286  dimension.GetCumulVarSoftLowerBound(i);
1287  cumul_soft_lower_bounds_[i].coefficient =
1288  dimension.GetCumulVarSoftLowerBoundCoefficient(i);
1289  }
1290  if (dimension.HasCumulVarPiecewiseLinearCost(i)) {
1291  has_cumul_piecewise_linear_costs = true;
1292  cumul_piecewise_linear_costs_[i] =
1293  dimension.GetCumulVarPiecewiseLinearCost(i);
1294  }
1295  IntVar* const cumul_var = cumuls_[i];
1296  if (cumul_var->Min() > 0 ||
1297  cumul_var->Max() < std::numeric_limits<int64_t>::max()) {
1298  has_cumul_hard_bounds = true;
1299  }
1300  }
1301  if (!has_cumul_soft_bounds) {
1302  cumul_soft_bounds_.clear();
1303  }
1304  if (!has_cumul_soft_lower_bounds) {
1305  cumul_soft_lower_bounds_.clear();
1306  }
1307  if (!has_cumul_piecewise_linear_costs) {
1308  cumul_piecewise_linear_costs_.clear();
1309  }
1310  if (!has_cumul_hard_bounds) {
1311  // Slacks don't need to be constrained if the cumuls don't have hard bounds;
1312  // therefore we can ignore the vehicle span cost coefficient (note that the
1313  // transit part is already handled by the arc cost filters).
1314  // This doesn't concern the global span filter though.
1315  vehicle_span_cost_coefficients_.assign(routing_model.vehicles(), 0);
1316  has_nonzero_vehicle_span_cost_coefficients_ = false;
1317  }
1318  start_to_vehicle_.resize(Size(), -1);
1319  for (int i = 0; i < routing_model.vehicles(); ++i) {
1320  start_to_vehicle_[routing_model.Start(i)] = i;
1321  evaluators_[i] = &dimension.transit_evaluator(i);
1322  }
1323 
1324  const std::vector<RoutingDimension::NodePrecedence>& node_precedences =
1325  dimension.GetNodePrecedences();
1326  if (!node_precedences.empty()) {
1327  current_min_max_node_cumuls_.resize(cumuls_.size(), {-1, -1});
1328  node_index_to_precedences_.resize(cumuls_.size());
1329  for (const auto& node_precedence : node_precedences) {
1330  node_index_to_precedences_[node_precedence.first_node].push_back(
1331  node_precedence);
1332  node_index_to_precedences_[node_precedence.second_node].push_back(
1333  node_precedence);
1334  }
1335  }
1336 
1337 #ifndef NDEBUG
1338  for (int vehicle = 0; vehicle < routing_model.vehicles(); vehicle++) {
1339  if (FilterWithDimensionCumulOptimizerForVehicle(vehicle)) {
1340  DCHECK_NE(optimizer_, nullptr);
1341  DCHECK_NE(mp_optimizer_, nullptr);
1342  }
1343  }
1344 #endif // NDEBUG
1345 }
1346 
1347 int64_t PathCumulFilter::GetCumulSoftCost(int64_t node,
1348  int64_t cumul_value) const {
1349  if (node < cumul_soft_bounds_.size()) {
1350  const int64_t bound = cumul_soft_bounds_[node].bound;
1351  const int64_t coefficient = cumul_soft_bounds_[node].coefficient;
1352  if (coefficient > 0 && bound < cumul_value) {
1354  }
1355  }
1356  return 0;
1357 }
1358 
1359 int64_t PathCumulFilter::GetCumulPiecewiseLinearCost(
1360  int64_t node, int64_t cumul_value) const {
1361  if (node < cumul_piecewise_linear_costs_.size()) {
1362  const PiecewiseLinearFunction* cost = cumul_piecewise_linear_costs_[node];
1363  if (cost != nullptr) {
1364  return cost->Value(cumul_value);
1365  }
1366  }
1367  return 0;
1368 }
1369 
1370 int64_t PathCumulFilter::GetCumulSoftLowerBoundCost(int64_t node,
1371  int64_t cumul_value) const {
1372  if (node < cumul_soft_lower_bounds_.size()) {
1373  const int64_t bound = cumul_soft_lower_bounds_[node].bound;
1374  const int64_t coefficient = cumul_soft_lower_bounds_[node].coefficient;
1375  if (coefficient > 0 && bound > cumul_value) {
1377  }
1378  }
1379  return 0;
1380 }
1381 
1382 int64_t PathCumulFilter::GetPathCumulSoftLowerBoundCost(
1383  const PathTransits& path_transits, int path) const {
1384  int64_t node = path_transits.Node(path, path_transits.PathSize(path) - 1);
1385  int64_t cumul = cumuls_[node]->Max();
1386  int64_t current_cumul_cost_value = GetCumulSoftLowerBoundCost(node, cumul);
1387  for (int i = path_transits.PathSize(path) - 2; i >= 0; --i) {
1388  node = path_transits.Node(path, i);
1389  cumul = CapSub(cumul, path_transits.Transit(path, i));
1390  cumul = std::min(cumuls_[node]->Max(), cumul);
1391  current_cumul_cost_value = CapAdd(current_cumul_cost_value,
1392  GetCumulSoftLowerBoundCost(node, cumul));
1393  }
1394  return current_cumul_cost_value;
1395 }
1396 
1397 void PathCumulFilter::OnBeforeSynchronizePaths() {
1398  total_current_cumul_cost_value_ = 0;
1399  cumul_cost_delta_ = 0;
1400  current_cumul_cost_values_.clear();
1401  if (NumPaths() > 0 &&
1402  (FilterSpanCost() || FilterCumulSoftBounds() || FilterSlackCost() ||
1403  FilterCumulSoftLowerBounds() || FilterCumulPiecewiseLinearCosts() ||
1404  FilterPrecedences() || FilterSoftSpanCost() ||
1405  FilterSoftSpanQuadraticCost())) {
1406  InitializeSupportedPathCumul(&current_min_start_,
1408  InitializeSupportedPathCumul(&current_max_end_,
1410  current_path_transits_.Clear();
1411  current_path_transits_.AddPaths(NumPaths());
1412  // For each path, compute the minimum end cumul and store the max of these.
1413  for (int r = 0; r < NumPaths(); ++r) {
1414  int64_t node = Start(r);
1415  const int vehicle = start_to_vehicle_[Start(r)];
1416  // First pass: evaluating route length to reserve memory to store route
1417  // information.
1418  int number_of_route_arcs = 0;
1419  while (node < Size()) {
1420  ++number_of_route_arcs;
1421  node = Value(node);
1422  }
1423  current_path_transits_.ReserveTransits(r, number_of_route_arcs);
1424  // Second pass: update cumul, transit and cost values.
1425  node = Start(r);
1426  int64_t cumul = cumuls_[node]->Min();
1427  min_path_cumuls_.clear();
1428  min_path_cumuls_.push_back(cumul);
1429 
1430  int64_t current_cumul_cost_value = GetCumulSoftCost(node, cumul);
1431  current_cumul_cost_value = CapAdd(
1432  current_cumul_cost_value, GetCumulPiecewiseLinearCost(node, cumul));
1433 
1434  int64_t total_transit = 0;
1435  while (node < Size()) {
1436  const int64_t next = Value(node);
1437  const int64_t transit = (*evaluators_[vehicle])(node, next);
1438  total_transit = CapAdd(total_transit, transit);
1439  const int64_t transit_slack = CapAdd(transit, slacks_[node]->Min());
1440  current_path_transits_.PushTransit(r, node, next, transit_slack);
1441  cumul = CapAdd(cumul, transit_slack);
1442  cumul =
1443  dimension_.GetFirstPossibleGreaterOrEqualValueForNode(next, cumul);
1444  cumul = std::max(cumuls_[next]->Min(), cumul);
1445  min_path_cumuls_.push_back(cumul);
1446  node = next;
1447  current_cumul_cost_value =
1448  CapAdd(current_cumul_cost_value, GetCumulSoftCost(node, cumul));
1449  current_cumul_cost_value = CapAdd(
1450  current_cumul_cost_value, GetCumulPiecewiseLinearCost(node, cumul));
1451  }
1452  if (FilterPrecedences()) {
1453  StoreMinMaxCumulOfNodesOnPath(/*path=*/r, min_path_cumuls_,
1454  /*is_delta=*/false);
1455  }
1456  if (number_of_route_arcs == 1 &&
1457  !routing_model_.IsVehicleUsedWhenEmpty(vehicle)) {
1458  // This is an empty route (single start->end arc) which we don't take
1459  // into account for costs.
1460  current_cumul_cost_values_[Start(r)] = 0;
1461  current_path_transits_.ClearPath(r);
1462  continue;
1463  }
1464  if (FilterSlackCost() || FilterSoftSpanCost() ||
1465  FilterSoftSpanQuadraticCost()) {
1466  const int64_t start = ComputePathMaxStartFromEndCumul(
1467  current_path_transits_, r, Start(r), cumul);
1468  const int64_t span_lower_bound = CapSub(cumul, start);
1469  if (FilterSlackCost()) {
1470  current_cumul_cost_value =
1471  CapAdd(current_cumul_cost_value,
1472  CapProd(vehicle_span_cost_coefficients_[vehicle],
1473  CapSub(span_lower_bound, total_transit)));
1474  }
1475  if (FilterSoftSpanCost()) {
1476  const BoundCost bound_cost =
1477  dimension_.GetSoftSpanUpperBoundForVehicle(vehicle);
1478  if (bound_cost.bound < span_lower_bound) {
1479  const int64_t violation =
1480  CapSub(span_lower_bound, bound_cost.bound);
1481  current_cumul_cost_value = CapAdd(
1482  current_cumul_cost_value, CapProd(bound_cost.cost, violation));
1483  }
1484  }
1485  if (FilterSoftSpanQuadraticCost()) {
1486  const BoundCost bound_cost =
1487  dimension_.GetQuadraticCostSoftSpanUpperBoundForVehicle(vehicle);
1488  if (bound_cost.bound < span_lower_bound) {
1489  const int64_t violation =
1490  CapSub(span_lower_bound, bound_cost.bound);
1491  current_cumul_cost_value =
1492  CapAdd(current_cumul_cost_value,
1493  CapProd(bound_cost.cost, CapProd(violation, violation)));
1494  }
1495  }
1496  }
1497  if (FilterCumulSoftLowerBounds()) {
1498  current_cumul_cost_value =
1499  CapAdd(current_cumul_cost_value,
1500  GetPathCumulSoftLowerBoundCost(current_path_transits_, r));
1501  }
1502  if (FilterWithDimensionCumulOptimizerForVehicle(vehicle)) {
1503  // TODO(user): Return a status from the optimizer to detect failures
1504  // The only admissible failures here are because of LP timeout.
1505  int64_t lp_cumul_cost_value = 0;
1506  LocalDimensionCumulOptimizer* const optimizer =
1507  FilterBreakCost(vehicle) ? mp_optimizer_ : optimizer_;
1508  DCHECK(optimizer != nullptr);
1510  optimizer->ComputeRouteCumulCostWithoutFixedTransits(
1511  vehicle, [this](int64_t node) { return Value(node); },
1512  &lp_cumul_cost_value);
1513  switch (status) {
1515  lp_cumul_cost_value = 0;
1516  break;
1517  case DimensionSchedulingStatus::RELAXED_OPTIMAL_ONLY:
1518  DCHECK(mp_optimizer_ != nullptr);
1519  if (mp_optimizer_->ComputeRouteCumulCostWithoutFixedTransits(
1520  vehicle, [this](int64_t node) { return Value(node); },
1521  &lp_cumul_cost_value) ==
1523  lp_cumul_cost_value = 0;
1524  }
1525  break;
1526  default:
1528  }
1529  current_cumul_cost_value =
1530  std::max(current_cumul_cost_value, lp_cumul_cost_value);
1531  }
1532  current_cumul_cost_values_[Start(r)] = current_cumul_cost_value;
1533  current_max_end_.path_values[r] = cumul;
1534  if (current_max_end_.cumul_value < cumul) {
1535  current_max_end_.cumul_value = cumul;
1536  current_max_end_.cumul_value_support = r;
1537  }
1538  total_current_cumul_cost_value_ =
1539  CapAdd(total_current_cumul_cost_value_, current_cumul_cost_value);
1540  }
1541  if (FilterPrecedences()) {
1542  // Update the min/max node cumuls of new unperformed nodes.
1543  for (int64_t node : GetNewSynchronizedUnperformedNodes()) {
1544  current_min_max_node_cumuls_[node] = {-1, -1};
1545  }
1546  }
1547  // Use the max of the path end cumul mins to compute the corresponding
1548  // maximum start cumul of each path; store the minimum of these.
1549  for (int r = 0; r < NumPaths(); ++r) {
1550  const int64_t start = ComputePathMaxStartFromEndCumul(
1551  current_path_transits_, r, Start(r), current_max_end_.cumul_value);
1552  current_min_start_.path_values[r] = start;
1553  if (current_min_start_.cumul_value > start) {
1554  current_min_start_.cumul_value = start;
1555  current_min_start_.cumul_value_support = r;
1556  }
1557  }
1558  }
1559  // Initialize this before considering any deltas (neighbor).
1560  delta_max_end_cumul_ = std::numeric_limits<int64_t>::min();
1561 
1562  DCHECK(global_span_cost_coefficient_ == 0 ||
1563  current_min_start_.cumul_value <= current_max_end_.cumul_value);
1564  synchronized_objective_value_ =
1565  CapAdd(total_current_cumul_cost_value_,
1566  CapProd(global_span_cost_coefficient_,
1567  CapSub(current_max_end_.cumul_value,
1568  current_min_start_.cumul_value)));
1569 }
1570 
1571 bool PathCumulFilter::AcceptPath(int64_t path_start, int64_t /*chain_start*/,
1572  int64_t /*chain_end*/) {
1573  int64_t node = path_start;
1574  int64_t cumul = cumuls_[node]->Min();
1575  int64_t cumul_cost_delta = 0;
1576  int64_t total_transit = 0;
1577  const int path = delta_path_transits_.AddPaths(1);
1578  const int vehicle = start_to_vehicle_[path_start];
1579  const int64_t capacity = vehicle_capacities_[vehicle];
1580  const bool filter_vehicle_costs =
1581  !routing_model_.IsEnd(GetNext(node)) ||
1582  routing_model_.IsVehicleUsedWhenEmpty(vehicle);
1583  if (filter_vehicle_costs) {
1584  cumul_cost_delta = CapAdd(GetCumulSoftCost(node, cumul),
1585  GetCumulPiecewiseLinearCost(node, cumul));
1586  }
1587  // Evaluating route length to reserve memory to store transit information.
1588  int number_of_route_arcs = 0;
1589  while (node < Size()) {
1590  ++number_of_route_arcs;
1591  node = GetNext(node);
1592  DCHECK_NE(node, kUnassigned);
1593  }
1594  delta_path_transits_.ReserveTransits(path, number_of_route_arcs);
1595  min_path_cumuls_.clear();
1596  min_path_cumuls_.push_back(cumul);
1597  // Check that the path is feasible with regards to cumul bounds, scanning
1598  // the paths from start to end (caching path node sequences and transits
1599  // for further span cost filtering).
1600  node = path_start;
1601  while (node < Size()) {
1602  const int64_t next = GetNext(node);
1603  const int64_t transit = (*evaluators_[vehicle])(node, next);
1604  total_transit = CapAdd(total_transit, transit);
1605  const int64_t transit_slack = CapAdd(transit, slacks_[node]->Min());
1606  delta_path_transits_.PushTransit(path, node, next, transit_slack);
1607  cumul = CapAdd(cumul, transit_slack);
1608  cumul = dimension_.GetFirstPossibleGreaterOrEqualValueForNode(next, cumul);
1609  if (cumul > std::min(capacity, cumuls_[next]->Max())) {
1610  return false;
1611  }
1612  cumul = std::max(cumuls_[next]->Min(), cumul);
1613  min_path_cumuls_.push_back(cumul);
1614  node = next;
1615  if (filter_vehicle_costs) {
1616  cumul_cost_delta =
1617  CapAdd(cumul_cost_delta, GetCumulSoftCost(node, cumul));
1618  cumul_cost_delta =
1619  CapAdd(cumul_cost_delta, GetCumulPiecewiseLinearCost(node, cumul));
1620  }
1621  }
1622  const int64_t min_end = cumul;
1623 
1624  if (!PickupToDeliveryLimitsRespected(delta_path_transits_, path,
1625  min_path_cumuls_)) {
1626  return false;
1627  }
1628  if (FilterSlackCost() || FilterBreakCost(vehicle) ||
1629  FilterSoftSpanCost(vehicle) || FilterSoftSpanQuadraticCost(vehicle)) {
1630  int64_t slack_max = std::numeric_limits<int64_t>::max();
1631  if (vehicle_span_upper_bounds_[vehicle] <
1633  const int64_t span_max = vehicle_span_upper_bounds_[vehicle];
1634  slack_max = std::min(slack_max, CapSub(span_max, total_transit));
1635  }
1636  const int64_t max_start_from_min_end = ComputePathMaxStartFromEndCumul(
1637  delta_path_transits_, path, path_start, min_end);
1638  const int64_t span_lb = CapSub(min_end, max_start_from_min_end);
1639  int64_t min_total_slack = CapSub(span_lb, total_transit);
1640  if (min_total_slack > slack_max) return false;
1641 
1642  if (dimension_.HasBreakConstraints()) {
1643  for (const auto [limit, min_break_duration] :
1644  dimension_.GetBreakDistanceDurationOfVehicle(vehicle)) {
1645  // Minimal number of breaks depends on total transit:
1646  // 0 breaks for 0 <= total transit <= limit,
1647  // 1 break for limit + 1 <= total transit <= 2 * limit,
1648  // i breaks for i * limit + 1 <= total transit <= (i+1) * limit, ...
1649  if (limit == 0 || total_transit == 0) continue;
1650  const int num_breaks_lb = (total_transit - 1) / limit;
1651  const int64_t slack_lb = CapProd(num_breaks_lb, min_break_duration);
1652  if (slack_lb > slack_max) return false;
1653  min_total_slack = std::max(min_total_slack, slack_lb);
1654  }
1655  // Compute a lower bound of the amount of break that must be made inside
1656  // the route. We compute a mandatory interval (might be empty)
1657  // [max_start, min_end[ during which the route will have to happen,
1658  // then the duration of break that must happen during this interval.
1659  int64_t min_total_break = 0;
1660  int64_t max_path_end = cumuls_[routing_model_.End(vehicle)]->Max();
1661  const int64_t max_start = ComputePathMaxStartFromEndCumul(
1662  delta_path_transits_, path, path_start, max_path_end);
1663  for (const IntervalVar* br :
1664  dimension_.GetBreakIntervalsOfVehicle(vehicle)) {
1665  if (!br->MustBePerformed()) continue;
1666  if (max_start < br->EndMin() && br->StartMax() < min_end) {
1667  min_total_break = CapAdd(min_total_break, br->DurationMin());
1668  }
1669  }
1670  if (min_total_break > slack_max) return false;
1671  min_total_slack = std::max(min_total_slack, min_total_break);
1672  }
1673  if (filter_vehicle_costs) {
1674  cumul_cost_delta = CapAdd(
1675  cumul_cost_delta,
1676  CapProd(vehicle_span_cost_coefficients_[vehicle], min_total_slack));
1677  const int64_t span_lower_bound = CapAdd(total_transit, min_total_slack);
1678  if (FilterSoftSpanCost()) {
1679  const BoundCost bound_cost =
1680  dimension_.GetSoftSpanUpperBoundForVehicle(vehicle);
1681  if (bound_cost.bound < span_lower_bound) {
1682  const int64_t violation = CapSub(span_lower_bound, bound_cost.bound);
1683  cumul_cost_delta =
1684  CapAdd(cumul_cost_delta, CapProd(bound_cost.cost, violation));
1685  }
1686  }
1687  if (FilterSoftSpanQuadraticCost()) {
1688  const BoundCost bound_cost =
1689  dimension_.GetQuadraticCostSoftSpanUpperBoundForVehicle(vehicle);
1690  if (bound_cost.bound < span_lower_bound) {
1691  const int64_t violation = CapSub(span_lower_bound, bound_cost.bound);
1692  cumul_cost_delta =
1693  CapAdd(cumul_cost_delta,
1694  CapProd(bound_cost.cost, CapProd(violation, violation)));
1695  }
1696  }
1697  }
1698  if (CapAdd(total_transit, min_total_slack) >
1699  vehicle_span_upper_bounds_[vehicle]) {
1700  return false;
1701  }
1702  }
1703  if (FilterCumulSoftLowerBounds() && filter_vehicle_costs) {
1704  cumul_cost_delta =
1705  CapAdd(cumul_cost_delta,
1706  GetPathCumulSoftLowerBoundCost(delta_path_transits_, path));
1707  }
1708  if (FilterPrecedences()) {
1709  StoreMinMaxCumulOfNodesOnPath(path, min_path_cumuls_, /*is_delta=*/true);
1710  }
1711  if (!filter_vehicle_costs) {
1712  // If this route's costs shouldn't be taken into account, reset the
1713  // cumul_cost_delta and delta_path_transits_ for this path.
1714  cumul_cost_delta = 0;
1715  delta_path_transits_.ClearPath(path);
1716  }
1717  if (FilterSpanCost() || FilterCumulSoftBounds() || FilterSlackCost() ||
1718  FilterCumulSoftLowerBounds() || FilterCumulPiecewiseLinearCosts() ||
1719  FilterSoftSpanCost(vehicle) || FilterSoftSpanQuadraticCost(vehicle)) {
1720  delta_paths_.insert(GetPath(path_start));
1721  delta_path_cumul_cost_values_[vehicle] = cumul_cost_delta;
1722  cumul_cost_delta =
1723  CapSub(cumul_cost_delta, current_cumul_cost_values_[path_start]);
1724  if (filter_vehicle_costs) {
1725  delta_max_end_cumul_ = std::max(delta_max_end_cumul_, min_end);
1726  }
1727  }
1728  cumul_cost_delta_ = CapAdd(cumul_cost_delta_, cumul_cost_delta);
1729  return true;
1730 }
1731 
1732 bool PathCumulFilter::FinalizeAcceptPath(int64_t /*objective_min*/,
1733  int64_t objective_max) {
1734  DCHECK(!lns_detected());
1735  if (!FilterSpanCost() && !FilterCumulSoftBounds() && !FilterSlackCost() &&
1736  !FilterCumulSoftLowerBounds() && !FilterCumulPiecewiseLinearCosts() &&
1737  !FilterPrecedences() && !FilterSoftSpanCost() &&
1738  !FilterSoftSpanQuadraticCost()) {
1739  return true;
1740  }
1741  if (FilterPrecedences()) {
1742  for (int64_t node : delta_nodes_with_precedences_and_changed_cumul_
1743  .PositionsSetAtLeastOnce()) {
1744  const std::pair<int64_t, int64_t> node_min_max_cumul_in_delta =
1745  gtl::FindWithDefault(node_with_precedence_to_delta_min_max_cumuls_,
1746  node, {-1, -1});
1747  // NOTE: This node was seen in delta, so its delta min/max cumul should be
1748  // stored in the map.
1749  DCHECK(node_min_max_cumul_in_delta.first >= 0 &&
1750  node_min_max_cumul_in_delta.second >= 0);
1751  for (const RoutingDimension::NodePrecedence& precedence :
1752  node_index_to_precedences_[node]) {
1753  const bool node_is_first = (precedence.first_node == node);
1754  const int64_t other_node =
1755  node_is_first ? precedence.second_node : precedence.first_node;
1756  if (GetNext(other_node) == kUnassigned ||
1757  GetNext(other_node) == other_node) {
1758  // The other node is unperformed, so the precedence constraint is
1759  // inactive.
1760  continue;
1761  }
1762  // max_cumul[second_node] should be greater or equal than
1763  // min_cumul[first_node] + offset.
1764  const std::pair<int64_t, int64_t>& other_min_max_cumul_in_delta =
1765  gtl::FindWithDefault(node_with_precedence_to_delta_min_max_cumuls_,
1766  other_node,
1767  current_min_max_node_cumuls_[other_node]);
1768 
1769  const int64_t first_min_cumul =
1770  node_is_first ? node_min_max_cumul_in_delta.first
1771  : other_min_max_cumul_in_delta.first;
1772  const int64_t second_max_cumul =
1773  node_is_first ? other_min_max_cumul_in_delta.second
1774  : node_min_max_cumul_in_delta.second;
1775 
1776  if (second_max_cumul < first_min_cumul + precedence.offset) {
1777  return false;
1778  }
1779  }
1780  }
1781  }
1782  int64_t new_max_end = delta_max_end_cumul_;
1783  int64_t new_min_start = std::numeric_limits<int64_t>::max();
1784  if (FilterSpanCost()) {
1785  if (new_max_end < current_max_end_.cumul_value) {
1786  // Delta max end is lower than the current solution one.
1787  // If the path supporting the current max end has been modified, we need
1788  // to check all paths to find the largest max end.
1789  if (!delta_paths_.contains(current_max_end_.cumul_value_support)) {
1790  new_max_end = current_max_end_.cumul_value;
1791  } else {
1792  for (int i = 0; i < current_max_end_.path_values.size(); ++i) {
1793  if (current_max_end_.path_values[i] > new_max_end &&
1794  !delta_paths_.contains(i)) {
1795  new_max_end = current_max_end_.path_values[i];
1796  }
1797  }
1798  }
1799  }
1800  // Now that the max end cumul has been found, compute the corresponding
1801  // min start cumul, first from the delta, then if the max end cumul has
1802  // changed, from the unchanged paths as well.
1803  for (int r = 0; r < delta_path_transits_.NumPaths(); ++r) {
1804  new_min_start =
1805  std::min(ComputePathMaxStartFromEndCumul(delta_path_transits_, r,
1806  Start(r), new_max_end),
1807  new_min_start);
1808  }
1809  if (new_max_end != current_max_end_.cumul_value) {
1810  for (int r = 0; r < NumPaths(); ++r) {
1811  if (delta_paths_.contains(r)) {
1812  continue;
1813  }
1814  new_min_start = std::min(new_min_start, ComputePathMaxStartFromEndCumul(
1815  current_path_transits_, r,
1816  Start(r), new_max_end));
1817  }
1818  } else if (new_min_start > current_min_start_.cumul_value) {
1819  // Delta min start is greater than the current solution one.
1820  // If the path supporting the current min start has been modified, we need
1821  // to check all paths to find the smallest min start.
1822  if (!delta_paths_.contains(current_min_start_.cumul_value_support)) {
1823  new_min_start = current_min_start_.cumul_value;
1824  } else {
1825  for (int i = 0; i < current_min_start_.path_values.size(); ++i) {
1826  if (current_min_start_.path_values[i] < new_min_start &&
1827  !delta_paths_.contains(i)) {
1828  new_min_start = current_min_start_.path_values[i];
1829  }
1830  }
1831  }
1832  }
1833  }
1834 
1835  // Filtering on objective value, calling LPs and MIPs if needed..
1836  accepted_objective_value_ =
1837  CapAdd(cumul_cost_delta_, CapProd(global_span_cost_coefficient_,
1838  CapSub(new_max_end, new_min_start)));
1839 
1840  if (can_use_lp_ && optimizer_ != nullptr &&
1841  accepted_objective_value_ <= objective_max) {
1842  const size_t num_touched_paths = GetTouchedPathStarts().size();
1843  std::vector<int64_t> path_delta_cost_values(num_touched_paths, 0);
1844  std::vector<bool> requires_mp(num_touched_paths, false);
1845  for (int i = 0; i < num_touched_paths; ++i) {
1846  const int64_t start = GetTouchedPathStarts()[i];
1847  const int vehicle = start_to_vehicle_[start];
1848  if (!FilterWithDimensionCumulOptimizerForVehicle(vehicle)) {
1849  continue;
1850  }
1851  int64_t path_delta_cost_with_lp = 0;
1853  optimizer_->ComputeRouteCumulCostWithoutFixedTransits(
1854  vehicle, [this](int64_t node) { return GetNext(node); },
1855  &path_delta_cost_with_lp);
1857  return false;
1858  }
1859  DCHECK(delta_paths_.contains(GetPath(start)));
1860  const int64_t path_cost_diff_with_lp = CapSub(
1861  path_delta_cost_with_lp, delta_path_cumul_cost_values_[vehicle]);
1862  if (path_cost_diff_with_lp > 0) {
1863  path_delta_cost_values[i] = path_delta_cost_with_lp;
1864  accepted_objective_value_ =
1865  CapAdd(accepted_objective_value_, path_cost_diff_with_lp);
1866  if (accepted_objective_value_ > objective_max) {
1867  return false;
1868  }
1869  } else {
1870  path_delta_cost_values[i] = delta_path_cumul_cost_values_[vehicle];
1871  }
1872  DCHECK_NE(mp_optimizer_, nullptr);
1873  requires_mp[i] =
1874  FilterBreakCost(vehicle) ||
1875  (status == DimensionSchedulingStatus::RELAXED_OPTIMAL_ONLY);
1876  }
1877 
1878  DCHECK_LE(accepted_objective_value_, objective_max);
1879 
1880  for (int i = 0; i < num_touched_paths; ++i) {
1881  if (!requires_mp[i]) {
1882  continue;
1883  }
1884  const int64_t start = GetTouchedPathStarts()[i];
1885  const int vehicle = start_to_vehicle_[start];
1886  int64_t path_delta_cost_with_mp = 0;
1887  if (mp_optimizer_->ComputeRouteCumulCostWithoutFixedTransits(
1888  vehicle, [this](int64_t node) { return GetNext(node); },
1889  &path_delta_cost_with_mp) ==
1891  return false;
1892  }
1893  DCHECK(delta_paths_.contains(GetPath(start)));
1894  const int64_t path_cost_diff_with_mp =
1895  CapSub(path_delta_cost_with_mp, path_delta_cost_values[i]);
1896  if (path_cost_diff_with_mp > 0) {
1897  accepted_objective_value_ =
1898  CapAdd(accepted_objective_value_, path_cost_diff_with_mp);
1899  if (accepted_objective_value_ > objective_max) {
1900  return false;
1901  }
1902  }
1903  }
1904  }
1905 
1906  return accepted_objective_value_ <= objective_max;
1907 }
1908 
1909 void PathCumulFilter::InitializeSupportedPathCumul(
1910  SupportedPathCumul* supported_cumul, int64_t default_value) {
1911  supported_cumul->cumul_value = default_value;
1912  supported_cumul->cumul_value_support = -1;
1913  supported_cumul->path_values.resize(NumPaths(), default_value);
1914 }
1915 
1916 bool PathCumulFilter::PickupToDeliveryLimitsRespected(
1917  const PathTransits& path_transits, int path,
1918  const std::vector<int64_t>& min_path_cumuls) const {
1919  if (!dimension_.HasPickupToDeliveryLimits()) {
1920  return true;
1921  }
1922  const int num_pairs = routing_model_.GetPickupAndDeliveryPairs().size();
1923  DCHECK_GT(num_pairs, 0);
1924  std::vector<std::pair<int, int64_t>> visited_delivery_and_min_cumul_per_pair(
1925  num_pairs, {-1, -1});
1926 
1927  const int path_size = path_transits.PathSize(path);
1928  CHECK_EQ(min_path_cumuls.size(), path_size);
1929 
1930  int64_t max_cumul = min_path_cumuls.back();
1931  for (int i = path_transits.PathSize(path) - 2; i >= 0; i--) {
1932  const int node_index = path_transits.Node(path, i);
1933  max_cumul = CapSub(max_cumul, path_transits.Transit(path, i));
1934  max_cumul = std::min(cumuls_[node_index]->Max(), max_cumul);
1935 
1936  const std::vector<std::pair<int, int>>& pickup_index_pairs =
1937  routing_model_.GetPickupIndexPairs(node_index);
1938  const std::vector<std::pair<int, int>>& delivery_index_pairs =
1939  routing_model_.GetDeliveryIndexPairs(node_index);
1940  if (!pickup_index_pairs.empty()) {
1941  // The node is a pickup. Check that it is not a delivery and that it
1942  // appears in a single pickup/delivery pair (as required when limits are
1943  // set on dimension cumuls for pickup and deliveries).
1944  DCHECK(delivery_index_pairs.empty());
1945  DCHECK_EQ(pickup_index_pairs.size(), 1);
1946  const int pair_index = pickup_index_pairs[0].first;
1947  // Get the delivery visited for this pair.
1948  const int delivery_index =
1949  visited_delivery_and_min_cumul_per_pair[pair_index].first;
1950  if (delivery_index < 0) {
1951  // No delivery visited after this pickup for this pickup/delivery pair.
1952  continue;
1953  }
1954  const int64_t cumul_diff_limit =
1955  dimension_.GetPickupToDeliveryLimitForPair(
1956  pair_index, pickup_index_pairs[0].second, delivery_index);
1957  if (CapSub(visited_delivery_and_min_cumul_per_pair[pair_index].second,
1958  max_cumul) > cumul_diff_limit) {
1959  return false;
1960  }
1961  }
1962  if (!delivery_index_pairs.empty()) {
1963  // The node is a delivery. Check that it's not a pickup and it belongs to
1964  // a single pair.
1965  DCHECK(pickup_index_pairs.empty());
1966  DCHECK_EQ(delivery_index_pairs.size(), 1);
1967  const int pair_index = delivery_index_pairs[0].first;
1968  std::pair<int, int64_t>& delivery_index_and_cumul =
1969  visited_delivery_and_min_cumul_per_pair[pair_index];
1970  int& delivery_index = delivery_index_and_cumul.first;
1971  DCHECK_EQ(delivery_index, -1);
1972  delivery_index = delivery_index_pairs[0].second;
1973  delivery_index_and_cumul.second = min_path_cumuls[i];
1974  }
1975  }
1976  return true;
1977 }
1978 
1979 void PathCumulFilter::StoreMinMaxCumulOfNodesOnPath(
1980  int path, const std::vector<int64_t>& min_path_cumuls, bool is_delta) {
1981  const PathTransits& path_transits =
1982  is_delta ? delta_path_transits_ : current_path_transits_;
1983 
1984  const int path_size = path_transits.PathSize(path);
1985  DCHECK_EQ(min_path_cumuls.size(), path_size);
1986 
1987  int64_t max_cumul = cumuls_[path_transits.Node(path, path_size - 1)]->Max();
1988  for (int i = path_size - 1; i >= 0; i--) {
1989  const int node_index = path_transits.Node(path, i);
1990 
1991  if (i < path_size - 1) {
1992  max_cumul = CapSub(max_cumul, path_transits.Transit(path, i));
1993  max_cumul = std::min(cumuls_[node_index]->Max(), max_cumul);
1994  }
1995 
1996  if (is_delta && node_index_to_precedences_[node_index].empty()) {
1997  // No need to update the delta cumul map for nodes without precedences.
1998  continue;
1999  }
2000 
2001  std::pair<int64_t, int64_t>& min_max_cumuls =
2002  is_delta ? node_with_precedence_to_delta_min_max_cumuls_[node_index]
2003  : current_min_max_node_cumuls_[node_index];
2004  min_max_cumuls.first = min_path_cumuls[i];
2005  min_max_cumuls.second = max_cumul;
2006 
2007  if (is_delta && !routing_model_.IsEnd(node_index) &&
2008  (min_max_cumuls.first !=
2009  current_min_max_node_cumuls_[node_index].first ||
2010  max_cumul != current_min_max_node_cumuls_[node_index].second)) {
2011  delta_nodes_with_precedences_and_changed_cumul_.Set(node_index);
2012  }
2013  }
2014 }
2015 
2016 int64_t PathCumulFilter::ComputePathMaxStartFromEndCumul(
2017  const PathTransits& path_transits, int path, int64_t path_start,
2018  int64_t min_end_cumul) const {
2019  int64_t cumul_from_min_end = min_end_cumul;
2020  int64_t cumul_from_max_end =
2021  cumuls_[routing_model_.End(start_to_vehicle_[path_start])]->Max();
2022  for (int i = path_transits.PathSize(path) - 2; i >= 0; --i) {
2023  const int64_t transit = path_transits.Transit(path, i);
2024  const int64_t node = path_transits.Node(path, i);
2025  cumul_from_min_end =
2026  std::min(cumuls_[node]->Max(), CapSub(cumul_from_min_end, transit));
2027  cumul_from_max_end = dimension_.GetLastPossibleLessOrEqualValueForNode(
2028  node, CapSub(cumul_from_max_end, transit));
2029  }
2030  return std::min(cumul_from_min_end, cumul_from_max_end);
2031 }
2032 
2033 } // namespace
2034 
2036  bool propagate_own_objective_value,
2037  bool filter_objective_cost,
2038  bool can_use_lp) {
2039  RoutingModel& model = *dimension.model();
2040  return model.solver()->RevAlloc(
2041  new PathCumulFilter(model, dimension, propagate_own_objective_value,
2042  filter_objective_cost, can_use_lp));
2043 }
2044 
2045 namespace {
2046 
2047 bool DimensionHasCumulCost(const RoutingDimension& dimension) {
2048  if (dimension.global_span_cost_coefficient() != 0) return true;
2049  if (dimension.HasSoftSpanUpperBounds()) return true;
2050  if (dimension.HasQuadraticCostSoftSpanUpperBounds()) return true;
2051  for (const int64_t coefficient : dimension.vehicle_span_cost_coefficients()) {
2052  if (coefficient != 0) return true;
2053  }
2054  for (int i = 0; i < dimension.cumuls().size(); ++i) {
2055  if (dimension.HasCumulVarSoftUpperBound(i)) return true;
2056  if (dimension.HasCumulVarSoftLowerBound(i)) return true;
2057  if (dimension.HasCumulVarPiecewiseLinearCost(i)) return true;
2058  }
2059  return false;
2060 }
2061 
2062 bool DimensionHasPathCumulConstraint(const RoutingDimension& dimension) {
2063  if (dimension.HasBreakConstraints()) return true;
2064  if (dimension.HasPickupToDeliveryLimits()) return true;
2065  for (const int64_t upper_bound : dimension.vehicle_span_upper_bounds()) {
2066  if (upper_bound != std::numeric_limits<int64_t>::max()) return true;
2067  }
2068  for (const IntVar* const slack : dimension.slacks()) {
2069  if (slack->Min() > 0) return true;
2070  }
2071  const std::vector<IntVar*>& cumuls = dimension.cumuls();
2072  for (int i = 0; i < cumuls.size(); ++i) {
2073  IntVar* const cumul_var = cumuls[i];
2074  if (cumul_var->Min() > 0 &&
2075  cumul_var->Max() < std::numeric_limits<int64_t>::max() &&
2076  !dimension.model()->IsEnd(i)) {
2077  return true;
2078  }
2079  if (dimension.forbidden_intervals()[i].NumIntervals() > 0) return true;
2080  }
2081  return false;
2082 }
2083 
2084 } // namespace
2085 
2087  const PathState* path_state,
2088  const std::vector<RoutingDimension*>& dimensions,
2089  std::vector<LocalSearchFilterManager::FilterEvent>* filters) {
2091  // For every dimension that fits, add a DimensionChecker.
2092  // Add a DimensionChecker for every dimension.
2093  for (const RoutingDimension* dimension : dimensions) {
2094  // Fill path capacities and classes.
2095  const int num_vehicles = dimension->model()->vehicles();
2096  std::vector<Interval> path_capacity(num_vehicles);
2097  std::vector<int> path_class(num_vehicles);
2098  for (int v = 0; v < num_vehicles; ++v) {
2099  const auto& vehicle_capacities = dimension->vehicle_capacities();
2100  path_capacity[v] = {0, vehicle_capacities[v]};
2101  path_class[v] = dimension->vehicle_to_class(v);
2102  }
2103  // For each class, retrieve the demands of each node.
2104  // Dimension store evaluators with a double indirection for compacity:
2105  // vehicle -> vehicle_class -> evaluator_index.
2106  // We replicate this in DimensionChecker,
2107  // except we expand evaluator_index to an array of values for all nodes.
2108  const int num_vehicle_classes =
2109  1 + *std::max_element(path_class.begin(), path_class.end());
2110  const int num_cumuls = dimension->cumuls().size();
2111  const int num_slacks = dimension->slacks().size();
2112  std::vector<std::function<Interval(int64_t, int64_t)>> transits(
2113  num_vehicle_classes, nullptr);
2114  for (int vehicle = 0; vehicle < num_vehicles; ++vehicle) {
2115  const int vehicle_class = path_class[vehicle];
2116  if (transits[vehicle_class] != nullptr) continue;
2117  const auto& unary_evaluator =
2118  dimension->GetUnaryTransitEvaluator(vehicle);
2119  if (unary_evaluator != nullptr) {
2120  transits[vehicle_class] = [&unary_evaluator, dimension, num_slacks](
2121  int64_t node, int64_t next) -> Interval {
2122  if (node >= num_slacks) return {0, 0};
2123  const int64_t min_transit = unary_evaluator(node);
2124  const int64_t max_transit =
2125  CapAdd(min_transit, dimension->SlackVar(node)->Max());
2126  return {min_transit, max_transit};
2127  };
2128  } else {
2129  const auto& binary_evaluator =
2130  dimension->GetBinaryTransitEvaluator(vehicle);
2131 
2132  transits[vehicle_class] = [&binary_evaluator, dimension, num_slacks](
2133  int64_t node, int64_t next) -> Interval {
2134  if (node >= num_slacks) return {0, 0};
2135  const int64_t min_transit = binary_evaluator(node, next);
2136  const int64_t max_transit =
2137  CapAdd(min_transit, dimension->SlackVar(node)->Max());
2138  return {min_transit, max_transit};
2139  };
2140  }
2141  }
2142  // Fill node capacities.
2143  std::vector<Interval> node_capacity(num_cumuls);
2144  for (int node = 0; node < num_cumuls; ++node) {
2145  const IntVar* cumul = dimension->CumulVar(node);
2146  node_capacity[node] = {cumul->Min(), cumul->Max()};
2147  }
2148  // Make the dimension checker and pass ownership to the filter.
2149  auto checker = std::make_unique<DimensionChecker>(
2150  path_state, std::move(path_capacity), std::move(path_class),
2151  std::move(transits), std::move(node_capacity));
2152  const auto kAccept = LocalSearchFilterManager::FilterEventType::kAccept;
2154  dimension->model()->solver(), std::move(checker), dimension->name());
2155  filters->push_back({filter, kAccept});
2156  }
2157 }
2158 
2160  const std::vector<RoutingDimension*>& dimensions,
2161  const RoutingSearchParameters& parameters, bool filter_objective_cost,
2162  bool use_chain_cumul_filter,
2163  std::vector<LocalSearchFilterManager::FilterEvent>* filters) {
2164  const auto kAccept = LocalSearchFilterManager::FilterEventType::kAccept;
2165  // Filter priority depth increases with complexity of filtering.
2166  // - Dimensions without any cumul-related costs or constraints will have a
2167  // ChainCumulFilter, lowest priority depth.
2168  // - Dimensions with cumul costs or constraints, but no global span cost
2169  // and/or precedences will have a PathCumulFilter.
2170  // - Dimensions with a global span cost coefficient and/or precedences will
2171  // have a global LP filter.
2172  const int num_dimensions = dimensions.size();
2173 
2174  const bool has_dimension_optimizers =
2175  !parameters.disable_scheduling_beware_this_may_degrade_performance();
2176  std::vector<bool> use_path_cumul_filter(num_dimensions);
2177  std::vector<bool> use_cumul_bounds_propagator_filter(num_dimensions);
2178  std::vector<bool> use_global_lp_filter(num_dimensions);
2179  std::vector<bool> use_resource_assignment_filter(num_dimensions);
2180  for (int d = 0; d < num_dimensions; d++) {
2181  const RoutingDimension& dimension = *dimensions[d];
2182  const bool has_cumul_cost = DimensionHasCumulCost(dimension);
2183  use_path_cumul_filter[d] =
2184  has_cumul_cost || DimensionHasPathCumulConstraint(dimension);
2185 
2186  const int num_dimension_resource_groups =
2187  dimension.model()->GetDimensionResourceGroupIndices(&dimension).size();
2188  const bool can_use_cumul_bounds_propagator_filter =
2189  !dimension.HasBreakConstraints() &&
2190  num_dimension_resource_groups == 0 &&
2191  (!filter_objective_cost || !has_cumul_cost);
2192  const bool has_precedences = !dimension.GetNodePrecedences().empty();
2193  use_global_lp_filter[d] =
2194  has_dimension_optimizers &&
2195  ((has_precedences && !can_use_cumul_bounds_propagator_filter) ||
2196  (filter_objective_cost &&
2197  dimension.global_span_cost_coefficient() > 0) ||
2198  num_dimension_resource_groups > 1);
2199 
2200  use_cumul_bounds_propagator_filter[d] =
2201  has_precedences && !use_global_lp_filter[d];
2202 
2203  use_resource_assignment_filter[d] =
2204  has_dimension_optimizers && num_dimension_resource_groups > 0;
2205  }
2206 
2207  for (int d = 0; d < num_dimensions; d++) {
2208  const RoutingDimension& dimension = *dimensions[d];
2209  const RoutingModel& model = *dimension.model();
2210  // NOTE: We always add the [Chain|Path]CumulFilter to filter each route's
2211  // feasibility separately to try and cut bad decisions earlier in the
2212  // search, but we don't propagate the computed cost if the LPCumulFilter is
2213  // already doing it.
2214  const bool use_global_lp = use_global_lp_filter[d];
2215  const bool filter_resource_assignment = use_resource_assignment_filter[d];
2216  if (use_path_cumul_filter[d]) {
2217  filters->push_back(
2218  {MakePathCumulFilter(dimension, /*propagate_own_objective_value*/
2219  !use_global_lp && !filter_resource_assignment,
2220  filter_objective_cost, has_dimension_optimizers),
2221  kAccept, /*priority*/ 0});
2222  } else if (use_chain_cumul_filter) {
2223  filters->push_back(
2224  {model.solver()->RevAlloc(new ChainCumulFilter(model, dimension)),
2225  kAccept, /*priority*/ 0});
2226  }
2227 
2228  if (use_cumul_bounds_propagator_filter[d]) {
2229  DCHECK(!use_global_lp);
2230  DCHECK(!filter_resource_assignment);
2231  filters->push_back({MakeCumulBoundsPropagatorFilter(dimension), kAccept,
2232  /*priority*/ 1});
2233  }
2234 
2235  if (filter_resource_assignment) {
2236  filters->push_back({MakeResourceAssignmentFilter(
2237  model.GetMutableLocalCumulLPOptimizer(dimension),
2238  model.GetMutableLocalCumulMPOptimizer(dimension),
2239  /*propagate_own_objective_value*/ !use_global_lp,
2240  filter_objective_cost),
2241  kAccept, /*priority*/ 2});
2242  }
2243 
2244  if (use_global_lp) {
2245  filters->push_back({MakeGlobalLPCumulFilter(
2246  model.GetMutableGlobalCumulLPOptimizer(dimension),
2247  model.GetMutableGlobalCumulMPOptimizer(dimension),
2248  filter_objective_cost),
2249  kAccept, /*priority*/ 3});
2250  }
2251  }
2252 }
2253 
2254 namespace {
2255 
2256 // Filter for pickup/delivery precedences.
2257 class PickupDeliveryFilter : public BasePathFilter {
2258  public:
2259  PickupDeliveryFilter(const std::vector<IntVar*>& nexts, int next_domain_size,
2260  const RoutingModel::IndexPairs& pairs,
2261  const std::vector<RoutingModel::PickupAndDeliveryPolicy>&
2262  vehicle_policies);
2263  ~PickupDeliveryFilter() override {}
2264  bool AcceptPath(int64_t path_start, int64_t chain_start,
2265  int64_t chain_end) override;
2266  std::string DebugString() const override { return "PickupDeliveryFilter"; }
2267 
2268  private:
2269  bool AcceptPathDefault(int64_t path_start);
2270  template <bool lifo>
2271  bool AcceptPathOrdered(int64_t path_start);
2272 
2273  std::vector<int> pair_firsts_;
2274  std::vector<int> pair_seconds_;
2275  const RoutingModel::IndexPairs pairs_;
2276  SparseBitset<> visited_;
2277  std::deque<int> visited_deque_;
2278  const std::vector<RoutingModel::PickupAndDeliveryPolicy> vehicle_policies_;
2279 };
2280 
2281 PickupDeliveryFilter::PickupDeliveryFilter(
2282  const std::vector<IntVar*>& nexts, int next_domain_size,
2283  const RoutingModel::IndexPairs& pairs,
2284  const std::vector<RoutingModel::PickupAndDeliveryPolicy>& vehicle_policies)
2285  : BasePathFilter(nexts, next_domain_size),
2286  pair_firsts_(next_domain_size, kUnassigned),
2287  pair_seconds_(next_domain_size, kUnassigned),
2288  pairs_(pairs),
2289  visited_(Size()),
2290  vehicle_policies_(vehicle_policies) {
2291  for (int i = 0; i < pairs.size(); ++i) {
2292  const auto& index_pair = pairs[i];
2293  for (int first : index_pair.first) {
2294  pair_firsts_[first] = i;
2295  }
2296  for (int second : index_pair.second) {
2297  pair_seconds_[second] = i;
2298  }
2299  }
2300 }
2301 
2302 bool PickupDeliveryFilter::AcceptPath(int64_t path_start,
2303  int64_t /*chain_start*/,
2304  int64_t /*chain_end*/) {
2305  switch (vehicle_policies_[GetPath(path_start)]) {
2306  case RoutingModel::PICKUP_AND_DELIVERY_NO_ORDER:
2307  return AcceptPathDefault(path_start);
2308  case RoutingModel::PICKUP_AND_DELIVERY_LIFO:
2309  return AcceptPathOrdered<true>(path_start);
2310  case RoutingModel::PICKUP_AND_DELIVERY_FIFO:
2311  return AcceptPathOrdered<false>(path_start);
2312  default:
2313  return true;
2314  }
2315 }
2316 
2317 bool PickupDeliveryFilter::AcceptPathDefault(int64_t path_start) {
2318  visited_.ClearAll();
2319  int64_t node = path_start;
2320  int64_t path_length = 1;
2321  while (node < Size()) {
2322  // Detect sub-cycles (path is longer than longest possible path).
2323  if (path_length > Size()) {
2324  return false;
2325  }
2326  if (pair_firsts_[node] != kUnassigned) {
2327  // Checking on pair firsts is not actually necessary (inconsistencies
2328  // will get caught when checking pair seconds); doing it anyway to
2329  // cut checks early.
2330  for (int second : pairs_[pair_firsts_[node]].second) {
2331  if (visited_[second]) {
2332  return false;
2333  }
2334  }
2335  }
2336  if (pair_seconds_[node] != kUnassigned) {
2337  bool found_first = false;
2338  bool some_synced = false;
2339  for (int first : pairs_[pair_seconds_[node]].first) {
2340  if (visited_[first]) {
2341  found_first = true;
2342  break;
2343  }
2344  if (IsVarSynced(first)) {
2345  some_synced = true;
2346  }
2347  }
2348  if (!found_first && some_synced) {
2349  return false;
2350  }
2351  }
2352  visited_.Set(node);
2353  const int64_t next = GetNext(node);
2354  if (next == kUnassigned) {
2355  // LNS detected, return true since path was ok up to now.
2356  return true;
2357  }
2358  node = next;
2359  ++path_length;
2360  }
2361  for (const int64_t node : visited_.PositionsSetAtLeastOnce()) {
2362  if (pair_firsts_[node] != kUnassigned) {
2363  bool found_second = false;
2364  bool some_synced = false;
2365  for (int second : pairs_[pair_firsts_[node]].second) {
2366  if (visited_[second]) {
2367  found_second = true;
2368  break;
2369  }
2370  if (IsVarSynced(second)) {
2371  some_synced = true;
2372  }
2373  }
2374  if (!found_second && some_synced) {
2375  return false;
2376  }
2377  }
2378  }
2379  return true;
2380 }
2381 
2382 template <bool lifo>
2383 bool PickupDeliveryFilter::AcceptPathOrdered(int64_t path_start) {
2384  visited_deque_.clear();
2385  int64_t node = path_start;
2386  int64_t path_length = 1;
2387  while (node < Size()) {
2388  // Detect sub-cycles (path is longer than longest possible path).
2389  if (path_length > Size()) {
2390  return false;
2391  }
2392  if (pair_firsts_[node] != kUnassigned) {
2393  if (lifo) {
2394  visited_deque_.push_back(node);
2395  } else {
2396  visited_deque_.push_front(node);
2397  }
2398  }
2399  if (pair_seconds_[node] != kUnassigned) {
2400  bool found_first = false;
2401  bool some_synced = false;
2402  for (int first : pairs_[pair_seconds_[node]].first) {
2403  if (!visited_deque_.empty() && visited_deque_.back() == first) {
2404  found_first = true;
2405  break;
2406  }
2407  if (IsVarSynced(first)) {
2408  some_synced = true;
2409  }
2410  }
2411  if (!found_first && some_synced) {
2412  return false;
2413  } else if (!visited_deque_.empty()) {
2414  visited_deque_.pop_back();
2415  }
2416  }
2417  const int64_t next = GetNext(node);
2418  if (next == kUnassigned) {
2419  // LNS detected, return true since path was ok up to now.
2420  return true;
2421  }
2422  node = next;
2423  ++path_length;
2424  }
2425  while (!visited_deque_.empty()) {
2426  for (int second : pairs_[pair_firsts_[visited_deque_.back()]].second) {
2427  if (IsVarSynced(second)) {
2428  return false;
2429  }
2430  }
2431  visited_deque_.pop_back();
2432  }
2433  return true;
2434 }
2435 
2436 } // namespace
2437 
2439  const RoutingModel& routing_model, const RoutingModel::IndexPairs& pairs,
2440  const std::vector<RoutingModel::PickupAndDeliveryPolicy>&
2441  vehicle_policies) {
2442  return routing_model.solver()->RevAlloc(new PickupDeliveryFilter(
2443  routing_model.Nexts(), routing_model.Size() + routing_model.vehicles(),
2444  pairs, vehicle_policies));
2445 }
2446 
2447 namespace {
2448 
2449 // Vehicle variable filter
2450 class VehicleVarFilter : public BasePathFilter {
2451  public:
2452  explicit VehicleVarFilter(const RoutingModel& routing_model);
2453  ~VehicleVarFilter() override {}
2454  bool AcceptPath(int64_t path_start, int64_t chain_start,
2455  int64_t chain_end) override;
2456  std::string DebugString() const override { return "VehicleVariableFilter"; }
2457 
2458  private:
2459  bool DisableFiltering() const override;
2460  bool IsVehicleVariableConstrained(int index) const;
2461 
2462  std::vector<int64_t> start_to_vehicle_;
2463  std::vector<IntVar*> vehicle_vars_;
2464  const int64_t unconstrained_vehicle_var_domain_size_;
2465 };
2466 
2467 VehicleVarFilter::VehicleVarFilter(const RoutingModel& routing_model)
2468  : BasePathFilter(routing_model.Nexts(),
2469  routing_model.Size() + routing_model.vehicles()),
2470  vehicle_vars_(routing_model.VehicleVars()),
2471  unconstrained_vehicle_var_domain_size_(routing_model.vehicles()) {
2472  start_to_vehicle_.resize(Size(), -1);
2473  for (int i = 0; i < routing_model.vehicles(); ++i) {
2474  start_to_vehicle_[routing_model.Start(i)] = i;
2475  }
2476 }
2477 
2478 bool VehicleVarFilter::AcceptPath(int64_t path_start, int64_t chain_start,
2479  int64_t chain_end) {
2480  const int64_t vehicle = start_to_vehicle_[path_start];
2481  int64_t node = chain_start;
2482  while (node != chain_end) {
2483  if (!vehicle_vars_[node]->Contains(vehicle)) {
2484  return false;
2485  }
2486  node = GetNext(node);
2487  }
2488  return vehicle_vars_[node]->Contains(vehicle);
2489 }
2490 
2491 bool VehicleVarFilter::DisableFiltering() const {
2492  for (int i = 0; i < vehicle_vars_.size(); ++i) {
2493  if (IsVehicleVariableConstrained(i)) return false;
2494  }
2495  return true;
2496 }
2497 
2498 bool VehicleVarFilter::IsVehicleVariableConstrained(int index) const {
2499  const IntVar* const vehicle_var = vehicle_vars_[index];
2500  // If vehicle variable contains -1 (optional node), then we need to
2501  // add it to the "unconstrained" domain. Impact we don't filter mandatory
2502  // nodes made inactive here, but it is covered by other filters.
2503  const int adjusted_unconstrained_vehicle_var_domain_size =
2504  vehicle_var->Min() >= 0 ? unconstrained_vehicle_var_domain_size_
2505  : unconstrained_vehicle_var_domain_size_ + 1;
2506  return vehicle_var->Size() != adjusted_unconstrained_vehicle_var_domain_size;
2507 }
2508 
2509 } // namespace
2510 
2512  const RoutingModel& routing_model) {
2513  return routing_model.solver()->RevAlloc(new VehicleVarFilter(routing_model));
2514 }
2515 
2516 namespace {
2517 
2518 class CumulBoundsPropagatorFilter : public IntVarLocalSearchFilter {
2519  public:
2520  explicit CumulBoundsPropagatorFilter(const RoutingDimension& dimension);
2521  bool Accept(const Assignment* delta, const Assignment* deltadelta,
2522  int64_t objective_min, int64_t objective_max) override;
2523  std::string DebugString() const override {
2524  return "CumulBoundsPropagatorFilter(" + propagator_.dimension().name() +
2525  ")";
2526  }
2527 
2528  private:
2529  CumulBoundsPropagator propagator_;
2530  const int64_t cumul_offset_;
2531  SparseBitset<int64_t> delta_touched_;
2532  std::vector<int64_t> delta_nexts_;
2533 };
2534 
2535 CumulBoundsPropagatorFilter::CumulBoundsPropagatorFilter(
2536  const RoutingDimension& dimension)
2537  : IntVarLocalSearchFilter(dimension.model()->Nexts()),
2538  propagator_(&dimension),
2539  cumul_offset_(dimension.GetGlobalOptimizerOffset()),
2540  delta_touched_(Size()),
2541  delta_nexts_(Size()) {}
2542 
2543 bool CumulBoundsPropagatorFilter::Accept(const Assignment* delta,
2544  const Assignment* /*deltadelta*/,
2545  int64_t /*objective_min*/,
2546  int64_t /*objective_max*/) {
2547  delta_touched_.ClearAll();
2548  for (const IntVarElement& delta_element :
2549  delta->IntVarContainer().elements()) {
2550  int64_t index = -1;
2551  if (FindIndex(delta_element.Var(), &index)) {
2552  if (!delta_element.Bound()) {
2553  // LNS detected
2554  return true;
2555  }
2556  delta_touched_.Set(index);
2557  delta_nexts_[index] = delta_element.Value();
2558  }
2559  }
2560  const auto& next_accessor = [this](int64_t index) {
2561  return delta_touched_[index] ? delta_nexts_[index] : Value(index);
2562  };
2563 
2564  return propagator_.PropagateCumulBounds(next_accessor, cumul_offset_);
2565 }
2566 
2567 } // namespace
2568 
2570  const RoutingDimension& dimension) {
2571  return dimension.model()->solver()->RevAlloc(
2572  new CumulBoundsPropagatorFilter(dimension));
2573 }
2574 
2575 namespace {
2576 
2577 class LPCumulFilter : public IntVarLocalSearchFilter {
2578  public:
2579  LPCumulFilter(const std::vector<IntVar*>& nexts,
2580  GlobalDimensionCumulOptimizer* optimizer,
2581  GlobalDimensionCumulOptimizer* mp_optimizer,
2582  bool filter_objective_cost);
2583  bool Accept(const Assignment* delta, const Assignment* deltadelta,
2584  int64_t objective_min, int64_t objective_max) override;
2585  int64_t GetAcceptedObjectiveValue() const override;
2586  void OnSynchronize(const Assignment* delta) override;
2587  int64_t GetSynchronizedObjectiveValue() const override;
2588  std::string DebugString() const override {
2589  return "LPCumulFilter(" + optimizer_.dimension()->name() + ")";
2590  }
2591 
2592  private:
2593  GlobalDimensionCumulOptimizer& optimizer_;
2594  GlobalDimensionCumulOptimizer& mp_optimizer_;
2595  const bool filter_objective_cost_;
2596  int64_t synchronized_cost_without_transit_;
2597  int64_t delta_cost_without_transit_;
2598  SparseBitset<int64_t> delta_touched_;
2599  std::vector<int64_t> delta_nexts_;
2600 };
2601 
2602 LPCumulFilter::LPCumulFilter(const std::vector<IntVar*>& nexts,
2603  GlobalDimensionCumulOptimizer* optimizer,
2604  GlobalDimensionCumulOptimizer* mp_optimizer,
2605  bool filter_objective_cost)
2606  : IntVarLocalSearchFilter(nexts),
2607  optimizer_(*optimizer),
2608  mp_optimizer_(*mp_optimizer),
2609  filter_objective_cost_(filter_objective_cost),
2610  synchronized_cost_without_transit_(-1),
2611  delta_cost_without_transit_(-1),
2612  delta_touched_(Size()),
2613  delta_nexts_(Size()) {}
2614 
2615 bool LPCumulFilter::Accept(const Assignment* delta,
2616  const Assignment* /*deltadelta*/,
2617  int64_t /*objective_min*/, int64_t objective_max) {
2618  delta_touched_.ClearAll();
2619  for (const IntVarElement& delta_element :
2620  delta->IntVarContainer().elements()) {
2621  int64_t index = -1;
2622  if (FindIndex(delta_element.Var(), &index)) {
2623  if (!delta_element.Bound()) {
2624  // LNS detected
2625  return true;
2626  }
2627  delta_touched_.Set(index);
2628  delta_nexts_[index] = delta_element.Value();
2629  }
2630  }
2631  const auto& next_accessor = [this](int64_t index) {
2632  return delta_touched_[index] ? delta_nexts_[index] : Value(index);
2633  };
2634 
2635  if (!filter_objective_cost_) {
2636  // No need to compute the cost of the LP, only verify its feasibility.
2637  delta_cost_without_transit_ = 0;
2639  optimizer_.ComputeCumuls(next_accessor, {}, nullptr, nullptr, nullptr);
2640  if (status == DimensionSchedulingStatus::OPTIMAL) return true;
2641  if (status == DimensionSchedulingStatus::RELAXED_OPTIMAL_ONLY &&
2642  mp_optimizer_.ComputeCumuls(next_accessor, {}, nullptr, nullptr,
2643  nullptr) ==
2645  return true;
2646  }
2647  return false;
2648  }
2649 
2651  optimizer_.ComputeCumulCostWithoutFixedTransits(
2652  next_accessor, &delta_cost_without_transit_);
2654  delta_cost_without_transit_ = std::numeric_limits<int64_t>::max();
2655  return false;
2656  }
2657  if (delta_cost_without_transit_ > objective_max) return false;
2658 
2659  if (status == DimensionSchedulingStatus::RELAXED_OPTIMAL_ONLY &&
2660  mp_optimizer_.ComputeCumulCostWithoutFixedTransits(
2661  next_accessor, &delta_cost_without_transit_) !=
2663  delta_cost_without_transit_ = std::numeric_limits<int64_t>::max();
2664  return false;
2665  }
2666  return delta_cost_without_transit_ <= objective_max;
2667 }
2668 
2669 int64_t LPCumulFilter::GetAcceptedObjectiveValue() const {
2670  return delta_cost_without_transit_;
2671 }
2672 
2673 void LPCumulFilter::OnSynchronize(const Assignment* /*delta*/) {
2674  // TODO(user): Try to optimize this so the LP is not called when the last
2675  // computed delta cost corresponds to the solution being synchronized.
2676  const RoutingModel& model = *optimizer_.dimension()->model();
2677  const auto& next_accessor = [this, &model](int64_t index) {
2678  return IsVarSynced(index) ? Value(index)
2679  : model.IsStart(index) ? model.End(model.VehicleIndex(index))
2680  : index;
2681  };
2682 
2684  optimizer_.ComputeCumulCostWithoutFixedTransits(
2685  next_accessor, &synchronized_cost_without_transit_);
2687  // TODO(user): This should only happen if the LP solver times out.
2688  // DCHECK the fail wasn't due to an infeasible model.
2689  synchronized_cost_without_transit_ = 0;
2690  }
2691  if (status == DimensionSchedulingStatus::RELAXED_OPTIMAL_ONLY &&
2692  mp_optimizer_.ComputeCumulCostWithoutFixedTransits(
2693  next_accessor, &synchronized_cost_without_transit_) !=
2695  // TODO(user): This should only happen if the MP solver times out.
2696  // DCHECK the fail wasn't due to an infeasible model.
2697  synchronized_cost_without_transit_ = 0;
2698  }
2699 }
2700 
2701 int64_t LPCumulFilter::GetSynchronizedObjectiveValue() const {
2702  return synchronized_cost_without_transit_;
2703 }
2704 
2705 } // namespace
2706 
2708  GlobalDimensionCumulOptimizer* optimizer,
2709  GlobalDimensionCumulOptimizer* mp_optimizer, bool filter_objective_cost) {
2710  DCHECK_NE(optimizer, nullptr);
2711  DCHECK_NE(mp_optimizer, nullptr);
2712  const RoutingModel& model = *optimizer->dimension()->model();
2713  return model.solver()->RevAlloc(new LPCumulFilter(
2714  model.Nexts(), optimizer, mp_optimizer, filter_objective_cost));
2715 }
2716 
2717 namespace {
2718 
2719 using ResourceGroup = RoutingModel::ResourceGroup;
2720 
2721 class ResourceGroupAssignmentFilter : public BasePathFilter {
2722  public:
2723  ResourceGroupAssignmentFilter(const std::vector<IntVar*>& nexts,
2724  const ResourceGroup* resource_group,
2725  LocalDimensionCumulOptimizer* lp_optimizer,
2726  LocalDimensionCumulOptimizer* mp_optimizer,
2727  bool filter_objective_cost);
2728  bool InitializeAcceptPath() override;
2729  bool AcceptPath(int64_t path_start, int64_t chain_start,
2730  int64_t chain_end) override;
2731  bool FinalizeAcceptPath(int64_t objective_min,
2732  int64_t objective_max) override;
2733  void OnBeforeSynchronizePaths() override;
2734  void OnSynchronizePathFromStart(int64_t start) override;
2735  void OnAfterSynchronizePaths() override;
2736 
2737  int64_t GetAcceptedObjectiveValue() const override {
2738  return lns_detected() ? 0 : delta_cost_without_transit_;
2739  }
2740  int64_t GetSynchronizedObjectiveValue() const override {
2741  return synchronized_cost_without_transit_;
2742  }
2743  std::string DebugString() const override {
2744  return "ResourceGroupAssignmentFilter(" + dimension_.name() + ")";
2745  }
2746 
2747  private:
2748  const RoutingModel& model_;
2749  const RoutingDimension& dimension_;
2750  const ResourceGroup& resource_group_;
2751  LocalDimensionCumulOptimizer* lp_optimizer_;
2752  LocalDimensionCumulOptimizer* mp_optimizer_;
2753  const bool filter_objective_cost_;
2754  bool current_synch_failed_;
2755  int64_t synchronized_cost_without_transit_;
2756  int64_t delta_cost_without_transit_;
2757  std::vector<std::vector<int64_t>> vehicle_to_resource_assignment_costs_;
2758  std::vector<std::vector<int64_t>> delta_vehicle_to_resource_assignment_costs_;
2759 };
2760 
2761 ResourceGroupAssignmentFilter::ResourceGroupAssignmentFilter(
2762  const std::vector<IntVar*>& nexts, const ResourceGroup* resource_group,
2763  LocalDimensionCumulOptimizer* lp_optimizer,
2764  LocalDimensionCumulOptimizer* mp_optimizer, bool filter_objective_cost)
2765  : BasePathFilter(nexts, lp_optimizer->dimension()->cumuls().size()),
2766  model_(*lp_optimizer->dimension()->model()),
2767  dimension_(*lp_optimizer->dimension()),
2768  resource_group_(*resource_group),
2769  lp_optimizer_(lp_optimizer),
2770  mp_optimizer_(mp_optimizer),
2771  filter_objective_cost_(filter_objective_cost),
2772  current_synch_failed_(false),
2773  synchronized_cost_without_transit_(-1),
2774  delta_cost_without_transit_(-1) {
2775  vehicle_to_resource_assignment_costs_.resize(model_.vehicles());
2776  delta_vehicle_to_resource_assignment_costs_.resize(model_.vehicles());
2777 }
2778 
2779 bool ResourceGroupAssignmentFilter::InitializeAcceptPath() {
2780  delta_vehicle_to_resource_assignment_costs_.assign(model_.vehicles(), {});
2781  // TODO(user): Keep track of num_used_vehicles internally and compute its
2782  // new value here by only going through the touched_paths_.
2783  int num_used_vehicles = 0;
2784  const int num_resources = resource_group_.Size();
2785  for (int v : resource_group_.GetVehiclesRequiringAResource()) {
2786  if (GetNext(model_.Start(v)) != model_.End(v) ||
2787  model_.IsVehicleUsedWhenEmpty(v)) {
2788  if (++num_used_vehicles > num_resources) {
2789  return false;
2790  }
2791  }
2792  }
2793  return true;
2794 }
2795 
2796 bool ResourceGroupAssignmentFilter::AcceptPath(int64_t path_start,
2797  int64_t /*chain_start*/,
2798  int64_t /*chain_end*/) {
2799  const int vehicle = model_.VehicleIndex(path_start);
2801  vehicle, resource_group_,
2802  [this](int64_t index) { return GetNext(index); },
2803  dimension_.transit_evaluator(vehicle), filter_objective_cost_,
2804  lp_optimizer_, mp_optimizer_,
2805  &delta_vehicle_to_resource_assignment_costs_[vehicle], nullptr, nullptr);
2806 }
2807 
2808 bool ResourceGroupAssignmentFilter::FinalizeAcceptPath(
2809  int64_t /*objective_min*/, int64_t objective_max) {
2810  delta_cost_without_transit_ = ComputeBestVehicleToResourceAssignment(
2811  resource_group_.GetVehiclesRequiringAResource(), resource_group_.Size(),
2812  /*vehicle_to_resource_assignment_costs=*/
2813  [this](int v) {
2814  return PathStartTouched(model_.Start(v))
2815  ? &delta_vehicle_to_resource_assignment_costs_[v]
2816  : &vehicle_to_resource_assignment_costs_[v];
2817  },
2818  nullptr);
2819  return delta_cost_without_transit_ >= 0 &&
2820  delta_cost_without_transit_ <= objective_max;
2821 }
2822 
2823 void ResourceGroupAssignmentFilter::OnBeforeSynchronizePaths() {
2824  current_synch_failed_ = false;
2825 }
2826 
2827 void ResourceGroupAssignmentFilter::OnSynchronizePathFromStart(int64_t start) {
2828  // NOTE(user): Even if filter_objective_cost_ is false, we still need to
2829  // call ComputeVehicleToResourcesAssignmentCosts() for every vehicle to keep
2830  // track of whether or not a given vehicle-to-resource assignment is possible
2831  // by storing 0 or -1 in vehicle_to_resource_assignment_costs_.
2832  const auto& next_accessor = [this](int64_t index) {
2833  return IsVarSynced(index) ? Value(index)
2834  : model_.IsStart(index) ? model_.End(model_.VehicleIndex(index))
2835  : index;
2836  };
2837  const int v = model_.VehicleIndex(start);
2839  v, resource_group_, next_accessor, dimension_.transit_evaluator(v),
2840  filter_objective_cost_, lp_optimizer_, mp_optimizer_,
2841  &vehicle_to_resource_assignment_costs_[v], nullptr, nullptr)) {
2842  vehicle_to_resource_assignment_costs_[v].assign(resource_group_.Size(), -1);
2843  current_synch_failed_ = true;
2844  }
2845 }
2846 
2847 void ResourceGroupAssignmentFilter::OnAfterSynchronizePaths() {
2848  synchronized_cost_without_transit_ =
2849  (current_synch_failed_ || !filter_objective_cost_)
2850  ? 0
2852  resource_group_.GetVehiclesRequiringAResource(),
2853  resource_group_.Size(),
2854  [this](int v) {
2855  return &vehicle_to_resource_assignment_costs_[v];
2856  },
2857  nullptr);
2858  synchronized_cost_without_transit_ =
2859  std::max<int64_t>(synchronized_cost_without_transit_, 0);
2860 }
2861 
2862 // ResourceAssignmentFilter
2863 class ResourceAssignmentFilter : public LocalSearchFilter {
2864  public:
2865  ResourceAssignmentFilter(const std::vector<IntVar*>& nexts,
2866  LocalDimensionCumulOptimizer* optimizer,
2867  LocalDimensionCumulOptimizer* mp_optimizer,
2868  bool propagate_own_objective_value,
2869  bool filter_objective_cost);
2870  bool Accept(const Assignment* delta, const Assignment* deltadelta,
2871  int64_t objective_min, int64_t objective_max) override;
2872  void Synchronize(const Assignment* assignment,
2873  const Assignment* delta) override;
2874 
2875  int64_t GetAcceptedObjectiveValue() const override {
2876  return propagate_own_objective_value_ ? delta_cost_ : 0;
2877  }
2878  int64_t GetSynchronizedObjectiveValue() const override {
2879  return propagate_own_objective_value_ ? synchronized_cost_ : 0;
2880  }
2881  std::string DebugString() const override {
2882  return "ResourceAssignmentFilter(" + dimension_name_ + ")";
2883  }
2884 
2885  private:
2886  std::vector<IntVarLocalSearchFilter*> resource_group_assignment_filters_;
2887  int64_t synchronized_cost_;
2888  int64_t delta_cost_;
2889  const bool propagate_own_objective_value_;
2890  const std::string dimension_name_;
2891 };
2892 
2893 ResourceAssignmentFilter::ResourceAssignmentFilter(
2894  const std::vector<IntVar*>& nexts, LocalDimensionCumulOptimizer* optimizer,
2895  LocalDimensionCumulOptimizer* mp_optimizer,
2896  bool propagate_own_objective_value, bool filter_objective_cost)
2897  : propagate_own_objective_value_(propagate_own_objective_value),
2898  dimension_name_(optimizer->dimension()->name()) {
2899  const RoutingModel& model = *optimizer->dimension()->model();
2900  for (const auto& resource_group : model.GetResourceGroups()) {
2901  resource_group_assignment_filters_.push_back(
2902  model.solver()->RevAlloc(new ResourceGroupAssignmentFilter(
2903  nexts, resource_group.get(), optimizer, mp_optimizer,
2904  filter_objective_cost)));
2905  }
2906 }
2907 
2908 bool ResourceAssignmentFilter::Accept(const Assignment* delta,
2909  const Assignment* deltadelta,
2910  int64_t objective_min,
2911  int64_t objective_max) {
2912  delta_cost_ = 0;
2913  for (LocalSearchFilter* group_filter : resource_group_assignment_filters_) {
2914  if (!group_filter->Accept(delta, deltadelta, objective_min,
2915  objective_max)) {
2916  return false;
2917  }
2918  delta_cost_ =
2919  std::max(delta_cost_, group_filter->GetAcceptedObjectiveValue());
2920  DCHECK_LE(delta_cost_, objective_max)
2921  << "ResourceGroupAssignmentFilter should return false when the "
2922  "objective_max is exceeded.";
2923  }
2924  return true;
2925 }
2926 
2927 void ResourceAssignmentFilter::Synchronize(const Assignment* assignment,
2928  const Assignment* delta) {
2929  synchronized_cost_ = 0;
2930  for (LocalSearchFilter* group_filter : resource_group_assignment_filters_) {
2931  group_filter->Synchronize(assignment, delta);
2932  synchronized_cost_ = std::max(
2933  synchronized_cost_, group_filter->GetSynchronizedObjectiveValue());
2934  }
2935 }
2936 
2937 } // namespace
2938 
2940  LocalDimensionCumulOptimizer* optimizer,
2941  LocalDimensionCumulOptimizer* mp_optimizer,
2942  bool propagate_own_objective_value, bool filter_objective_cost) {
2943  const RoutingModel& model = *optimizer->dimension()->model();
2944  DCHECK_NE(optimizer, nullptr);
2945  DCHECK_NE(mp_optimizer, nullptr);
2946  return model.solver()->RevAlloc(new ResourceAssignmentFilter(
2947  model.Nexts(), optimizer, mp_optimizer, propagate_own_objective_value,
2948  filter_objective_cost));
2949 }
2950 
2951 namespace {
2952 
2953 // This filter accepts deltas for which the assignment satisfies the
2954 // constraints of the Solver. This is verified by keeping an internal copy of
2955 // the assignment with all Next vars and their updated values, and calling
2956 // RestoreAssignment() on the assignment+delta.
2957 // TODO(user): Also call the solution finalizer on variables, with the
2958 // exception of Next Vars (woud fail on large instances).
2959 // WARNING: In the case of mandatory nodes, when all vehicles are currently
2960 // being used in the solution but uninserted nodes still remain, this filter
2961 // will reject the solution, even if the node could be inserted on one of these
2962 // routes, because all Next vars of vehicle starts are already instantiated.
2963 // TODO(user): Avoid such false negatives.
2964 
2965 class CPFeasibilityFilter : public IntVarLocalSearchFilter {
2966  public:
2967  explicit CPFeasibilityFilter(RoutingModel* routing_model);
2968  ~CPFeasibilityFilter() override {}
2969  std::string DebugString() const override { return "CPFeasibilityFilter"; }
2970  bool Accept(const Assignment* delta, const Assignment* deltadelta,
2971  int64_t objective_min, int64_t objective_max) override;
2972  void OnSynchronize(const Assignment* delta) override;
2973 
2974  private:
2975  void AddDeltaToAssignment(const Assignment* delta, Assignment* assignment);
2976 
2977  static const int64_t kUnassigned;
2978  const RoutingModel* const model_;
2979  Solver* const solver_;
2980  Assignment* const assignment_;
2981  Assignment* const temp_assignment_;
2982  DecisionBuilder* const restore_;
2983  SearchLimit* const limit_;
2984 };
2985 
2986 const int64_t CPFeasibilityFilter::kUnassigned = -1;
2987 
2988 CPFeasibilityFilter::CPFeasibilityFilter(RoutingModel* routing_model)
2989  : IntVarLocalSearchFilter(routing_model->Nexts()),
2990  model_(routing_model),
2991  solver_(routing_model->solver()),
2992  assignment_(solver_->MakeAssignment()),
2993  temp_assignment_(solver_->MakeAssignment()),
2994  restore_(solver_->MakeRestoreAssignment(temp_assignment_)),
2995  limit_(solver_->MakeCustomLimit(
2996  [routing_model]() { return routing_model->CheckLimit(); })) {
2997  assignment_->Add(routing_model->Nexts());
2998 }
2999 
3000 bool CPFeasibilityFilter::Accept(const Assignment* delta,
3001  const Assignment* /*deltadelta*/,
3002  int64_t /*objective_min*/,
3003  int64_t /*objective_max*/) {
3004  temp_assignment_->Copy(assignment_);
3005  AddDeltaToAssignment(delta, temp_assignment_);
3006 
3007  return solver_->Solve(restore_, limit_);
3008 }
3009 
3010 void CPFeasibilityFilter::OnSynchronize(const Assignment* delta) {
3011  AddDeltaToAssignment(delta, assignment_);
3012 }
3013 
3014 void CPFeasibilityFilter::AddDeltaToAssignment(const Assignment* delta,
3015  Assignment* assignment) {
3016  if (delta == nullptr) {
3017  return;
3018  }
3019  Assignment::IntContainer* const container =
3020  assignment->MutableIntVarContainer();
3021  const Assignment::IntContainer& delta_container = delta->IntVarContainer();
3022  const int delta_size = delta_container.Size();
3023 
3024  for (int i = 0; i < delta_size; i++) {
3025  const IntVarElement& delta_element = delta_container.Element(i);
3026  IntVar* const var = delta_element.Var();
3027  int64_t index = kUnassigned;
3028  // Ignoring variables found in the delta which are not next variables, such
3029  // as vehicle variables.
3030  if (!FindIndex(var, &index)) continue;
3031  DCHECK_EQ(var, Var(index));
3032  const int64_t value = delta_element.Value();
3033 
3034  container->AddAtPosition(var, index)->SetValue(value);
3035  if (model_->IsStart(index)) {
3036  if (model_->IsEnd(value)) {
3037  // Do not restore unused routes.
3038  container->MutableElement(index)->Deactivate();
3039  } else {
3040  // Re-activate the route's start in case it was deactivated before.
3041  container->MutableElement(index)->Activate();
3042  }
3043  }
3044  }
3045 }
3046 
3047 } // namespace
3048 
3050  return routing_model->solver()->RevAlloc(
3051  new CPFeasibilityFilter(routing_model));
3052 }
3053 
3054 // TODO(user): Implement same-vehicle filter. Could be merged with node
3055 // precedence filter.
3056 
3057 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
std::vector< int > dimensions
const E & Element(const V *const var) const
An Assignment is a variable -> domains mapping, used to report solutions to the user.
AssignmentContainer< IntVar, IntVarElement > IntContainer
bool Accept(const Assignment *delta, const Assignment *deltadelta, int64_t objective_min, int64_t objective_max) override
Accepts a "delta" given the assignment with which the filter has been synchronized; the delta holds t...
BasePathFilter(const std::vector< IntVar * > &nexts, int next_domain_size)
void OnSynchronize(const Assignment *delta) override
virtual int64_t Min() const =0
virtual int64_t Max() const =0
The class IntVar is a subset of IntExpr.
bool FindIndex(IntVar *const var, int64_t *index) const
Local Search Filters are used for fast neighbor pruning.
Dimensions represent quantities accumulated at nodes along the routes.
Definition: routing.h:2750
RoutingModel * model() const
Returns the model on which the dimension was created.
Definition: routing.h:2754
int64_t global_span_cost_coefficient() const
Definition: routing.h:3090
bool HasBreakConstraints() const
Returns true if any break interval or break distance was defined.
Definition: routing.cc:7458
const std::vector< NodePrecedence > & GetNodePrecedences() const
Definition: routing.h:3056
const std::vector< int > & GetDimensionResourceGroupIndices(const RoutingDimension *dimension) const
Returns the indices of resource groups for this dimension.
Definition: routing.cc:1775
int64_t Size() const
Returns the number of next variables in the model.
Definition: routing.h:1654
Solver * solver() const
Returns the underlying constraint solver.
Definition: routing.h:1630
RoutingIndexPairs IndexPairs
Definition: routing.h:287
int vehicles() const
Returns the number of vehicle routes in the model.
Definition: routing.h:1652
const std::vector< IntVar * > & Nexts() const
Returns all next variables of the model, such that Nexts(i) is the next variable of the node correspo...
Definition: routing.h:1472
RoutingDisjunctionIndex DisjunctionIndex
Definition: routing.h:279
T * RevAlloc(T *object)
Registers the given object as being reversible.
const std::vector< IntegerType > & PositionsSetAtLeastOnce() const
Definition: bitset.h:806
void Set(IntegerType index)
Definition: bitset.h:792
Block * next
SatParameters parameters
const std::string name
int64_t value
IntVar * var
Definition: expr_array.cc:1874
const int64_t limit_
absl::Status status
Definition: g_gurobi.cc:41
const std::vector< IntVar * > cumuls_
GRBmodel * model
int index
Collection::value_type::second_type & LookupOrInsert(Collection *const collection, const typename Collection::value_type::first_type &key, const typename Collection::value_type::second_type &value)
Definition: map_util.h:237
const Collection::value_type::second_type & FindWithDefault(const Collection &collection, const typename Collection::value_type::first_type &key, const typename Collection::value_type::second_type &value)
Definition: map_util.h:29
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)
IntVarLocalSearchFilter * MakeCumulBoundsPropagatorFilter(const RoutingDimension &dimension)
Returns a filter handling dimension cumul bounds.
int64_t ComputeBestVehicleToResourceAssignment(std::vector< int > vehicles, int num_resources, std::function< const std::vector< int64_t > *(int)> vehicle_to_resource_assignment_costs, std::vector< int > *resource_indices)
int64_t CapSub(int64_t x, int64_t y)
IntVarLocalSearchFilter * MakeVehicleAmortizedCostFilter(const RoutingModel &routing_model)
Returns a filter computing vehicle amortized costs.
IntVarLocalSearchFilter * MakeCPFeasibilityFilter(RoutingModel *routing_model)
Returns a filter checking the current solution using CP propagation.
LocalSearchFilter * MakeResourceAssignmentFilter(LocalDimensionCumulOptimizer *optimizer, LocalDimensionCumulOptimizer *mp_optimizer, bool propagate_own_objective_value, bool filter_objective_cost)
Returns a filter checking the feasibility and cost of the resource assignment.
IntVarLocalSearchFilter * MakeGlobalLPCumulFilter(GlobalDimensionCumulOptimizer *optimizer, GlobalDimensionCumulOptimizer *mp_optimizer, bool filter_objective_cost)
Returns a filter checking global linear constraints and costs.
LocalSearchFilter * MakeDimensionFilter(Solver *solver, std::unique_ptr< DimensionChecker > checker, const std::string &dimension_name)
IntVarLocalSearchFilter * MakeMaxActiveVehiclesFilter(const RoutingModel &routing_model)
Returns a filter ensuring that max active vehicles constraints are enforced.
int64_t CapProd(int64_t x, int64_t y)
void AppendDimensionCumulFilters(const std::vector< RoutingDimension * > &dimensions, const RoutingSearchParameters &parameters, bool filter_objective_cost, bool use_chain_cumul_filter, std::vector< LocalSearchFilterManager::FilterEvent > *filters)
IntVarLocalSearchFilter * MakeVehicleVarFilter(const RoutingModel &routing_model)
Returns a filter checking that vehicle variable domains are respected.
IntVarLocalSearchFilter * MakePathCumulFilter(const RoutingDimension &dimension, bool propagate_own_objective_value, bool filter_objective_cost, bool can_use_lp)
Returns a filter handling dimension costs and constraints.
IntVarLocalSearchFilter * MakePickupDeliveryFilter(const RoutingModel &routing_model, const RoutingModel::IndexPairs &pairs, const std::vector< RoutingModel::PickupAndDeliveryPolicy > &vehicle_policies)
Returns a filter enforcing pickup and delivery constraints for the given pair of nodes and given poli...
IntVarLocalSearchFilter * MakeTypeRegulationsFilter(const RoutingModel &routing_model)
Returns a filter ensuring type regulation constraints are enforced.
static const int kUnassigned
Definition: routing.cc:1131
void AppendLightWeightDimensionFilters(const PathState *path_state, const std::vector< RoutingDimension * > &dimensions, std::vector< LocalSearchFilterManager::FilterEvent > *filters)
Appends dimension-based filters to the given list of filters using a path state.
IntVarLocalSearchFilter * MakeNodeDisjunctionFilter(const RoutingModel &routing_model, bool filter_cost)
Returns a filter ensuring that node disjunction constraints are enforced.
bool ComputeVehicleToResourcesAssignmentCosts(int v, const RoutingModel::ResourceGroup &resource_group, const std::function< int64_t(int64_t)> &next_accessor, const std::function< int64_t(int64_t, int64_t)> &transit_accessor, bool optimize_vehicle_costs, LocalDimensionCumulOptimizer *lp_optimizer, LocalDimensionCumulOptimizer *mp_optimizer, std::vector< int64_t > *assignment_costs, std::vector< std::vector< int64_t >> *cumul_values, std::vector< std::vector< int64_t >> *break_values)
int64_t delta
Definition: resource.cc:1695
IntVar * upper_bound
Definition: routing.cc:1087
int cumul_value_support
int64_t bound
int64_t cumul_value
ABSL_FLAG(bool, routing_strong_debug_checks, false, "Run stronger checks in debug; these stronger tests might change " "the complexity of the code in particular.")
int64_t coefficient
std::vector< int64_t > path_values
int64_t capacity
int64_t cost
int vehicle_class
std::optional< int64_t > end
int64_t start