Google OR-Tools v9.9
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
routing_filters.cc
Go to the documentation of this file.
1// Copyright 2010-2024 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14// Implementation of 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 <limits>
25#include <memory>
26#include <set>
27#include <string>
28#include <utility>
29#include <vector>
30
31#include "absl/algorithm/container.h"
32#include "absl/container/btree_set.h"
33#include "absl/container/flat_hash_map.h"
34#include "absl/container/flat_hash_set.h"
35#include "absl/flags/flag.h"
36#include "absl/log/check.h"
37#include "absl/strings/str_cat.h"
38#include "absl/strings/string_view.h"
39#include "absl/types/span.h"
43#include "ortools/base/types.h"
48#include "ortools/constraint_solver/routing_parameters.pb.h"
50#include "ortools/util/bitset.h"
53
54ABSL_FLAG(bool, routing_strong_debug_checks, false,
55 "Run stronger checks in debug; these stronger tests might change "
56 "the complexity of the code in particular.");
57
58namespace operations_research {
59
60namespace {
61
62// Max active vehicles filter.
63
64class MaxActiveVehiclesFilter : public IntVarLocalSearchFilter {
65 public:
66 explicit MaxActiveVehiclesFilter(const RoutingModel& routing_model)
67 : IntVarLocalSearchFilter(routing_model.Nexts()),
68 routing_model_(routing_model),
69 is_active_(routing_model.vehicles(), false),
70 active_vehicles_(0) {}
71 bool Accept(const Assignment* delta, const Assignment* /*deltadelta*/,
72 int64_t /*objective_min*/, int64_t /*objective_max*/) override {
73 const int64_t kUnassigned = -1;
74 const Assignment::IntContainer& container = delta->IntVarContainer();
75 int current_active_vehicles = active_vehicles_;
76 for (const IntVarElement& new_element : container.elements()) {
77 IntVar* const var = new_element.Var();
78 int64_t index = kUnassigned;
79 if (FindIndex(var, &index) && routing_model_.IsStart(index)) {
80 if (new_element.Min() != new_element.Max()) {
81 // LNS detected.
82 return true;
83 }
84 const int vehicle = routing_model_.VehicleIndex(index);
85 const bool is_active =
86 (new_element.Min() != routing_model_.End(vehicle));
87 if (is_active && !is_active_[vehicle]) {
88 ++current_active_vehicles;
89 } else if (!is_active && is_active_[vehicle]) {
90 --current_active_vehicles;
91 }
92 }
93 }
94 return current_active_vehicles <=
95 routing_model_.GetMaximumNumberOfActiveVehicles();
96 }
97
98 private:
99 void OnSynchronize(const Assignment* /*delta*/) override {
100 active_vehicles_ = 0;
101 for (int i = 0; i < routing_model_.vehicles(); ++i) {
102 const int index = routing_model_.Start(i);
103 if (IsVarSynced(index) && Value(index) != routing_model_.End(i)) {
104 is_active_[i] = true;
105 ++active_vehicles_;
106 } else {
107 is_active_[i] = false;
108 }
109 }
110 }
111
112 const RoutingModel& routing_model_;
113 std::vector<bool> is_active_;
114 int active_vehicles_;
115};
116} // namespace
117
119 const RoutingModel& routing_model) {
120 return routing_model.solver()->RevAlloc(
121 new MaxActiveVehiclesFilter(routing_model));
122}
123
124namespace {
125
126// Node disjunction filter class.
127class NodeDisjunctionFilter : public IntVarLocalSearchFilter {
128 public:
129 explicit NodeDisjunctionFilter(const RoutingModel& routing_model,
130 bool filter_cost)
131 : IntVarLocalSearchFilter(routing_model.Nexts()),
132 routing_model_(routing_model),
133 active_per_disjunction_(routing_model.GetNumberOfDisjunctions(), 0),
134 inactive_per_disjunction_(routing_model.GetNumberOfDisjunctions(), 0),
135 synchronized_objective_value_(std::numeric_limits<int64_t>::min()),
136 accepted_objective_value_(std::numeric_limits<int64_t>::min()),
137 filter_cost_(filter_cost),
138 has_mandatory_disjunctions_(routing_model.HasMandatoryDisjunctions()) {}
139
140 bool Accept(const Assignment* delta, const Assignment* /*deltadelta*/,
141 int64_t /*objective_min*/, int64_t objective_max) override {
142 const int64_t kUnassigned = -1;
143 const Assignment::IntContainer& container = delta->IntVarContainer();
145 disjunction_active_deltas;
147 disjunction_inactive_deltas;
148 bool lns_detected = false;
149 // Update active/inactive count per disjunction for each element of delta.
150 for (const IntVarElement& new_element : container.elements()) {
151 IntVar* const var = new_element.Var();
152 int64_t index = kUnassigned;
153 if (FindIndex(var, &index)) {
154 const bool is_inactive =
155 (new_element.Min() <= index && new_element.Max() >= index);
156 if (new_element.Min() != new_element.Max()) {
157 lns_detected = true;
158 }
159 for (const RoutingModel::DisjunctionIndex disjunction_index :
160 routing_model_.GetDisjunctionIndices(index)) {
161 const bool is_var_synced = IsVarSynced(index);
162 if (!is_var_synced || (Value(index) == index) != is_inactive) {
163 ++gtl::LookupOrInsert(is_inactive ? &disjunction_inactive_deltas
164 : &disjunction_active_deltas,
165 disjunction_index, 0);
166 if (is_var_synced) {
167 --gtl::LookupOrInsert(is_inactive ? &disjunction_active_deltas
168 : &disjunction_inactive_deltas,
169 disjunction_index, 0);
170 }
171 }
172 }
173 }
174 }
175 // Check if any disjunction has too many active nodes.
176 for (const auto [disjunction_index, active_nodes] :
177 disjunction_active_deltas) {
178 // Too many active nodes.
179 if (active_per_disjunction_[disjunction_index] + active_nodes >
180 routing_model_.GetDisjunctionMaxCardinality(disjunction_index)) {
181 return false;
182 }
183 }
184 if (lns_detected || (!filter_cost_ && !has_mandatory_disjunctions_)) {
185 accepted_objective_value_ = 0;
186 return true;
187 }
188 // Update penalty costs for disjunctions.
189 accepted_objective_value_ = synchronized_objective_value_;
190 for (const auto [disjunction_index, inactive_nodes] :
191 disjunction_inactive_deltas) {
192 const int64_t penalty =
193 routing_model_.GetDisjunctionPenalty(disjunction_index);
194 if (penalty == 0) continue;
195 const int current_inactive_nodes =
196 inactive_per_disjunction_[disjunction_index];
197 const int max_inactive_cardinality =
198 routing_model_.GetDisjunctionNodeIndices(disjunction_index).size() -
199 routing_model_.GetDisjunctionMaxCardinality(disjunction_index);
200 // Too many inactive nodes.
201 if (current_inactive_nodes + inactive_nodes > max_inactive_cardinality) {
202 if (penalty < 0) {
203 // Nodes are mandatory, i.e. exactly max_cardinality nodes must be
204 // performed, so the move is not acceptable.
205 return false;
206 } else if (current_inactive_nodes <= max_inactive_cardinality) {
207 // Add penalty if there were not too many inactive nodes before the
208 // move.
209 accepted_objective_value_ =
210 CapAdd(accepted_objective_value_, penalty);
211 }
212 } else if (current_inactive_nodes > max_inactive_cardinality) {
213 // Remove penalty if there were too many inactive nodes before the
214 // move and there are not too many after the move.
215 accepted_objective_value_ = CapSub(accepted_objective_value_, penalty);
216 }
217 }
218 // Only compare to max as a cost lower bound is computed.
219 return accepted_objective_value_ <= objective_max;
220 }
221 std::string DebugString() const override { return "NodeDisjunctionFilter"; }
222 int64_t GetSynchronizedObjectiveValue() const override {
223 return synchronized_objective_value_;
224 }
225 int64_t GetAcceptedObjectiveValue() const override {
226 return accepted_objective_value_;
227 }
228
229 private:
230 void OnSynchronize(const Assignment* /*delta*/) override {
231 synchronized_objective_value_ = 0;
232 for (RoutingModel::DisjunctionIndex i(0);
233 i < active_per_disjunction_.size(); ++i) {
234 active_per_disjunction_[i] = 0;
235 inactive_per_disjunction_[i] = 0;
236 const std::vector<int64_t>& disjunction_indices =
237 routing_model_.GetDisjunctionNodeIndices(i);
238 for (const int64_t index : disjunction_indices) {
239 if (IsVarSynced(index)) {
240 if (Value(index) != index) {
241 ++active_per_disjunction_[i];
242 } else {
243 ++inactive_per_disjunction_[i];
244 }
245 }
246 }
247 if (!filter_cost_) continue;
248 const int64_t penalty = routing_model_.GetDisjunctionPenalty(i);
249 const int max_cardinality =
250 routing_model_.GetDisjunctionMaxCardinality(i);
251 if (inactive_per_disjunction_[i] >
252 disjunction_indices.size() - max_cardinality &&
253 penalty > 0) {
254 synchronized_objective_value_ =
255 CapAdd(synchronized_objective_value_, penalty);
256 }
257 }
258 }
259
260 const RoutingModel& routing_model_;
261
263 active_per_disjunction_;
265 inactive_per_disjunction_;
266 int64_t synchronized_objective_value_;
267 int64_t accepted_objective_value_;
268 const bool filter_cost_;
269 const bool has_mandatory_disjunctions_;
270};
271} // namespace
272
274 const RoutingModel& routing_model, bool filter_cost) {
275 return routing_model.solver()->RevAlloc(
276 new NodeDisjunctionFilter(routing_model, filter_cost));
277}
278
279const int64_t BasePathFilter::kUnassigned = -1;
280
281BasePathFilter::BasePathFilter(const std::vector<IntVar*>& nexts,
282 int next_domain_size)
284 node_path_starts_(next_domain_size, kUnassigned),
285 paths_(nexts.size(), -1),
286 new_synchronized_unperformed_nodes_(nexts.size()),
287 new_nexts_(nexts.size(), kUnassigned),
288 touched_paths_(nexts.size()),
289 touched_path_chain_start_ends_(nexts.size(), {kUnassigned, kUnassigned}),
290 ranks_(next_domain_size, -1),
291 status_(BasePathFilter::UNKNOWN),
292 lns_detected_(false) {}
293
295 const Assignment* /*deltadelta*/,
296 int64_t objective_min, int64_t objective_max) {
297 if (IsDisabled()) return true;
298 lns_detected_ = false;
299 for (const int touched : delta_touched_) {
300 new_nexts_[touched] = kUnassigned;
301 }
302 delta_touched_.clear();
303 const Assignment::IntContainer& container = delta->IntVarContainer();
304 delta_touched_.reserve(container.Size());
305 // Determining touched paths and their touched chain start and ends (a node is
306 // touched if it corresponds to an element of delta or that an element of
307 // delta points to it).
308 // The start and end of a touched path subchain will have remained on the same
309 // path and will correspond to the min and max ranks of touched nodes in the
310 // current assignment.
311 for (int64_t touched_path : touched_paths_.PositionsSetAtLeastOnce()) {
312 touched_path_chain_start_ends_[touched_path] = {kUnassigned, kUnassigned};
313 }
314 touched_paths_.SparseClearAll();
315
316 const auto update_touched_path_chain_start_end = [this](int64_t index) {
317 const int64_t start = node_path_starts_[index];
318 if (start == kUnassigned) return;
319 touched_paths_.Set(start);
320
321 int64_t& chain_start = touched_path_chain_start_ends_[start].first;
322 if (chain_start == kUnassigned || ranks_[index] < ranks_[chain_start]) {
323 chain_start = index;
324 }
325
326 int64_t& chain_end = touched_path_chain_start_ends_[start].second;
327 if (chain_end == kUnassigned || ranks_[index] > ranks_[chain_end]) {
328 chain_end = index;
329 }
330 };
331
332 for (const IntVarElement& new_element : container.elements()) {
333 IntVar* const var = new_element.Var();
334 int64_t index = kUnassigned;
335 if (FindIndex(var, &index)) {
336 if (!new_element.Bound()) {
337 // LNS detected
338 lns_detected_ = true;
339 return true;
340 }
341 new_nexts_[index] = new_element.Value();
342 delta_touched_.push_back(index);
343 update_touched_path_chain_start_end(index);
344 update_touched_path_chain_start_end(new_nexts_[index]);
345 }
346 }
347 // Checking feasibility of touched paths.
348 if (!InitializeAcceptPath()) return false;
349 for (const int64_t touched_start : touched_paths_.PositionsSetAtLeastOnce()) {
350 const std::pair<int64_t, int64_t> start_end =
351 touched_path_chain_start_ends_[touched_start];
352 if (!AcceptPath(touched_start, start_end.first, start_end.second)) {
353 return false;
354 }
355 }
356 // NOTE: FinalizeAcceptPath() is only called if InitializeAcceptPath() is true
357 // and all paths are accepted.
358 return FinalizeAcceptPath(objective_min, objective_max);
359}
360
361void BasePathFilter::ComputePathStarts(std::vector<int64_t>* path_starts,
362 std::vector<int>* index_to_path) {
363 path_starts->clear();
364 const int nexts_size = Size();
365 index_to_path->assign(nexts_size, kUnassigned);
366 Bitset64<> has_prevs(nexts_size);
367 for (int i = 0; i < nexts_size; ++i) {
368 if (!IsVarSynced(i)) {
369 has_prevs.Set(i);
370 } else {
371 const int next = Value(i);
372 if (next < nexts_size) {
373 has_prevs.Set(next);
374 }
375 }
376 }
377 for (int i = 0; i < nexts_size; ++i) {
378 if (!has_prevs[i]) {
379 (*index_to_path)[i] = path_starts->size();
380 path_starts->push_back(i);
381 }
382 }
383}
384
385bool BasePathFilter::HavePathsChanged() {
386 std::vector<int64_t> path_starts;
387 std::vector<int> index_to_path(Size(), kUnassigned);
388 ComputePathStarts(&path_starts, &index_to_path);
389 if (path_starts.size() != starts_.size()) {
390 return true;
391 }
392 for (int i = 0; i < path_starts.size(); ++i) {
393 if (path_starts[i] != starts_[i]) {
394 return true;
395 }
396 }
397 for (int i = 0; i < Size(); ++i) {
398 if (index_to_path[i] != paths_[i]) {
399 return true;
400 }
401 }
402 return false;
403}
404
405void BasePathFilter::SynchronizeFullAssignment() {
406 // Subclasses of BasePathFilter might not propagate injected objective values
407 // so making sure it is done here (can be done again by the subclass if
408 // needed).
409 ComputePathStarts(&starts_, &paths_);
410 for (int64_t index = 0; index < Size(); index++) {
411 if (IsVarSynced(index) && Value(index) == index &&
412 node_path_starts_[index] != kUnassigned) {
413 // index was performed before and is now unperformed.
414 new_synchronized_unperformed_nodes_.Set(index);
415 }
416 }
417 // Marking unactive nodes (which are not on a path).
418 node_path_starts_.assign(node_path_starts_.size(), kUnassigned);
419 // Marking nodes on a path and storing next values.
420 const int nexts_size = Size();
421 for (const int64_t start : starts_) {
422 int node = start;
423 node_path_starts_[node] = start;
424 DCHECK(IsVarSynced(node));
425 int next = Value(node);
426 while (next < nexts_size) {
427 node = next;
428 node_path_starts_[node] = start;
429 DCHECK(IsVarSynced(node));
430 next = Value(node);
431 }
432 node_path_starts_[next] = start;
433 }
434 for (const int touched : delta_touched_) {
435 new_nexts_[touched] = kUnassigned;
436 }
437 delta_touched_.clear();
438 OnBeforeSynchronizePaths();
439 UpdateAllRanks();
440 OnAfterSynchronizePaths();
441}
442
444 if (status_ == BasePathFilter::UNKNOWN) {
445 status_ =
446 DisableFiltering() ? BasePathFilter::DISABLED : BasePathFilter::ENABLED;
447 }
448 if (IsDisabled()) return;
449 new_synchronized_unperformed_nodes_.ClearAll();
450 if (delta == nullptr || delta->Empty() || starts_.empty()) {
451 SynchronizeFullAssignment();
452 return;
453 }
454 // Subclasses of BasePathFilter might not propagate injected objective values
455 // so making sure it is done here (can be done again by the subclass if
456 // needed).
457 // This code supposes that path starts didn't change.
458 DCHECK(!absl::GetFlag(FLAGS_routing_strong_debug_checks) ||
459 !HavePathsChanged());
460 const Assignment::IntContainer& container = delta->IntVarContainer();
461 touched_paths_.SparseClearAll();
462 for (const IntVarElement& new_element : container.elements()) {
463 int64_t index = kUnassigned;
464 if (FindIndex(new_element.Var(), &index)) {
465 const int64_t start = node_path_starts_[index];
466 if (start != kUnassigned) {
467 touched_paths_.Set(start);
468 if (Value(index) == index) {
469 // New unperformed node (its previous start isn't unassigned).
470 DCHECK_LT(index, new_nexts_.size());
471 new_synchronized_unperformed_nodes_.Set(index);
472 node_path_starts_[index] = kUnassigned;
473 }
474 }
475 }
476 }
477 for (const int touched : delta_touched_) {
478 new_nexts_[touched] = kUnassigned;
479 }
480 delta_touched_.clear();
481 OnBeforeSynchronizePaths();
482 for (const int64_t touched_start : touched_paths_.PositionsSetAtLeastOnce()) {
483 int64_t node = touched_start;
484 while (node < Size()) {
485 node_path_starts_[node] = touched_start;
486 node = Value(node);
487 }
488 node_path_starts_[node] = touched_start;
489 UpdatePathRanksFromStart(touched_start);
490 OnSynchronizePathFromStart(touched_start);
491 }
492 OnAfterSynchronizePaths();
493}
494
495void BasePathFilter::UpdateAllRanks() {
496 ranks_.assign(ranks_.size(), kUnassigned);
497 for (int r = 0; r < NumPaths(); ++r) {
498 UpdatePathRanksFromStart(Start(r));
499 OnSynchronizePathFromStart(Start(r));
500 }
501}
502
503void 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
514namespace {
515
516class 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
549VehicleAmortizedCostFilter::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
571void 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
579void 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
603bool VehicleAmortizedCostFilter::InitializeAcceptPath() {
604 delta_vehicle_cost_ = current_vehicle_cost_;
605 return true;
606}
607
608bool 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
657bool 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
670namespace {
671
672class 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
696TypeRegulationsFilter::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
720void 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
743bool 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
800bool 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
819namespace {
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
826class 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
853ChainCumulFilter::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.
877void 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).
906bool 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
936class 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_total_slack_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 dimension_.GetSlackCostCoefficientForVehicle(vehicle) > 0) {
1063 ++num_linear_constraints;
1064 }
1065 if (FilterSoftSpanCost(vehicle)) ++num_linear_constraints;
1066 if (FilterCumulSoftLowerBounds()) ++num_linear_constraints;
1067 if (FilterCumulSoftBounds()) ++num_linear_constraints;
1068 if (vehicle_span_upper_bounds_[vehicle] <
1069 std::numeric_limits<int64_t>::max()) {
1070 ++num_linear_constraints;
1071 }
1072 const bool has_breaks = FilterBreakCost(vehicle);
1073 if (has_breaks) ++num_linear_constraints;
1074
1075 // The DimensionCumulOptimizer is used to compute a more precise value of
1076 // the cost related to the cumul values (soft bounds and span/slack costs).
1077 // It is also used to guarantee feasibility with complex mixes of
1078 // constraints and in particular in the presence of break requests along
1079 // other constraints. Therefore, without breaks, we only use the optimizer
1080 // when the costs are actually used to filter the solutions, i.e. when
1081 // filter_objective_cost_ is true.
1082 return num_linear_constraints >= 2 &&
1083 (has_breaks || filter_objective_cost_);
1084 }
1085
1086 int64_t GetCumulPiecewiseLinearCost(int64_t node, int64_t cumul_value) const;
1087
1088 bool FilterCumulSoftLowerBounds() const {
1089 return !cumul_soft_lower_bounds_.empty();
1090 }
1091
1092 bool FilterPrecedences() const { return !node_index_to_precedences_.empty(); }
1093
1094 bool FilterSoftSpanCost() const {
1095 return dimension_.HasSoftSpanUpperBounds();
1096 }
1097 bool FilterSoftSpanCost(int vehicle) const {
1098 return dimension_.HasSoftSpanUpperBounds() &&
1099 dimension_.GetSoftSpanUpperBoundForVehicle(vehicle).cost > 0;
1100 }
1101 bool FilterSoftSpanQuadraticCost() const {
1102 return dimension_.HasQuadraticCostSoftSpanUpperBounds();
1103 }
1104 bool FilterSoftSpanQuadraticCost(int vehicle) const {
1105 return dimension_.HasQuadraticCostSoftSpanUpperBounds() &&
1106 dimension_.GetQuadraticCostSoftSpanUpperBoundForVehicle(vehicle)
1107 .cost > 0;
1108 }
1109
1110 int64_t GetCumulSoftLowerBoundCost(int64_t node, int64_t cumul_value) const;
1111
1112 int64_t GetPathCumulSoftLowerBoundCost(const PathTransits& path_transits,
1113 int path) const;
1114
1115 void InitializeSupportedPathCumul(SupportedPathCumul* supported_cumul,
1116 int64_t default_value);
1117
1118 // Given the vector of minimum cumuls on the path, determines if the pickup to
1119 // delivery limits for this dimension (if there are any) can be respected by
1120 // this path.
1121 // Returns true if for every pickup/delivery nodes visited on this path,
1122 // min_cumul_value(delivery) - max_cumul_value(pickup) is less than the limit
1123 // set for this pickup to delivery.
1124 // TODO(user): Verify if we should filter the pickup/delivery limits using
1125 // the LP, for a perfect filtering.
1126 bool PickupToDeliveryLimitsRespected(
1127 const PathTransits& path_transits, int path,
1128 absl::Span<const int64_t> min_path_cumuls) const;
1129
1130 // Computes the maximum cumul value of nodes along the path using
1131 // [current|delta]_path_transits_, and stores the min/max cumul
1132 // related to each node in the corresponding vector
1133 // [current|delta]_[min|max]_node_cumuls_.
1134 // The boolean is_delta indicates if the computations should take place on the
1135 // "delta" or "current" members. When true, the nodes for which the min/max
1136 // cumul has changed from the current value are marked in
1137 // delta_nodes_with_precedences_and_changed_cumul_.
1138 void StoreMinMaxCumulOfNodesOnPath(int path,
1139 absl::Span<const int64_t> min_path_cumuls,
1140 bool is_delta);
1141
1142 // Compute the max start cumul value for a given path and a given minimal end
1143 // cumul value.
1144 // NOTE: Since this function is used to compute a lower bound on the span of
1145 // the routes, we don't "jump" over the forbidden intervals with this min end
1146 // cumul value. We do however concurrently compute the max possible start
1147 // given the max end cumul, for which we can "jump" over forbidden intervals,
1148 // and return the minimum of the two.
1149 int64_t ComputePathMaxStartFromEndCumul(const PathTransits& path_transits,
1150 int path, int64_t path_start,
1151 int64_t min_end_cumul) const;
1152
1153 const RoutingModel& routing_model_;
1154 const RoutingDimension& dimension_;
1155 const std::vector<IntVar*> cumuls_;
1156 const std::vector<IntVar*> slacks_;
1157 std::vector<int64_t> start_to_vehicle_;
1158 std::vector<const RoutingModel::TransitCallback2*> evaluators_;
1159 std::vector<int64_t> vehicle_span_upper_bounds_;
1160 const bool has_vehicle_span_upper_bounds_;
1161 int64_t total_current_cumul_cost_value_;
1162 int64_t synchronized_objective_value_;
1163 int64_t accepted_objective_value_;
1164 // Map between paths and path soft cumul bound costs. The paths are indexed
1165 // by the index of the start node of the path.
1166 absl::flat_hash_map<int64_t, int64_t> current_cumul_cost_values_;
1167 int64_t cumul_cost_delta_;
1168 // Cumul cost values for paths in delta, indexed by vehicle.
1169 std::vector<int64_t> delta_path_cumul_cost_values_;
1170 const int64_t global_span_cost_coefficient_;
1171 std::vector<SoftBound> cumul_soft_bounds_;
1172 std::vector<SoftBound> cumul_soft_lower_bounds_;
1173 std::vector<const PiecewiseLinearFunction*> cumul_piecewise_linear_costs_;
1174 std::vector<int64_t> vehicle_total_slack_cost_coefficients_;
1175 bool has_nonzero_vehicle_total_slack_cost_coefficients_;
1176 const std::vector<int64_t> vehicle_capacities_;
1177 // node_index_to_precedences_[node_index] contains all NodePrecedence elements
1178 // with node_index as either "first_node" or "second_node".
1179 // This vector is empty if there are no precedences on the dimension_.
1180 std::vector<std::vector<RoutingDimension::NodePrecedence>>
1181 node_index_to_precedences_;
1182 // Data reflecting information on paths and cumul variables for the solution
1183 // to which the filter was synchronized.
1184 SupportedPathCumul current_min_start_;
1185 SupportedPathCumul current_max_end_;
1186 PathTransits current_path_transits_;
1187 // Current min/max cumul values, indexed by node.
1188 std::vector<std::pair<int64_t, int64_t>> current_min_max_node_cumuls_;
1189 // Data reflecting information on paths and cumul variables for the "delta"
1190 // solution (aka neighbor solution) being examined.
1191 PathTransits delta_path_transits_;
1192 int64_t delta_max_end_cumul_;
1193 SparseBitset<int64_t> delta_nodes_with_precedences_and_changed_cumul_;
1194 absl::flat_hash_map<int64_t, std::pair<int64_t, int64_t>>
1195 node_with_precedence_to_delta_min_max_cumuls_;
1196 absl::btree_set<int> delta_paths_;
1197 const std::string name_;
1198
1199 LocalDimensionCumulOptimizer* optimizer_;
1200 LocalDimensionCumulOptimizer* mp_optimizer_;
1201 const std::function<int64_t(int64_t)> path_accessor_;
1202 const bool filter_objective_cost_;
1203 // This boolean indicates if the LP optimizer can be used if necessary to
1204 // optimize the dimension cumuls.
1205 const bool can_use_lp_;
1206 const bool propagate_own_objective_value_;
1207
1208 std::vector<int64_t> min_path_cumuls_;
1209};
1210
1211namespace {
1212template <typename T>
1213std::vector<T> SumOfVectors(const std::vector<T>& v1,
1214 const std::vector<T>& v2) {
1215 DCHECK_EQ(v1.size(), v2.size());
1216 std::vector<T> sum(v1.size());
1217 absl::c_transform(v1, v2, sum.begin(), [](T a, T b) { return CapAdd(a, b); });
1218 return sum;
1219}
1220} // namespace
1221
1222PathCumulFilter::PathCumulFilter(const RoutingModel& routing_model,
1223 const RoutingDimension& dimension,
1224 bool propagate_own_objective_value,
1225 bool filter_objective_cost, bool can_use_lp)
1226 : BasePathFilter(routing_model.Nexts(), dimension.cumuls().size()),
1227 routing_model_(routing_model),
1228 dimension_(dimension),
1229 cumuls_(dimension.cumuls()),
1230 slacks_(dimension.slacks()),
1231 evaluators_(routing_model.vehicles(), nullptr),
1232 vehicle_span_upper_bounds_(dimension.vehicle_span_upper_bounds()),
1233 has_vehicle_span_upper_bounds_(absl::c_any_of(
1234 vehicle_span_upper_bounds_,
1235 [](int64_t upper_bound) {
1236 return upper_bound != std::numeric_limits<int64_t>::max();
1237 })),
1238 total_current_cumul_cost_value_(0),
1239 synchronized_objective_value_(0),
1240 accepted_objective_value_(0),
1241 current_cumul_cost_values_(),
1242 cumul_cost_delta_(0),
1243 delta_path_cumul_cost_values_(routing_model.vehicles(),
1244 std::numeric_limits<int64_t>::min()),
1245 global_span_cost_coefficient_(dimension.global_span_cost_coefficient()),
1246 vehicle_total_slack_cost_coefficients_(
1247 SumOfVectors(dimension.vehicle_span_cost_coefficients(),
1248 dimension.vehicle_slack_cost_coefficients())),
1249 has_nonzero_vehicle_total_slack_cost_coefficients_(
1250 absl::c_any_of(vehicle_total_slack_cost_coefficients_,
1251 [](int64_t coefficient) { return coefficient != 0; })),
1252 vehicle_capacities_(dimension.vehicle_capacities()),
1253 delta_max_end_cumul_(0),
1254 delta_nodes_with_precedences_and_changed_cumul_(routing_model.Size()),
1255 name_(dimension.name()),
1256 optimizer_(routing_model.GetMutableLocalCumulLPOptimizer(dimension)),
1257 mp_optimizer_(routing_model.GetMutableLocalCumulMPOptimizer(dimension)),
1258 path_accessor_([this](int64_t node) { return GetNext(node); }),
1259 filter_objective_cost_(filter_objective_cost),
1260 can_use_lp_(can_use_lp),
1261 propagate_own_objective_value_(propagate_own_objective_value) {
1262 cumul_soft_bounds_.resize(cumuls_.size());
1263 cumul_soft_lower_bounds_.resize(cumuls_.size());
1264 cumul_piecewise_linear_costs_.resize(cumuls_.size());
1265 bool has_cumul_soft_bounds = false;
1266 bool has_cumul_soft_lower_bounds = false;
1267 bool has_cumul_piecewise_linear_costs = false;
1268 bool has_cumul_hard_bounds = false;
1269 for (const IntVar* const slack : slacks_) {
1270 if (slack->Min() > 0) {
1271 has_cumul_hard_bounds = true;
1272 break;
1273 }
1274 }
1275 for (int i = 0; i < cumuls_.size(); ++i) {
1276 if (dimension.HasCumulVarSoftUpperBound(i)) {
1277 has_cumul_soft_bounds = true;
1278 cumul_soft_bounds_[i].bound = dimension.GetCumulVarSoftUpperBound(i);
1279 cumul_soft_bounds_[i].coefficient =
1280 dimension.GetCumulVarSoftUpperBoundCoefficient(i);
1281 }
1282 if (dimension.HasCumulVarSoftLowerBound(i)) {
1283 has_cumul_soft_lower_bounds = true;
1284 cumul_soft_lower_bounds_[i].bound =
1285 dimension.GetCumulVarSoftLowerBound(i);
1286 cumul_soft_lower_bounds_[i].coefficient =
1287 dimension.GetCumulVarSoftLowerBoundCoefficient(i);
1288 }
1289 if (dimension.HasCumulVarPiecewiseLinearCost(i)) {
1290 has_cumul_piecewise_linear_costs = true;
1291 cumul_piecewise_linear_costs_[i] =
1292 dimension.GetCumulVarPiecewiseLinearCost(i);
1293 }
1294 IntVar* const cumul_var = cumuls_[i];
1295 if (cumul_var->Min() > 0 ||
1296 cumul_var->Max() < std::numeric_limits<int64_t>::max()) {
1297 has_cumul_hard_bounds = true;
1298 }
1299 }
1300 if (!has_cumul_soft_bounds) {
1301 cumul_soft_bounds_.clear();
1302 }
1303 if (!has_cumul_soft_lower_bounds) {
1304 cumul_soft_lower_bounds_.clear();
1305 }
1306 if (!has_cumul_piecewise_linear_costs) {
1307 cumul_piecewise_linear_costs_.clear();
1308 }
1309 if (!has_cumul_hard_bounds) {
1310 // Slacks don't need to be constrained if the cumuls don't have hard bounds;
1311 // therefore we can ignore the vehicle span/slack cost coefficient (note
1312 // that the transit part is already handled by the arc cost filters). This
1313 // doesn't concern the global span filter though.
1314 vehicle_total_slack_cost_coefficients_.assign(routing_model.vehicles(), 0);
1315 has_nonzero_vehicle_total_slack_cost_coefficients_ = false;
1316 }
1317 start_to_vehicle_.resize(Size(), -1);
1318 for (int i = 0; i < routing_model.vehicles(); ++i) {
1319 start_to_vehicle_[routing_model.Start(i)] = i;
1320 evaluators_[i] = &dimension.transit_evaluator(i);
1321 }
1322
1323 const std::vector<RoutingDimension::NodePrecedence>& node_precedences =
1324 dimension.GetNodePrecedences();
1325 if (!node_precedences.empty()) {
1326 current_min_max_node_cumuls_.resize(cumuls_.size(), {-1, -1});
1327 node_index_to_precedences_.resize(cumuls_.size());
1328 for (const auto& node_precedence : node_precedences) {
1329 node_index_to_precedences_[node_precedence.first_node].push_back(
1330 node_precedence);
1331 node_index_to_precedences_[node_precedence.second_node].push_back(
1332 node_precedence);
1333 }
1334 }
1335
1336#ifndef NDEBUG
1337 for (int vehicle = 0; vehicle < routing_model.vehicles(); vehicle++) {
1338 if (FilterWithDimensionCumulOptimizerForVehicle(vehicle)) {
1339 DCHECK_NE(optimizer_, nullptr);
1340 DCHECK_NE(mp_optimizer_, nullptr);
1341 }
1342 }
1343#endif // NDEBUG
1344}
1345
1346int64_t PathCumulFilter::GetCumulSoftCost(int64_t node,
1347 int64_t cumul_value) const {
1348 if (node < cumul_soft_bounds_.size()) {
1349 const int64_t bound = cumul_soft_bounds_[node].bound;
1350 const int64_t coefficient = cumul_soft_bounds_[node].coefficient;
1351 if (coefficient > 0 && bound < cumul_value) {
1353 }
1354 }
1355 return 0;
1356}
1357
1358int64_t PathCumulFilter::GetCumulPiecewiseLinearCost(
1359 int64_t node, int64_t cumul_value) const {
1360 if (node < cumul_piecewise_linear_costs_.size()) {
1361 const PiecewiseLinearFunction* cost = cumul_piecewise_linear_costs_[node];
1362 if (cost != nullptr) {
1363 return cost->Value(cumul_value);
1364 }
1365 }
1366 return 0;
1367}
1368
1369int64_t PathCumulFilter::GetCumulSoftLowerBoundCost(int64_t node,
1370 int64_t cumul_value) const {
1371 if (node < cumul_soft_lower_bounds_.size()) {
1372 const int64_t bound = cumul_soft_lower_bounds_[node].bound;
1373 const int64_t coefficient = cumul_soft_lower_bounds_[node].coefficient;
1374 if (coefficient > 0 && bound > cumul_value) {
1376 }
1377 }
1378 return 0;
1379}
1380
1381int64_t PathCumulFilter::GetPathCumulSoftLowerBoundCost(
1382 const PathTransits& path_transits, int path) const {
1383 int64_t node = path_transits.Node(path, path_transits.PathSize(path) - 1);
1384 int64_t cumul = cumuls_[node]->Max();
1385 int64_t current_cumul_cost_value = GetCumulSoftLowerBoundCost(node, cumul);
1386 for (int i = path_transits.PathSize(path) - 2; i >= 0; --i) {
1387 node = path_transits.Node(path, i);
1388 cumul = CapSub(cumul, path_transits.Transit(path, i));
1389 cumul = std::min(cumuls_[node]->Max(), cumul);
1390 current_cumul_cost_value = CapAdd(current_cumul_cost_value,
1391 GetCumulSoftLowerBoundCost(node, cumul));
1392 }
1393 return current_cumul_cost_value;
1394}
1395
1396void PathCumulFilter::OnBeforeSynchronizePaths() {
1397 total_current_cumul_cost_value_ = 0;
1398 cumul_cost_delta_ = 0;
1399 current_cumul_cost_values_.clear();
1400 if (NumPaths() > 0 &&
1401 (FilterSpanCost() || FilterCumulSoftBounds() || FilterSlackCost() ||
1402 FilterCumulSoftLowerBounds() || FilterCumulPiecewiseLinearCosts() ||
1403 FilterPrecedences() || FilterSoftSpanCost() ||
1404 FilterSoftSpanQuadraticCost())) {
1405 InitializeSupportedPathCumul(&current_min_start_,
1406 std::numeric_limits<int64_t>::max());
1407 InitializeSupportedPathCumul(&current_max_end_,
1408 std::numeric_limits<int64_t>::min());
1409 current_path_transits_.Clear();
1410 current_path_transits_.AddPaths(NumPaths());
1411 // For each path, compute the minimum end cumul and store the max of these.
1412 for (int r = 0; r < NumPaths(); ++r) {
1413 int64_t node = Start(r);
1414 const int vehicle = start_to_vehicle_[Start(r)];
1415 // First pass: evaluating route length to reserve memory to store route
1416 // information.
1417 int number_of_route_arcs = 0;
1418 while (node < Size()) {
1419 ++number_of_route_arcs;
1420 node = Value(node);
1421 }
1422 current_path_transits_.ReserveTransits(r, number_of_route_arcs);
1423 // Second pass: update cumul, transit and cost values.
1424 node = Start(r);
1425 int64_t cumul = cumuls_[node]->Min();
1426 min_path_cumuls_.clear();
1427 min_path_cumuls_.push_back(cumul);
1428
1429 int64_t current_cumul_cost_value = GetCumulSoftCost(node, cumul);
1430 current_cumul_cost_value = CapAdd(
1431 current_cumul_cost_value, GetCumulPiecewiseLinearCost(node, cumul));
1432
1433 int64_t total_transit = 0;
1434 while (node < Size()) {
1435 const int64_t next = Value(node);
1436 const int64_t transit = (*evaluators_[vehicle])(node, next);
1437 total_transit = CapAdd(total_transit, transit);
1438 const int64_t transit_slack = CapAdd(transit, slacks_[node]->Min());
1439 current_path_transits_.PushTransit(r, node, next, transit_slack);
1440 cumul = CapAdd(cumul, transit_slack);
1441 cumul =
1442 dimension_.GetFirstPossibleGreaterOrEqualValueForNode(next, cumul);
1443 cumul = std::max(cumuls_[next]->Min(), cumul);
1444 min_path_cumuls_.push_back(cumul);
1445 node = next;
1446 current_cumul_cost_value =
1447 CapAdd(current_cumul_cost_value, GetCumulSoftCost(node, cumul));
1448 current_cumul_cost_value = CapAdd(
1449 current_cumul_cost_value, GetCumulPiecewiseLinearCost(node, cumul));
1450 }
1451 if (FilterPrecedences()) {
1452 StoreMinMaxCumulOfNodesOnPath(/*path=*/r, min_path_cumuls_,
1453 /*is_delta=*/false);
1454 }
1455 if (number_of_route_arcs == 1 &&
1456 !routing_model_.IsVehicleUsedWhenEmpty(vehicle)) {
1457 // This is an empty route (single start->end arc) which we don't take
1458 // into account for costs.
1459 current_cumul_cost_values_[Start(r)] = 0;
1460 current_path_transits_.ClearPath(r);
1461 continue;
1462 }
1463 if (FilterSlackCost() || FilterSoftSpanCost() ||
1464 FilterSoftSpanQuadraticCost()) {
1465 const int64_t start = ComputePathMaxStartFromEndCumul(
1466 current_path_transits_, r, Start(r), cumul);
1467 const int64_t span_lower_bound = CapSub(cumul, start);
1468 if (FilterSlackCost()) {
1469 current_cumul_cost_value =
1470 CapAdd(current_cumul_cost_value,
1471 CapProd(vehicle_total_slack_cost_coefficients_[vehicle],
1472 CapSub(span_lower_bound, total_transit)));
1473 }
1474 if (FilterSoftSpanCost()) {
1475 const BoundCost bound_cost =
1476 dimension_.GetSoftSpanUpperBoundForVehicle(vehicle);
1477 if (bound_cost.bound < span_lower_bound) {
1478 const int64_t violation =
1479 CapSub(span_lower_bound, bound_cost.bound);
1480 current_cumul_cost_value = CapAdd(
1481 current_cumul_cost_value, CapProd(bound_cost.cost, violation));
1482 }
1483 }
1484 if (FilterSoftSpanQuadraticCost()) {
1485 const BoundCost bound_cost =
1486 dimension_.GetQuadraticCostSoftSpanUpperBoundForVehicle(vehicle);
1487 if (bound_cost.bound < span_lower_bound) {
1488 const int64_t violation =
1489 CapSub(span_lower_bound, bound_cost.bound);
1490 current_cumul_cost_value =
1491 CapAdd(current_cumul_cost_value,
1492 CapProd(bound_cost.cost, CapProd(violation, violation)));
1493 }
1494 }
1495 }
1496 if (FilterCumulSoftLowerBounds()) {
1497 current_cumul_cost_value =
1498 CapAdd(current_cumul_cost_value,
1499 GetPathCumulSoftLowerBoundCost(current_path_transits_, r));
1500 }
1501 if (FilterWithDimensionCumulOptimizerForVehicle(vehicle)) {
1502 // TODO(user): Return a status from the optimizer to detect failures
1503 // The only admissible failures here are because of LP timeout.
1504 int64_t lp_cumul_cost_value = 0;
1505 LocalDimensionCumulOptimizer* const optimizer =
1506 FilterBreakCost(vehicle) ? mp_optimizer_ : optimizer_;
1507 DCHECK(optimizer != nullptr);
1509 optimizer->ComputeRouteCumulCostWithoutFixedTransits(
1510 vehicle, path_accessor_,
1511 filter_objective_cost_ ? &lp_cumul_cost_value : nullptr);
1512 switch (status) {
1513 case DimensionSchedulingStatus::INFEASIBLE:
1514 lp_cumul_cost_value = 0;
1515 break;
1516 case DimensionSchedulingStatus::RELAXED_OPTIMAL_ONLY:
1517 DCHECK(mp_optimizer_ != nullptr);
1518 if (mp_optimizer_->ComputeRouteCumulCostWithoutFixedTransits(
1519 vehicle, path_accessor_,
1520 filter_objective_cost_ ? &lp_cumul_cost_value : nullptr) ==
1522 lp_cumul_cost_value = 0;
1523 }
1524 break;
1525 default:
1526 DCHECK(status == DimensionSchedulingStatus::OPTIMAL);
1527 }
1528 current_cumul_cost_value =
1529 std::max(current_cumul_cost_value, lp_cumul_cost_value);
1530 }
1531 current_cumul_cost_values_[Start(r)] = current_cumul_cost_value;
1532 current_max_end_.path_values[r] = cumul;
1533 if (current_max_end_.cumul_value < cumul) {
1534 current_max_end_.cumul_value = cumul;
1535 current_max_end_.cumul_value_support = r;
1536 }
1537 total_current_cumul_cost_value_ =
1538 CapAdd(total_current_cumul_cost_value_, current_cumul_cost_value);
1539 }
1540 if (FilterPrecedences()) {
1541 // Update the min/max node cumuls of new unperformed nodes.
1542 for (int64_t node : GetNewSynchronizedUnperformedNodes()) {
1543 current_min_max_node_cumuls_[node] = {-1, -1};
1544 }
1545 }
1546 // Use the max of the path end cumul mins to compute the corresponding
1547 // maximum start cumul of each path; store the minimum of these.
1548 for (int r = 0; r < NumPaths(); ++r) {
1549 const int64_t start = ComputePathMaxStartFromEndCumul(
1550 current_path_transits_, r, Start(r), current_max_end_.cumul_value);
1551 current_min_start_.path_values[r] = start;
1552 if (current_min_start_.cumul_value > start) {
1553 current_min_start_.cumul_value = start;
1554 current_min_start_.cumul_value_support = r;
1555 }
1556 }
1557 }
1558 // Initialize this before considering any deltas (neighbor).
1559 delta_max_end_cumul_ = std::numeric_limits<int64_t>::min();
1560
1561 DCHECK(global_span_cost_coefficient_ == 0 ||
1562 current_min_start_.cumul_value <= current_max_end_.cumul_value);
1563 synchronized_objective_value_ =
1564 CapAdd(total_current_cumul_cost_value_,
1565 CapProd(global_span_cost_coefficient_,
1566 CapSub(current_max_end_.cumul_value,
1567 current_min_start_.cumul_value)));
1568}
1569
1570bool PathCumulFilter::AcceptPath(int64_t path_start, int64_t /*chain_start*/,
1571 int64_t /*chain_end*/) {
1572 int64_t node = path_start;
1573 int64_t cumul = cumuls_[node]->Min();
1574 int64_t cumul_cost_delta = 0;
1575 int64_t total_transit = 0;
1576 const int path = delta_path_transits_.AddPaths(1);
1577 const int vehicle = start_to_vehicle_[path_start];
1578 const int64_t capacity = vehicle_capacities_[vehicle];
1579 const bool filter_vehicle_costs =
1580 !routing_model_.IsEnd(GetNext(node)) ||
1581 routing_model_.IsVehicleUsedWhenEmpty(vehicle);
1582 if (filter_vehicle_costs) {
1583 cumul_cost_delta = CapAdd(GetCumulSoftCost(node, cumul),
1584 GetCumulPiecewiseLinearCost(node, cumul));
1585 }
1586 // Evaluating route length to reserve memory to store transit information.
1587 int number_of_route_arcs = 0;
1588 while (node < Size()) {
1589 ++number_of_route_arcs;
1590 node = GetNext(node);
1591 DCHECK_NE(node, kUnassigned);
1592 }
1593 delta_path_transits_.ReserveTransits(path, number_of_route_arcs);
1594 min_path_cumuls_.clear();
1595 min_path_cumuls_.push_back(cumul);
1596 // Check that the path is feasible with regards to cumul bounds, scanning
1597 // the paths from start to end (caching path node sequences and transits
1598 // for further span cost filtering).
1599 node = path_start;
1600 while (node < Size()) {
1601 const int64_t next = GetNext(node);
1602 const int64_t transit = (*evaluators_[vehicle])(node, next);
1603 total_transit = CapAdd(total_transit, transit);
1604 const int64_t transit_slack = CapAdd(transit, slacks_[node]->Min());
1605 delta_path_transits_.PushTransit(path, node, next, transit_slack);
1606 cumul = CapAdd(cumul, transit_slack);
1607 cumul = dimension_.GetFirstPossibleGreaterOrEqualValueForNode(next, cumul);
1608 if (cumul > std::min(capacity, cumuls_[next]->Max())) {
1609 return false;
1610 }
1611 cumul = std::max(cumuls_[next]->Min(), cumul);
1612 min_path_cumuls_.push_back(cumul);
1613 node = next;
1614 if (filter_vehicle_costs) {
1615 cumul_cost_delta =
1616 CapAdd(cumul_cost_delta, GetCumulSoftCost(node, cumul));
1617 cumul_cost_delta =
1618 CapAdd(cumul_cost_delta, GetCumulPiecewiseLinearCost(node, cumul));
1619 }
1620 }
1621 const int64_t min_end = cumul;
1622
1623 if (!PickupToDeliveryLimitsRespected(delta_path_transits_, path,
1624 min_path_cumuls_)) {
1625 return false;
1626 }
1627 if (FilterSlackCost() || FilterBreakCost(vehicle) ||
1628 FilterSoftSpanCost(vehicle) || FilterSoftSpanQuadraticCost(vehicle)) {
1629 int64_t slack_max = std::numeric_limits<int64_t>::max();
1630 if (vehicle_span_upper_bounds_[vehicle] <
1631 std::numeric_limits<int64_t>::max()) {
1632 const int64_t span_max = vehicle_span_upper_bounds_[vehicle];
1633 slack_max = std::min(slack_max, CapSub(span_max, total_transit));
1634 }
1635 const int64_t max_start_from_min_end = ComputePathMaxStartFromEndCumul(
1636 delta_path_transits_, path, path_start, min_end);
1637 const int64_t span_lb = CapSub(min_end, max_start_from_min_end);
1638 int64_t min_total_slack = CapSub(span_lb, total_transit);
1639 if (min_total_slack > slack_max) return false;
1640
1641 if (dimension_.HasBreakConstraints()) {
1642 for (const auto [limit, min_break_duration] :
1643 dimension_.GetBreakDistanceDurationOfVehicle(vehicle)) {
1644 // Minimal number of breaks depends on total transit:
1645 // 0 breaks for 0 <= total transit <= limit,
1646 // 1 break for limit + 1 <= total transit <= 2 * limit,
1647 // i breaks for i * limit + 1 <= total transit <= (i+1) * limit, ...
1648 if (limit == 0 || total_transit == 0) continue;
1649 const int num_breaks_lb = (total_transit - 1) / limit;
1650 const int64_t slack_lb = CapProd(num_breaks_lb, min_break_duration);
1651 if (slack_lb > slack_max) return false;
1652 min_total_slack = std::max(min_total_slack, slack_lb);
1653 }
1654 // Compute a lower bound of the amount of break that must be made inside
1655 // the route. We compute a mandatory interval (might be empty)
1656 // [max_start, min_end[ during which the route will have to happen,
1657 // then the duration of break that must happen during this interval.
1658 int64_t min_total_break = 0;
1659 int64_t max_path_end = cumuls_[routing_model_.End(vehicle)]->Max();
1660 const int64_t max_start = ComputePathMaxStartFromEndCumul(
1661 delta_path_transits_, path, path_start, max_path_end);
1662 for (const IntervalVar* br :
1663 dimension_.GetBreakIntervalsOfVehicle(vehicle)) {
1664 if (!br->MustBePerformed()) continue;
1665 if (max_start < br->EndMin() && br->StartMax() < min_end) {
1666 min_total_break = CapAdd(min_total_break, br->DurationMin());
1667 }
1668 }
1669 if (min_total_break > slack_max) return false;
1670 min_total_slack = std::max(min_total_slack, min_total_break);
1671 }
1672 if (filter_vehicle_costs) {
1673 cumul_cost_delta =
1674 CapAdd(cumul_cost_delta,
1675 CapProd(vehicle_total_slack_cost_coefficients_[vehicle],
1676 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
1732bool 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, path_accessor_,
1855 filter_objective_cost_ ? &path_delta_cost_with_lp : nullptr);
1856 if (status == DimensionSchedulingStatus::INFEASIBLE) {
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, path_accessor_,
1889 filter_objective_cost_ ? &path_delta_cost_with_mp : nullptr) ==
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
1909void 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
1916bool PathCumulFilter::PickupToDeliveryLimitsRespected(
1917 const PathTransits& path_transits, int path,
1918 absl::Span<const 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<RoutingModel::PickupDeliveryPosition>& pickup_positions =
1937 routing_model_.GetPickupPositions(node_index);
1938 const std::vector<RoutingModel::PickupDeliveryPosition>&
1939 delivery_positions = routing_model_.GetDeliveryPositions(node_index);
1940 if (!pickup_positions.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_positions.empty());
1945 DCHECK_EQ(pickup_positions.size(), 1);
1946 const auto [pair_index, pickup_alternative_index] = pickup_positions[0];
1947 // Get the delivery visited for this pair.
1948 const int delivery_alternative_index =
1949 visited_delivery_and_min_cumul_per_pair[pair_index].first;
1950 if (delivery_alternative_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_alternative_index, delivery_alternative_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_positions.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_positions.empty());
1966 DCHECK_EQ(delivery_positions.size(), 1);
1967 const auto [pair_index, delivery_alternative_index] =
1968 delivery_positions[0];
1969 std::pair<int, int64_t>& delivery_index_and_cumul =
1970 visited_delivery_and_min_cumul_per_pair[pair_index];
1971 int& delivery_index = delivery_index_and_cumul.first;
1972 DCHECK_EQ(delivery_index, -1);
1973 delivery_index = delivery_alternative_index;
1974 delivery_index_and_cumul.second = min_path_cumuls[i];
1975 }
1976 }
1977 return true;
1978}
1979
1980void PathCumulFilter::StoreMinMaxCumulOfNodesOnPath(
1981 int path, absl::Span<const int64_t> min_path_cumuls, bool is_delta) {
1982 const PathTransits& path_transits =
1983 is_delta ? delta_path_transits_ : current_path_transits_;
1984
1985 const int path_size = path_transits.PathSize(path);
1986 DCHECK_EQ(min_path_cumuls.size(), path_size);
1987
1988 int64_t max_cumul = cumuls_[path_transits.Node(path, path_size - 1)]->Max();
1989 for (int i = path_size - 1; i >= 0; i--) {
1990 const int node_index = path_transits.Node(path, i);
1991
1992 if (i < path_size - 1) {
1993 max_cumul = CapSub(max_cumul, path_transits.Transit(path, i));
1994 max_cumul = std::min(cumuls_[node_index]->Max(), max_cumul);
1995 }
1996
1997 if (is_delta && node_index_to_precedences_[node_index].empty()) {
1998 // No need to update the delta cumul map for nodes without precedences.
1999 continue;
2000 }
2001
2002 std::pair<int64_t, int64_t>& min_max_cumuls =
2003 is_delta ? node_with_precedence_to_delta_min_max_cumuls_[node_index]
2004 : current_min_max_node_cumuls_[node_index];
2005 min_max_cumuls.first = min_path_cumuls[i];
2006 min_max_cumuls.second = max_cumul;
2007
2008 if (is_delta && !routing_model_.IsEnd(node_index) &&
2009 (min_max_cumuls.first !=
2010 current_min_max_node_cumuls_[node_index].first ||
2011 max_cumul != current_min_max_node_cumuls_[node_index].second)) {
2012 delta_nodes_with_precedences_and_changed_cumul_.Set(node_index);
2013 }
2014 }
2015}
2016
2017int64_t PathCumulFilter::ComputePathMaxStartFromEndCumul(
2018 const PathTransits& path_transits, int path, int64_t path_start,
2019 int64_t min_end_cumul) const {
2020 int64_t cumul_from_min_end = min_end_cumul;
2021 int64_t cumul_from_max_end =
2022 cumuls_[routing_model_.End(start_to_vehicle_[path_start])]->Max();
2023 for (int i = path_transits.PathSize(path) - 2; i >= 0; --i) {
2024 const int64_t transit = path_transits.Transit(path, i);
2025 const int64_t node = path_transits.Node(path, i);
2026 cumul_from_min_end =
2027 std::min(cumuls_[node]->Max(), CapSub(cumul_from_min_end, transit));
2028 cumul_from_max_end = dimension_.GetLastPossibleLessOrEqualValueForNode(
2029 node, CapSub(cumul_from_max_end, transit));
2030 }
2031 return std::min(cumul_from_min_end, cumul_from_max_end);
2032}
2033
2034} // namespace
2035
2036IntVarLocalSearchFilter* MakePathCumulFilter(const RoutingDimension& dimension,
2037 bool propagate_own_objective_value,
2038 bool filter_objective_cost,
2039 bool can_use_lp) {
2040 RoutingModel& model = *dimension.model();
2041 return model.solver()->RevAlloc(
2042 new PathCumulFilter(model, dimension, propagate_own_objective_value,
2043 filter_objective_cost, can_use_lp));
2044}
2045
2046namespace {
2047
2048bool DimensionHasCumulCost(const RoutingDimension& dimension) {
2049 if (dimension.global_span_cost_coefficient() != 0) return true;
2050 if (dimension.HasSoftSpanUpperBounds()) return true;
2051 if (dimension.HasQuadraticCostSoftSpanUpperBounds()) return true;
2052 if (absl::c_any_of(dimension.vehicle_span_cost_coefficients(),
2053 [](int64_t coefficient) { return coefficient != 0; })) {
2054 return true;
2055 }
2056 if (absl::c_any_of(dimension.vehicle_slack_cost_coefficients(),
2057 [](int64_t coefficient) { return coefficient != 0; })) {
2058 return true;
2059 }
2060 for (int i = 0; i < dimension.cumuls().size(); ++i) {
2061 if (dimension.HasCumulVarSoftUpperBound(i)) return true;
2062 if (dimension.HasCumulVarSoftLowerBound(i)) return true;
2063 if (dimension.HasCumulVarPiecewiseLinearCost(i)) return true;
2064 }
2065 return false;
2066}
2067
2068bool DimensionHasPathCumulConstraint(const RoutingDimension& dimension) {
2069 if (dimension.HasBreakConstraints()) return true;
2070 if (dimension.HasPickupToDeliveryLimits()) return true;
2071 if (absl::c_any_of(
2072 dimension.vehicle_span_upper_bounds(), [](int64_t upper_bound) {
2073 return upper_bound != std::numeric_limits<int64_t>::max();
2074 })) {
2075 return true;
2076 }
2077 if (absl::c_any_of(dimension.slacks(),
2078 [](IntVar* slack) { return slack->Min() > 0; })) {
2079 return true;
2080 }
2081 const std::vector<IntVar*>& cumuls = dimension.cumuls();
2082 for (int i = 0; i < cumuls.size(); ++i) {
2083 IntVar* const cumul_var = cumuls[i];
2084 if (cumul_var->Min() > 0 &&
2085 cumul_var->Max() < std::numeric_limits<int64_t>::max() &&
2086 !dimension.model()->IsEnd(i)) {
2087 return true;
2088 }
2089 if (dimension.forbidden_intervals()[i].NumIntervals() > 0) return true;
2090 }
2091 return false;
2092}
2093
2094} // namespace
2095
2097 const PathState* path_state,
2098 const std::vector<RoutingDimension*>& dimensions,
2099 std::vector<LocalSearchFilterManager::FilterEvent>* filters) {
2100 using Interval = DimensionChecker::Interval;
2101 // For every dimension that fits, add a DimensionChecker.
2102 // Add a DimensionChecker for every dimension.
2103 for (const RoutingDimension* dimension : dimensions) {
2104 // Fill path capacities and classes.
2105 const int num_vehicles = dimension->model()->vehicles();
2106 std::vector<Interval> path_capacity(num_vehicles);
2107 std::vector<int> path_class(num_vehicles);
2108 for (int v = 0; v < num_vehicles; ++v) {
2109 const auto& vehicle_capacities = dimension->vehicle_capacities();
2110 path_capacity[v] = {0, vehicle_capacities[v]};
2111 path_class[v] = dimension->vehicle_to_class(v);
2112 }
2113 // For each class, retrieve the demands of each node.
2114 // Dimension store evaluators with a double indirection for compacity:
2115 // vehicle -> vehicle_class -> evaluator_index.
2116 // We replicate this in DimensionChecker,
2117 // except we expand evaluator_index to an array of values for all nodes.
2118 const int num_vehicle_classes =
2119 1 + *std::max_element(path_class.begin(), path_class.end());
2120 const int num_cumuls = dimension->cumuls().size();
2121 const int num_slacks = dimension->slacks().size();
2122 std::vector<std::function<Interval(int64_t, int64_t)>> transits(
2123 num_vehicle_classes, nullptr);
2124 for (int vehicle = 0; vehicle < num_vehicles; ++vehicle) {
2125 const int vehicle_class = path_class[vehicle];
2126 if (transits[vehicle_class] != nullptr) continue;
2127 const auto& unary_evaluator =
2128 dimension->GetUnaryTransitEvaluator(vehicle);
2129 if (unary_evaluator != nullptr) {
2130 transits[vehicle_class] = [&unary_evaluator, dimension, num_slacks](
2131 int64_t node, int64_t) -> Interval {
2132 if (node >= num_slacks) return {0, 0};
2133 const int64_t min_transit = unary_evaluator(node);
2134 const int64_t max_transit =
2135 CapAdd(min_transit, dimension->SlackVar(node)->Max());
2136 return {min_transit, max_transit};
2137 };
2138 } else {
2139 const auto& binary_evaluator =
2140 dimension->GetBinaryTransitEvaluator(vehicle);
2141
2142 transits[vehicle_class] = [&binary_evaluator, dimension, num_slacks](
2143 int64_t node, int64_t next) -> Interval {
2144 if (node >= num_slacks) return {0, 0};
2145 const int64_t min_transit = binary_evaluator(node, next);
2146 const int64_t max_transit =
2147 CapAdd(min_transit, dimension->SlackVar(node)->Max());
2148 return {min_transit, max_transit};
2149 };
2150 }
2151 }
2152 // Fill node capacities.
2153 std::vector<Interval> node_capacity(num_cumuls);
2154 for (int node = 0; node < num_cumuls; ++node) {
2155 const IntVar* cumul = dimension->CumulVar(node);
2156 node_capacity[node] = {cumul->Min(), cumul->Max()};
2157 }
2158 // Make the dimension checker and pass ownership to the filter.
2159 auto checker = std::make_unique<DimensionChecker>(
2160 path_state, std::move(path_capacity), std::move(path_class),
2161 std::move(transits), std::move(node_capacity));
2162 const auto kAccept = LocalSearchFilterManager::FilterEventType::kAccept;
2164 dimension->model()->solver(), std::move(checker), dimension->name());
2165 filters->push_back({filter, kAccept});
2166 }
2167}
2168
2170 const std::vector<RoutingDimension*>& dimensions,
2171 const RoutingSearchParameters& parameters, bool filter_objective_cost,
2172 bool use_chain_cumul_filter,
2173 std::vector<LocalSearchFilterManager::FilterEvent>* filters) {
2174 const auto kAccept = LocalSearchFilterManager::FilterEventType::kAccept;
2175 // Filter priority depth increases with complexity of filtering.
2176 // - Dimensions without any cumul-related costs or constraints will have a
2177 // ChainCumulFilter, lowest priority depth.
2178 // - Dimensions with cumul costs or constraints, but no global span cost
2179 // and/or precedences will have a PathCumulFilter.
2180 // - Dimensions with a global span cost coefficient and/or precedences will
2181 // have a global LP filter.
2182 const int num_dimensions = dimensions.size();
2183
2184 const bool has_dimension_optimizers =
2185 !parameters.disable_scheduling_beware_this_may_degrade_performance();
2186 std::vector<bool> use_path_cumul_filter(num_dimensions);
2187 std::vector<bool> use_cumul_bounds_propagator_filter(num_dimensions);
2188 std::vector<bool> use_global_lp_filter(num_dimensions);
2189 std::vector<bool> use_resource_assignment_filter(num_dimensions);
2190 for (int d = 0; d < num_dimensions; d++) {
2191 const RoutingDimension& dimension = *dimensions[d];
2192 const bool has_cumul_cost = DimensionHasCumulCost(dimension);
2193 use_path_cumul_filter[d] =
2194 has_cumul_cost || DimensionHasPathCumulConstraint(dimension);
2195
2196 const int num_dimension_resource_groups =
2197 dimension.model()->GetDimensionResourceGroupIndices(&dimension).size();
2198 const bool can_use_cumul_bounds_propagator_filter =
2199 !dimension.HasBreakConstraints() &&
2200 num_dimension_resource_groups == 0 &&
2201 (!filter_objective_cost || !has_cumul_cost);
2202 const bool has_precedences = !dimension.GetNodePrecedences().empty();
2203 use_global_lp_filter[d] =
2204 has_dimension_optimizers &&
2205 ((has_precedences && !can_use_cumul_bounds_propagator_filter) ||
2206 (filter_objective_cost &&
2207 dimension.global_span_cost_coefficient() > 0) ||
2208 num_dimension_resource_groups > 1);
2209
2210 use_cumul_bounds_propagator_filter[d] =
2211 has_precedences && !use_global_lp_filter[d];
2212
2213 use_resource_assignment_filter[d] =
2214 has_dimension_optimizers && num_dimension_resource_groups > 0;
2215 }
2216
2217 for (int d = 0; d < num_dimensions; d++) {
2218 const RoutingDimension& dimension = *dimensions[d];
2219 const RoutingModel& model = *dimension.model();
2220 // NOTE: We always add the [Chain|Path]CumulFilter to filter each route's
2221 // feasibility separately to try and cut bad decisions earlier in the
2222 // search, but we don't propagate the computed cost if the LPCumulFilter is
2223 // already doing it.
2224 const bool use_global_lp = use_global_lp_filter[d];
2225 const bool filter_resource_assignment = use_resource_assignment_filter[d];
2226 if (use_path_cumul_filter[d]) {
2227 filters->push_back(
2228 {MakePathCumulFilter(dimension, /*propagate_own_objective_value*/
2229 !use_global_lp && !filter_resource_assignment,
2230 filter_objective_cost, has_dimension_optimizers),
2231 kAccept, /*priority*/ 0});
2232 } else if (use_chain_cumul_filter) {
2233 filters->push_back(
2234 {model.solver()->RevAlloc(new ChainCumulFilter(model, dimension)),
2235 kAccept, /*priority*/ 0});
2236 }
2237
2238 if (use_cumul_bounds_propagator_filter[d]) {
2239 DCHECK(!use_global_lp);
2240 DCHECK(!filter_resource_assignment);
2241 filters->push_back({MakeCumulBoundsPropagatorFilter(dimension), kAccept,
2242 /*priority*/ 1});
2243 }
2244
2245 if (filter_resource_assignment) {
2246 filters->push_back({MakeResourceAssignmentFilter(
2247 model.GetMutableLocalCumulLPOptimizer(dimension),
2248 model.GetMutableLocalCumulMPOptimizer(dimension),
2249 /*propagate_own_objective_value*/ !use_global_lp,
2250 filter_objective_cost),
2251 kAccept, /*priority*/ 2});
2252 }
2253
2254 if (use_global_lp) {
2255 filters->push_back({MakeGlobalLPCumulFilter(
2256 model.GetMutableGlobalCumulLPOptimizer(dimension),
2257 model.GetMutableGlobalCumulMPOptimizer(dimension),
2258 filter_objective_cost),
2259 kAccept, /*priority*/ 3});
2260 }
2261 }
2262}
2263
2264namespace {
2265
2266// Filter for pickup/delivery precedences.
2267class PickupDeliveryFilter : public BasePathFilter {
2268 public:
2269 PickupDeliveryFilter(const std::vector<IntVar*>& nexts, int next_domain_size,
2270 const std::vector<PickupDeliveryPair>& pairs,
2271 const std::vector<RoutingModel::PickupAndDeliveryPolicy>&
2272 vehicle_policies);
2273 ~PickupDeliveryFilter() override {}
2274 bool AcceptPath(int64_t path_start, int64_t chain_start,
2275 int64_t chain_end) override;
2276 std::string DebugString() const override { return "PickupDeliveryFilter"; }
2277
2278 private:
2279 bool AcceptPathDefault(int64_t path_start);
2280 template <bool lifo>
2281 bool AcceptPathOrdered(int64_t path_start);
2282
2283 std::vector<int> pair_firsts_;
2284 std::vector<int> pair_seconds_;
2285 const std::vector<PickupDeliveryPair> pairs_;
2286 SparseBitset<> visited_;
2287 std::deque<int> visited_deque_;
2288 const std::vector<RoutingModel::PickupAndDeliveryPolicy> vehicle_policies_;
2289};
2290
2291PickupDeliveryFilter::PickupDeliveryFilter(
2292 const std::vector<IntVar*>& nexts, int next_domain_size,
2293 const std::vector<PickupDeliveryPair>& pairs,
2294 const std::vector<RoutingModel::PickupAndDeliveryPolicy>& vehicle_policies)
2295 : BasePathFilter(nexts, next_domain_size),
2296 pair_firsts_(next_domain_size, kUnassigned),
2297 pair_seconds_(next_domain_size, kUnassigned),
2298 pairs_(pairs),
2299 visited_(Size()),
2300 vehicle_policies_(vehicle_policies) {
2301 for (int i = 0; i < pairs.size(); ++i) {
2302 const auto& index_pair = pairs[i];
2303 for (int first : index_pair.pickup_alternatives) {
2304 pair_firsts_[first] = i;
2305 }
2306 for (int second : index_pair.delivery_alternatives) {
2307 pair_seconds_[second] = i;
2308 }
2309 }
2310}
2311
2312bool PickupDeliveryFilter::AcceptPath(int64_t path_start,
2313 int64_t /*chain_start*/,
2314 int64_t /*chain_end*/) {
2315 switch (vehicle_policies_[GetPath(path_start)]) {
2316 case RoutingModel::PICKUP_AND_DELIVERY_NO_ORDER:
2317 return AcceptPathDefault(path_start);
2318 case RoutingModel::PICKUP_AND_DELIVERY_LIFO:
2319 return AcceptPathOrdered<true>(path_start);
2320 case RoutingModel::PICKUP_AND_DELIVERY_FIFO:
2321 return AcceptPathOrdered<false>(path_start);
2322 default:
2323 return true;
2324 }
2325}
2326
2327bool PickupDeliveryFilter::AcceptPathDefault(int64_t path_start) {
2328 visited_.ClearAll();
2329 int64_t node = path_start;
2330 int64_t path_length = 1;
2331 while (node < Size()) {
2332 // Detect sub-cycles (path is longer than longest possible path).
2333 if (path_length > Size()) {
2334 return false;
2335 }
2336 if (pair_firsts_[node] != kUnassigned) {
2337 // Checking on pair firsts is not actually necessary (inconsistencies
2338 // will get caught when checking pair seconds); doing it anyway to
2339 // cut checks early.
2340 for (int second : pairs_[pair_firsts_[node]].delivery_alternatives) {
2341 if (visited_[second]) {
2342 return false;
2343 }
2344 }
2345 }
2346 if (pair_seconds_[node] != kUnassigned) {
2347 bool found_first = false;
2348 bool some_synced = false;
2349 for (int first : pairs_[pair_seconds_[node]].pickup_alternatives) {
2350 if (visited_[first]) {
2351 found_first = true;
2352 break;
2353 }
2354 if (IsVarSynced(first)) {
2355 some_synced = true;
2356 }
2357 }
2358 if (!found_first && some_synced) {
2359 return false;
2360 }
2361 }
2362 visited_.Set(node);
2363 const int64_t next = GetNext(node);
2364 if (next == kUnassigned) {
2365 // LNS detected, return true since path was ok up to now.
2366 return true;
2367 }
2368 node = next;
2369 ++path_length;
2370 }
2371 for (const int64_t node : visited_.PositionsSetAtLeastOnce()) {
2372 if (pair_firsts_[node] != kUnassigned) {
2373 bool found_second = false;
2374 bool some_synced = false;
2375 for (int second : pairs_[pair_firsts_[node]].delivery_alternatives) {
2376 if (visited_[second]) {
2377 found_second = true;
2378 break;
2379 }
2380 if (IsVarSynced(second)) {
2381 some_synced = true;
2382 }
2383 }
2384 if (!found_second && some_synced) {
2385 return false;
2386 }
2387 }
2388 }
2389 return true;
2390}
2391
2392template <bool lifo>
2393bool PickupDeliveryFilter::AcceptPathOrdered(int64_t path_start) {
2394 visited_deque_.clear();
2395 int64_t node = path_start;
2396 int64_t path_length = 1;
2397 while (node < Size()) {
2398 // Detect sub-cycles (path is longer than longest possible path).
2399 if (path_length > Size()) {
2400 return false;
2401 }
2402 if (pair_firsts_[node] != kUnassigned) {
2403 if (lifo) {
2404 visited_deque_.push_back(node);
2405 } else {
2406 visited_deque_.push_front(node);
2407 }
2408 }
2409 if (pair_seconds_[node] != kUnassigned) {
2410 bool found_first = false;
2411 bool some_synced = false;
2412 for (int first : pairs_[pair_seconds_[node]].pickup_alternatives) {
2413 if (!visited_deque_.empty() && visited_deque_.back() == first) {
2414 found_first = true;
2415 break;
2416 }
2417 if (IsVarSynced(first)) {
2418 some_synced = true;
2419 }
2420 }
2421 if (!found_first && some_synced) {
2422 return false;
2423 } else if (!visited_deque_.empty()) {
2424 visited_deque_.pop_back();
2425 }
2426 }
2427 const int64_t next = GetNext(node);
2428 if (next == kUnassigned) {
2429 // LNS detected, return true since path was ok up to now.
2430 return true;
2431 }
2432 node = next;
2433 ++path_length;
2434 }
2435 while (!visited_deque_.empty()) {
2436 for (int second :
2437 pairs_[pair_firsts_[visited_deque_.back()]].delivery_alternatives) {
2438 if (IsVarSynced(second)) {
2439 return false;
2440 }
2441 }
2442 visited_deque_.pop_back();
2443 }
2444 return true;
2445}
2446
2447} // namespace
2448
2450 const RoutingModel& routing_model,
2451 const std::vector<PickupDeliveryPair>& pairs,
2452 const std::vector<RoutingModel::PickupAndDeliveryPolicy>&
2453 vehicle_policies) {
2454 return routing_model.solver()->RevAlloc(new PickupDeliveryFilter(
2455 routing_model.Nexts(), routing_model.Size() + routing_model.vehicles(),
2456 pairs, vehicle_policies));
2457}
2458
2459namespace {
2460
2461// Vehicle variable filter
2462class VehicleVarFilter : public BasePathFilter {
2463 public:
2464 explicit VehicleVarFilter(const RoutingModel& routing_model);
2465 ~VehicleVarFilter() override {}
2466 bool AcceptPath(int64_t path_start, int64_t chain_start,
2467 int64_t chain_end) override;
2468 std::string DebugString() const override { return "VehicleVariableFilter"; }
2469
2470 private:
2471 bool DisableFiltering() const override;
2472 bool IsVehicleVariableConstrained(int index) const;
2473
2474 std::vector<int64_t> start_to_vehicle_;
2475 std::vector<IntVar*> vehicle_vars_;
2476 const int64_t unconstrained_vehicle_var_domain_size_;
2477 SparseBitset<int> touched_;
2478};
2479
2480VehicleVarFilter::VehicleVarFilter(const RoutingModel& routing_model)
2481 : BasePathFilter(routing_model.Nexts(),
2482 routing_model.Size() + routing_model.vehicles()),
2483 vehicle_vars_(routing_model.VehicleVars()),
2484 unconstrained_vehicle_var_domain_size_(routing_model.vehicles()),
2485 touched_(routing_model.Nexts().size()) {
2486 start_to_vehicle_.resize(Size(), -1);
2487 for (int i = 0; i < routing_model.vehicles(); ++i) {
2488 start_to_vehicle_[routing_model.Start(i)] = i;
2489 }
2490}
2491
2492bool VehicleVarFilter::AcceptPath(int64_t path_start, int64_t chain_start,
2493 int64_t chain_end) {
2494 touched_.SparseClearAll();
2495 const int64_t vehicle = start_to_vehicle_[path_start];
2496 int64_t node = chain_start;
2497 while (node != chain_end) {
2498 if (touched_[node] || !vehicle_vars_[node]->Contains(vehicle)) {
2499 return false;
2500 }
2501 touched_.Set(node);
2502 node = GetNext(node);
2503 }
2504 return vehicle_vars_[node]->Contains(vehicle);
2505}
2506
2507bool VehicleVarFilter::DisableFiltering() const {
2508 for (int i = 0; i < vehicle_vars_.size(); ++i) {
2509 if (IsVehicleVariableConstrained(i)) return false;
2510 }
2511 return true;
2512}
2513
2514bool VehicleVarFilter::IsVehicleVariableConstrained(int index) const {
2515 const IntVar* const vehicle_var = vehicle_vars_[index];
2516 // If vehicle variable contains -1 (optional node), then we need to
2517 // add it to the "unconstrained" domain. Impact we don't filter mandatory
2518 // nodes made inactive here, but it is covered by other filters.
2519 const int adjusted_unconstrained_vehicle_var_domain_size =
2520 vehicle_var->Min() >= 0 ? unconstrained_vehicle_var_domain_size_
2521 : unconstrained_vehicle_var_domain_size_ + 1;
2522 return vehicle_var->Size() != adjusted_unconstrained_vehicle_var_domain_size;
2523}
2524
2525} // namespace
2526
2528 const RoutingModel& routing_model) {
2529 return routing_model.solver()->RevAlloc(new VehicleVarFilter(routing_model));
2530}
2531
2532namespace {
2533
2534class CumulBoundsPropagatorFilter : public IntVarLocalSearchFilter {
2535 public:
2536 explicit CumulBoundsPropagatorFilter(const RoutingDimension& dimension);
2537 bool Accept(const Assignment* delta, const Assignment* deltadelta,
2538 int64_t objective_min, int64_t objective_max) override;
2539 std::string DebugString() const override {
2540 return "CumulBoundsPropagatorFilter(" + propagator_.dimension().name() +
2541 ")";
2542 }
2543
2544 private:
2545 CumulBoundsPropagator propagator_;
2546 const int64_t cumul_offset_;
2547 SparseBitset<int64_t> delta_touched_;
2548 std::vector<int64_t> delta_nexts_;
2549};
2550
2551CumulBoundsPropagatorFilter::CumulBoundsPropagatorFilter(
2552 const RoutingDimension& dimension)
2553 : IntVarLocalSearchFilter(dimension.model()->Nexts()),
2554 propagator_(&dimension),
2555 cumul_offset_(dimension.GetGlobalOptimizerOffset()),
2556 delta_touched_(Size()),
2557 delta_nexts_(Size()) {}
2558
2559bool CumulBoundsPropagatorFilter::Accept(const Assignment* delta,
2560 const Assignment* /*deltadelta*/,
2561 int64_t /*objective_min*/,
2562 int64_t /*objective_max*/) {
2563 delta_touched_.ClearAll();
2564 for (const IntVarElement& delta_element :
2565 delta->IntVarContainer().elements()) {
2566 int64_t index = -1;
2567 if (FindIndex(delta_element.Var(), &index)) {
2568 if (!delta_element.Bound()) {
2569 // LNS detected
2570 return true;
2571 }
2572 delta_touched_.Set(index);
2573 delta_nexts_[index] = delta_element.Value();
2574 }
2575 }
2576 const auto& next_accessor = [this](int64_t index) {
2577 return delta_touched_[index] ? delta_nexts_[index] : Value(index);
2578 };
2579
2580 return propagator_.PropagateCumulBounds(next_accessor, cumul_offset_);
2581}
2582
2583} // namespace
2584
2586 const RoutingDimension& dimension) {
2587 return dimension.model()->solver()->RevAlloc(
2588 new CumulBoundsPropagatorFilter(dimension));
2589}
2590
2591namespace {
2592
2593class LPCumulFilter : public IntVarLocalSearchFilter {
2594 public:
2595 LPCumulFilter(const std::vector<IntVar*>& nexts,
2596 GlobalDimensionCumulOptimizer* optimizer,
2597 GlobalDimensionCumulOptimizer* mp_optimizer,
2598 bool filter_objective_cost);
2599 bool Accept(const Assignment* delta, const Assignment* deltadelta,
2600 int64_t objective_min, int64_t objective_max) override;
2601 int64_t GetAcceptedObjectiveValue() const override;
2602 void OnSynchronize(const Assignment* delta) override;
2603 int64_t GetSynchronizedObjectiveValue() const override;
2604 std::string DebugString() const override {
2605 return "LPCumulFilter(" + optimizer_.dimension()->name() + ")";
2606 }
2607
2608 private:
2609 GlobalDimensionCumulOptimizer& optimizer_;
2610 GlobalDimensionCumulOptimizer& mp_optimizer_;
2611 const bool filter_objective_cost_;
2612 int64_t synchronized_cost_without_transit_;
2613 int64_t delta_cost_without_transit_;
2614 SparseBitset<int64_t> delta_touched_;
2615 std::vector<int64_t> delta_nexts_;
2616};
2617
2618LPCumulFilter::LPCumulFilter(const std::vector<IntVar*>& nexts,
2619 GlobalDimensionCumulOptimizer* optimizer,
2620 GlobalDimensionCumulOptimizer* mp_optimizer,
2621 bool filter_objective_cost)
2622 : IntVarLocalSearchFilter(nexts),
2623 optimizer_(*optimizer),
2624 mp_optimizer_(*mp_optimizer),
2625 filter_objective_cost_(filter_objective_cost),
2626 synchronized_cost_without_transit_(-1),
2627 delta_cost_without_transit_(-1),
2628 delta_touched_(Size()),
2629 delta_nexts_(Size()) {}
2630
2631bool LPCumulFilter::Accept(const Assignment* delta,
2632 const Assignment* /*deltadelta*/,
2633 int64_t /*objective_min*/, int64_t objective_max) {
2634 delta_touched_.ClearAll();
2635 for (const IntVarElement& delta_element :
2636 delta->IntVarContainer().elements()) {
2637 int64_t index = -1;
2638 if (FindIndex(delta_element.Var(), &index)) {
2639 if (!delta_element.Bound()) {
2640 // LNS detected
2641 return true;
2642 }
2643 delta_touched_.Set(index);
2644 delta_nexts_[index] = delta_element.Value();
2645 }
2646 }
2647 const auto& next_accessor = [this](int64_t index) {
2648 return delta_touched_[index] ? delta_nexts_[index] : Value(index);
2649 };
2650
2651 if (!filter_objective_cost_) {
2652 // No need to compute the cost of the LP, only verify its feasibility.
2653 delta_cost_without_transit_ = 0;
2655 optimizer_.ComputeCumuls(next_accessor, {}, nullptr, nullptr, nullptr);
2656 if (status == DimensionSchedulingStatus::OPTIMAL) return true;
2657 if (status == DimensionSchedulingStatus::RELAXED_OPTIMAL_ONLY &&
2658 mp_optimizer_.ComputeCumuls(next_accessor, {}, nullptr, nullptr,
2659 nullptr) ==
2660 DimensionSchedulingStatus::OPTIMAL) {
2661 return true;
2662 }
2663 return false;
2664 }
2665
2667 optimizer_.ComputeCumulCostWithoutFixedTransits(
2668 next_accessor, &delta_cost_without_transit_);
2669 if (status == DimensionSchedulingStatus::INFEASIBLE) {
2670 delta_cost_without_transit_ = std::numeric_limits<int64_t>::max();
2671 return false;
2672 }
2673 if (delta_cost_without_transit_ > objective_max) return false;
2674
2675 if (status == DimensionSchedulingStatus::RELAXED_OPTIMAL_ONLY &&
2676 mp_optimizer_.ComputeCumulCostWithoutFixedTransits(
2677 next_accessor, &delta_cost_without_transit_) !=
2678 DimensionSchedulingStatus::OPTIMAL) {
2679 delta_cost_without_transit_ = std::numeric_limits<int64_t>::max();
2680 return false;
2681 }
2682 return delta_cost_without_transit_ <= objective_max;
2683}
2684
2685int64_t LPCumulFilter::GetAcceptedObjectiveValue() const {
2686 return delta_cost_without_transit_;
2687}
2688
2689void LPCumulFilter::OnSynchronize(const Assignment* /*delta*/) {
2690 // TODO(user): Try to optimize this so the LP is not called when the last
2691 // computed delta cost corresponds to the solution being synchronized.
2692 const RoutingModel& model = *optimizer_.dimension()->model();
2693 const auto& next_accessor = [this, &model](int64_t index) {
2694 return IsVarSynced(index) ? Value(index)
2695 : model.IsStart(index) ? model.End(model.VehicleIndex(index))
2696 : index;
2697 };
2698
2700 optimizer_.ComputeCumulCostWithoutFixedTransits(
2701 next_accessor, &synchronized_cost_without_transit_);
2702 if (status == DimensionSchedulingStatus::INFEASIBLE) {
2703 // TODO(user): This should only happen if the LP solver times out.
2704 // DCHECK the fail wasn't due to an infeasible model.
2705 synchronized_cost_without_transit_ = 0;
2706 }
2707 if (status == DimensionSchedulingStatus::RELAXED_OPTIMAL_ONLY &&
2708 mp_optimizer_.ComputeCumulCostWithoutFixedTransits(
2709 next_accessor, &synchronized_cost_without_transit_) !=
2710 DimensionSchedulingStatus::OPTIMAL) {
2711 // TODO(user): This should only happen if the MP solver times out.
2712 // DCHECK the fail wasn't due to an infeasible model.
2713 synchronized_cost_without_transit_ = 0;
2714 }
2715}
2716
2717int64_t LPCumulFilter::GetSynchronizedObjectiveValue() const {
2718 return synchronized_cost_without_transit_;
2719}
2720
2721} // namespace
2722
2725 GlobalDimensionCumulOptimizer* mp_optimizer, bool filter_objective_cost) {
2726 DCHECK_NE(optimizer, nullptr);
2727 DCHECK_NE(mp_optimizer, nullptr);
2728 const RoutingModel& model = *optimizer->dimension()->model();
2729 return model.solver()->RevAlloc(new LPCumulFilter(
2730 model.Nexts(), optimizer, mp_optimizer, filter_objective_cost));
2731}
2732
2733namespace {
2734
2735using ResourceGroup = RoutingModel::ResourceGroup;
2736
2737class ResourceGroupAssignmentFilter : public BasePathFilter {
2738 public:
2739 ResourceGroupAssignmentFilter(const std::vector<IntVar*>& nexts,
2740 const ResourceGroup* resource_group,
2741 LocalDimensionCumulOptimizer* lp_optimizer,
2742 LocalDimensionCumulOptimizer* mp_optimizer,
2743 bool filter_objective_cost);
2744 bool InitializeAcceptPath() override;
2745 bool AcceptPath(int64_t path_start, int64_t chain_start,
2746 int64_t chain_end) override;
2747 bool FinalizeAcceptPath(int64_t objective_min,
2748 int64_t objective_max) override;
2749 void OnBeforeSynchronizePaths() override;
2750 void OnSynchronizePathFromStart(int64_t start) override;
2751 void OnAfterSynchronizePaths() override;
2752
2753 int64_t GetAcceptedObjectiveValue() const override {
2754 return lns_detected() ? 0 : delta_cost_without_transit_;
2755 }
2756 int64_t GetSynchronizedObjectiveValue() const override {
2757 return synchronized_cost_without_transit_;
2758 }
2759 std::string DebugString() const override {
2760 return "ResourceGroupAssignmentFilter(" + dimension_.name() + ")";
2761 }
2762
2763 private:
2764 const RoutingModel& model_;
2765 const RoutingDimension& dimension_;
2766 const ResourceGroup& resource_group_;
2767 LocalDimensionCumulOptimizer* lp_optimizer_;
2768 LocalDimensionCumulOptimizer* mp_optimizer_;
2769 const bool filter_objective_cost_;
2770 bool current_synch_failed_;
2771 int64_t synchronized_cost_without_transit_;
2772 int64_t delta_cost_without_transit_;
2773 std::vector<std::vector<int64_t>> vehicle_to_resource_class_assignment_costs_;
2774 std::vector<std::vector<int64_t>>
2775 delta_vehicle_to_resource_class_assignment_costs_;
2776};
2777
2778ResourceGroupAssignmentFilter::ResourceGroupAssignmentFilter(
2779 const std::vector<IntVar*>& nexts, const ResourceGroup* resource_group,
2780 LocalDimensionCumulOptimizer* lp_optimizer,
2781 LocalDimensionCumulOptimizer* mp_optimizer, bool filter_objective_cost)
2782 : BasePathFilter(nexts, lp_optimizer->dimension()->cumuls().size()),
2783 model_(*lp_optimizer->dimension()->model()),
2784 dimension_(*lp_optimizer->dimension()),
2785 resource_group_(*resource_group),
2786 lp_optimizer_(lp_optimizer),
2787 mp_optimizer_(mp_optimizer),
2788 filter_objective_cost_(filter_objective_cost),
2789 current_synch_failed_(false),
2790 synchronized_cost_without_transit_(-1),
2791 delta_cost_without_transit_(-1) {
2792 vehicle_to_resource_class_assignment_costs_.resize(model_.vehicles());
2793 delta_vehicle_to_resource_class_assignment_costs_.resize(model_.vehicles());
2794}
2795
2796bool ResourceGroupAssignmentFilter::InitializeAcceptPath() {
2797 delta_vehicle_to_resource_class_assignment_costs_.assign(model_.vehicles(),
2798 {});
2799 // TODO(user): Keep track of num_used_vehicles internally and compute its
2800 // new value here by only going through the touched_paths_.
2801 int num_used_vehicles = 0;
2802 const int num_resources = resource_group_.Size();
2803 for (int v : resource_group_.GetVehiclesRequiringAResource()) {
2804 if (GetNext(model_.Start(v)) != model_.End(v) ||
2805 model_.IsVehicleUsedWhenEmpty(v)) {
2806 if (++num_used_vehicles > num_resources) {
2807 return false;
2808 }
2809 }
2810 }
2811 return true;
2812}
2813
2814bool ResourceGroupAssignmentFilter::AcceptPath(int64_t path_start,
2815 int64_t /*chain_start*/,
2816 int64_t /*chain_end*/) {
2817 const int vehicle = model_.VehicleIndex(path_start);
2818 // TODO(user): Make delta_vehicles_requiring_resource_assignment_ and
2819 // delta_ignored_resources_per_class_ class members, properly update them in
2820 // AcceptPath(), and delay calls to
2821 // ComputeVehicleToResourceClassAssignmentCosts() to FinalizeAcceptPath().
2822 using RCIndex = RoutingModel::ResourceClassIndex;
2824 ignored_resources_per_class(resource_group_.GetResourceClassesCount());
2826 vehicle, resource_group_, ignored_resources_per_class,
2827 [this](int64_t index) { return GetNext(index); },
2828 dimension_.transit_evaluator(vehicle), filter_objective_cost_,
2829 lp_optimizer_, mp_optimizer_,
2830 &delta_vehicle_to_resource_class_assignment_costs_[vehicle], nullptr,
2831 nullptr);
2832}
2833
2834bool ResourceGroupAssignmentFilter::FinalizeAcceptPath(
2835 int64_t /*objective_min*/, int64_t objective_max) {
2836 using RCIndex = RoutingModel::ResourceClassIndex;
2838 ignored_resources_per_class(resource_group_.GetResourceClassesCount());
2839 delta_cost_without_transit_ = ComputeBestVehicleToResourceAssignment(
2840 resource_group_.GetVehiclesRequiringAResource(),
2841 resource_group_.GetResourceIndicesPerClass(), ignored_resources_per_class,
2842 /*vehicle_to_resource_class_assignment_costs=*/
2843 [this](int v) {
2844 return PathStartTouched(model_.Start(v))
2845 ? &delta_vehicle_to_resource_class_assignment_costs_[v]
2846 : &vehicle_to_resource_class_assignment_costs_[v];
2847 },
2848 nullptr);
2849 return delta_cost_without_transit_ >= 0 &&
2850 delta_cost_without_transit_ <= objective_max;
2851}
2852
2853void ResourceGroupAssignmentFilter::OnBeforeSynchronizePaths() {
2854 current_synch_failed_ = false;
2855}
2856
2857void ResourceGroupAssignmentFilter::OnSynchronizePathFromStart(int64_t start) {
2858 // NOTE(user): Even if filter_objective_cost_ is false, we still need to
2859 // call ComputeVehicleToResourceClassAssignmentCosts() for every vehicle to
2860 // keep track of whether or not a given vehicle-to-resource-class assignment
2861 // is possible by storing 0 or -1 in
2862 // vehicle_to_resource_class_assignment_costs_.
2863 const auto& next_accessor = [this](int64_t index) {
2864 return IsVarSynced(index) ? Value(index)
2865 : model_.IsStart(index) ? model_.End(model_.VehicleIndex(index))
2866 : index;
2867 };
2868 const int v = model_.VehicleIndex(start);
2869 // TODO(user): Make vehicles_requiring_resource_assignment_ and
2870 // ignored_resources_per_class_ class members, properly update them in
2871 // OnSynchronizePathFromStart(), and delay calls to
2872 // ComputeVehicleToResourceClassAssignmentCosts() to OnAfterSynchronizePaths()
2873 using RCIndex = RoutingModel::ResourceClassIndex;
2875 ignored_resources_per_class(resource_group_.GetResourceClassesCount());
2877 v, resource_group_, ignored_resources_per_class, next_accessor,
2878 dimension_.transit_evaluator(v), filter_objective_cost_,
2879 lp_optimizer_, mp_optimizer_,
2880 &vehicle_to_resource_class_assignment_costs_[v], nullptr, nullptr)) {
2881 vehicle_to_resource_class_assignment_costs_[v].assign(
2882 resource_group_.GetResourceClassesCount(), -1);
2883 current_synch_failed_ = true;
2884 }
2885}
2886
2887void ResourceGroupAssignmentFilter::OnAfterSynchronizePaths() {
2888 using RCIndex = RoutingModel::ResourceClassIndex;
2890 ignored_resources_per_class(resource_group_.GetResourceClassesCount());
2891 synchronized_cost_without_transit_ =
2892 (current_synch_failed_ || !filter_objective_cost_)
2893 ? 0
2895 resource_group_.GetVehiclesRequiringAResource(),
2896 resource_group_.GetResourceIndicesPerClass(),
2897 ignored_resources_per_class,
2898 [this](int v) {
2899 return &vehicle_to_resource_class_assignment_costs_[v];
2900 },
2901 nullptr);
2902 synchronized_cost_without_transit_ =
2903 std::max<int64_t>(synchronized_cost_without_transit_, 0);
2904}
2905
2906// ResourceAssignmentFilter
2907class ResourceAssignmentFilter : public LocalSearchFilter {
2908 public:
2909 ResourceAssignmentFilter(const std::vector<IntVar*>& nexts,
2910 LocalDimensionCumulOptimizer* optimizer,
2911 LocalDimensionCumulOptimizer* mp_optimizer,
2912 bool propagate_own_objective_value,
2913 bool filter_objective_cost);
2914 bool Accept(const Assignment* delta, const Assignment* deltadelta,
2915 int64_t objective_min, int64_t objective_max) override;
2916 void Synchronize(const Assignment* assignment,
2917 const Assignment* delta) override;
2918
2919 int64_t GetAcceptedObjectiveValue() const override {
2920 return propagate_own_objective_value_ ? delta_cost_ : 0;
2921 }
2922 int64_t GetSynchronizedObjectiveValue() const override {
2923 return propagate_own_objective_value_ ? synchronized_cost_ : 0;
2924 }
2925 std::string DebugString() const override {
2926 return "ResourceAssignmentFilter(" + dimension_name_ + ")";
2927 }
2928
2929 private:
2930 std::vector<IntVarLocalSearchFilter*> resource_group_assignment_filters_;
2931 int64_t synchronized_cost_;
2932 int64_t delta_cost_;
2933 const bool propagate_own_objective_value_;
2934 const std::string dimension_name_;
2935};
2936
2937ResourceAssignmentFilter::ResourceAssignmentFilter(
2938 const std::vector<IntVar*>& nexts, LocalDimensionCumulOptimizer* optimizer,
2939 LocalDimensionCumulOptimizer* mp_optimizer,
2940 bool propagate_own_objective_value, bool filter_objective_cost)
2941 : propagate_own_objective_value_(propagate_own_objective_value),
2942 dimension_name_(optimizer->dimension()->name()) {
2943 const RoutingModel& model = *optimizer->dimension()->model();
2944 for (const auto& resource_group : model.GetResourceGroups()) {
2945 resource_group_assignment_filters_.push_back(
2946 model.solver()->RevAlloc(new ResourceGroupAssignmentFilter(
2947 nexts, resource_group.get(), optimizer, mp_optimizer,
2948 filter_objective_cost)));
2949 }
2950}
2951
2952bool ResourceAssignmentFilter::Accept(const Assignment* delta,
2953 const Assignment* deltadelta,
2954 int64_t objective_min,
2955 int64_t objective_max) {
2956 delta_cost_ = 0;
2957 for (LocalSearchFilter* group_filter : resource_group_assignment_filters_) {
2958 if (!group_filter->Accept(delta, deltadelta, objective_min,
2959 objective_max)) {
2960 return false;
2961 }
2962 delta_cost_ =
2963 std::max(delta_cost_, group_filter->GetAcceptedObjectiveValue());
2964 DCHECK_LE(delta_cost_, objective_max)
2965 << "ResourceGroupAssignmentFilter should return false when the "
2966 "objective_max is exceeded.";
2967 }
2968 return true;
2969}
2970
2971void ResourceAssignmentFilter::Synchronize(const Assignment* assignment,
2972 const Assignment* delta) {
2973 synchronized_cost_ = 0;
2974 for (LocalSearchFilter* group_filter : resource_group_assignment_filters_) {
2975 group_filter->Synchronize(assignment, delta);
2976 synchronized_cost_ = std::max(
2977 synchronized_cost_, group_filter->GetSynchronizedObjectiveValue());
2978 }
2979}
2980
2981} // namespace
2982
2985 LocalDimensionCumulOptimizer* mp_optimizer,
2986 bool propagate_own_objective_value, bool filter_objective_cost) {
2987 const RoutingModel& model = *optimizer->dimension()->model();
2988 DCHECK_NE(optimizer, nullptr);
2989 DCHECK_NE(mp_optimizer, nullptr);
2990 return model.solver()->RevAlloc(new ResourceAssignmentFilter(
2991 model.Nexts(), optimizer, mp_optimizer, propagate_own_objective_value,
2992 filter_objective_cost));
2993}
2994
2995namespace {
2996
2997// This filter accepts deltas for which the assignment satisfies the
2998// constraints of the Solver. This is verified by keeping an internal copy of
2999// the assignment with all Next vars and their updated values, and calling
3000// RestoreAssignment() on the assignment+delta.
3001// TODO(user): Also call the solution finalizer on variables, with the
3002// exception of Next Vars (woud fail on large instances).
3003// WARNING: In the case of mandatory nodes, when all vehicles are currently
3004// being used in the solution but uninserted nodes still remain, this filter
3005// will reject the solution, even if the node could be inserted on one of these
3006// routes, because all Next vars of vehicle starts are already instantiated.
3007// TODO(user): Avoid such false negatives.
3008
3009class CPFeasibilityFilter : public IntVarLocalSearchFilter {
3010 public:
3011 explicit CPFeasibilityFilter(RoutingModel* routing_model);
3012 ~CPFeasibilityFilter() override {}
3013 std::string DebugString() const override { return "CPFeasibilityFilter"; }
3014 bool Accept(const Assignment* delta, const Assignment* deltadelta,
3015 int64_t objective_min, int64_t objective_max) override;
3016 void OnSynchronize(const Assignment* delta) override;
3017
3018 private:
3019 void AddDeltaToAssignment(const Assignment* delta, Assignment* assignment);
3020
3021 static const int64_t kUnassigned;
3022 const RoutingModel* const model_;
3023 Solver* const solver_;
3024 Assignment* const assignment_;
3025 Assignment* const temp_assignment_;
3026 DecisionBuilder* const restore_;
3027 SearchLimit* const limit_;
3028};
3029
3030const int64_t CPFeasibilityFilter::kUnassigned = -1;
3031
3032CPFeasibilityFilter::CPFeasibilityFilter(RoutingModel* routing_model)
3033 : IntVarLocalSearchFilter(routing_model->Nexts()),
3034 model_(routing_model),
3035 solver_(routing_model->solver()),
3036 assignment_(solver_->MakeAssignment()),
3037 temp_assignment_(solver_->MakeAssignment()),
3038 restore_(solver_->MakeRestoreAssignment(temp_assignment_)),
3039 limit_(solver_->MakeCustomLimit(
3040 [routing_model]() { return routing_model->CheckLimit(); })) {
3041 assignment_->Add(routing_model->Nexts());
3042}
3043
3044bool CPFeasibilityFilter::Accept(const Assignment* delta,
3045 const Assignment* /*deltadelta*/,
3046 int64_t /*objective_min*/,
3047 int64_t /*objective_max*/) {
3048 temp_assignment_->Copy(assignment_);
3049 AddDeltaToAssignment(delta, temp_assignment_);
3050
3051 return solver_->Solve(restore_, limit_);
3052}
3053
3054void CPFeasibilityFilter::OnSynchronize(const Assignment* delta) {
3055 AddDeltaToAssignment(delta, assignment_);
3056}
3057
3058void CPFeasibilityFilter::AddDeltaToAssignment(const Assignment* delta,
3059 Assignment* assignment) {
3060 if (delta == nullptr) {
3061 return;
3062 }
3063 Assignment::IntContainer* const container =
3064 assignment->MutableIntVarContainer();
3065 for (const IntVarElement& delta_element :
3066 delta->IntVarContainer().elements()) {
3067 IntVar* const var = delta_element.Var();
3068 int64_t index = kUnassigned;
3069 // Ignoring variables found in the delta which are not next variables, such
3070 // as vehicle variables.
3071 if (!FindIndex(var, &index)) continue;
3072 DCHECK_EQ(var, Var(index));
3073 const int64_t value = delta_element.Value();
3074
3075 container->AddAtPosition(var, index)->SetValue(value);
3076 if (model_->IsStart(index)) {
3077 if (model_->IsEnd(value)) {
3078 // Do not restore unused routes.
3079 container->MutableElement(index)->Deactivate();
3080 } else {
3081 // Re-activate the route's start in case it was deactivated before.
3082 container->MutableElement(index)->Activate();
3083 }
3084 }
3085 }
3086}
3087
3088} // namespace
3089
3091 return routing_model->solver()->RevAlloc(
3092 new CPFeasibilityFilter(routing_model));
3093}
3094
3095PathEnergyCostChecker::PathEnergyCostChecker(
3096 const PathState* path_state, std::vector<int> force_class,
3097 std::vector<const std::function<int64_t(int64_t)>*> force_per_class,
3098 std::vector<int> distance_class,
3099 std::vector<const std::function<int64_t(int64_t, int64_t)>*>
3100 distance_per_class,
3101 std::vector<int64_t> path_unit_costs,
3102 std::vector<bool> path_has_cost_when_empty)
3103 : path_state_(path_state),
3104 force_class_(std::move(force_class)),
3105 distance_class_(std::move(distance_class)),
3106 force_per_class_(std::move(force_per_class)),
3107 distance_per_class_(std::move(distance_per_class)),
3108 path_unit_costs_(std::move(path_unit_costs)),
3109 path_has_cost_when_empty_(std::move(path_has_cost_when_empty)) {
3110 committed_total_cost_ = 0;
3111 committed_path_cost_.assign(path_state_->NumPaths(), 0);
3112 const int num_paths = path_state_->NumPaths();
3113 for (int path = 0; path < num_paths; ++path) {
3114 committed_path_cost_[path] = ComputePathCost(path);
3115 committed_total_cost_ =
3116 CapAdd(committed_total_cost_, committed_path_cost_[path]);
3117 }
3118 accepted_total_cost_ = committed_total_cost_;
3119}
3120
3122 if (path_state_->IsInvalid()) return true;
3123 accepted_total_cost_ = committed_total_cost_;
3124 for (const int path : path_state_->ChangedPaths()) {
3125 accepted_total_cost_ =
3126 CapSub(accepted_total_cost_, committed_path_cost_[path]);
3127 accepted_total_cost_ = CapAdd(accepted_total_cost_, ComputePathCost(path));
3128 if (accepted_total_cost_ == kint64max) return false;
3129 }
3130 return true;
3131}
3132
3134 for (const int path : path_state_->ChangedPaths()) {
3135 committed_total_cost_ =
3136 CapSub(committed_total_cost_, committed_path_cost_[path]);
3137 committed_path_cost_[path] = ComputePathCost(path);
3138 committed_total_cost_ =
3139 CapAdd(committed_total_cost_, committed_path_cost_[path]);
3140 }
3141}
3142
3143int64_t PathEnergyCostChecker::ComputePathCost(int64_t path) const {
3144 const int force_class = force_class_[path];
3145 const auto& force_evaluator = *force_per_class_[force_class];
3146 const int distance_class = distance_class_[path];
3147 const auto& distance_evaluator = *distance_per_class_[distance_class];
3148
3149 int64_t total_energy = 0;
3150 int64_t total_force = 0;
3151 int64_t min_total_force = kint64max;
3152 int64_t total_distance = 0;
3153 int num_path_nodes = 0;
3154 int prev_node = path_state_->Start(path);
3155 for (const auto chain : path_state_->Chains(path)) {
3156 num_path_nodes += chain.NumNodes();
3157 const int first = chain.First();
3158 // Add energy needed to go from prev_node to chain.First() if needed,
3159 // then add energy needed to go from chain.First() to chain.Last().
3160 for (const int node :
3161 (first == prev_node ? chain.WithoutFirstNode() : chain)) {
3162 const int64_t force = force_evaluator(prev_node);
3163 const int64_t distance = distance_evaluator(prev_node, node);
3164 total_force = CapAdd(total_force, force);
3165 total_energy = CapAdd(total_energy, CapProd(total_force, distance));
3166 total_distance = CapAdd(total_distance, distance);
3167 min_total_force = std::min(min_total_force, total_force);
3168 prev_node = node;
3169 }
3170 }
3171 // If total_force was ever < 0, offset it to 0.
3172 if (min_total_force < 0) {
3173 const int64_t offset = CapProd(total_distance, CapOpp(min_total_force));
3174 total_energy = CapAdd(total_energy, offset);
3175 }
3176 return (num_path_nodes == 2 && !path_has_cost_when_empty_[path])
3177 ? 0
3178 : CapProd(total_energy, path_unit_costs_[path]);
3179}
3180
3181namespace {
3182
3183class PathEnergyCostFilter : public LocalSearchFilter {
3184 public:
3185 std::string DebugString() const override { return name_; }
3186 PathEnergyCostFilter(std::unique_ptr<PathEnergyCostChecker> checker,
3187 absl::string_view energy_name)
3188 : checker_(std::move(checker)),
3189 name_(absl::StrCat("PathEnergyCostFilter(", energy_name, ")")) {}
3190
3191 bool Accept(const Assignment*, const Assignment*, int64_t objective_min,
3192 int64_t objective_max) override {
3193 if (objective_max > kint64max / 2) return true;
3194 if (!checker_->Check()) return false;
3195 const int64_t cost = checker_->AcceptedCost();
3196 return objective_min <= cost && cost <= objective_max;
3197 }
3198
3199 void Synchronize(const Assignment*, const Assignment*) override {
3200 checker_->Commit();
3201 }
3202
3203 int64_t GetSynchronizedObjectiveValue() const override {
3204 return checker_->CommittedCost();
3205 }
3206 int64_t GetAcceptedObjectiveValue() const override {
3207 return checker_->AcceptedCost();
3208 }
3209
3210 private:
3211 std::unique_ptr<PathEnergyCostChecker> checker_;
3212 const std::string name_;
3213};
3214
3215} // namespace
3216
3218 Solver* solver, std::unique_ptr<PathEnergyCostChecker> checker,
3219 absl::string_view dimension_name) {
3220 PathEnergyCostFilter* filter =
3221 new PathEnergyCostFilter(std::move(checker), dimension_name);
3222 return solver->RevAlloc(filter);
3223}
3224
3225// TODO(user): Implement same-vehicle filter. Could be merged with node
3226// precedence filter.
3227
3228} // namespace operations_research
int64_t min
std::vector< int > dimensions
STL vector ---------------------------------------------------------------—.
const std::vector< E > & elements() const
AssignmentContainer< IntVar, IntVarElement > IntContainer
BasePathFilter(const std::vector< IntVar * > &nexts, int next_domain_size)
bool Accept(const Assignment *delta, const Assignment *deltadelta, int64_t objective_min, int64_t objective_max) override
void OnSynchronize(const Assignment *delta) override
virtual int64_t Min() const =0
virtual int64_t Max() const =0
bool FindIndex(IntVar *const var, int64_t *index) const
IntVar * Var() override
Creates a variable from the expression.
const std::vector< int > & ChangedPaths() const
int NumPaths() const
Returns the number of paths (empty paths included).
int Start(int path) const
Returns the start of a path.
For the time being, Solver is neither MT_SAFE nor MT_HOT.
const std::vector< IntegerType > & PositionsSetAtLeastOnce() const
Definition bitset.h:859
void Set(IntegerType index)
Definition bitset.h:845
int64_t b
Definition table.cc:45
int64_t a
Definition table.cc:44
Block * next
SatParameters parameters
const std::string name
A name for logging purposes.
int64_t value
IntVar * var
const int64_t limit_
absl::Status status
Definition g_gurobi.cc:44
const std::vector< IntVar * > cumuls_
double upper_bound
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
For infeasible and unbounded see Not checked if options check_solutions_if_inf_or_unbounded and the If options first_solution_only is false
problem is infeasible or unbounded (default).
Definition matchers.h:453
std::function< int64_t(const Model &) Value)(IntegerVariable v)
This checks that the variable is fixed.
Definition integer.h:1868
In SWIG mode, we don't want anything besides these top-level includes.
IntVarLocalSearchFilter * MakeVehicleAmortizedCostFilter(const RoutingModel &routing_model)
Returns a filter computing vehicle amortized costs.
int64_t CapAdd(int64_t x, int64_t y)
IntVarLocalSearchFilter * MakeMaxActiveVehiclesFilter(const RoutingModel &routing_model)
Returns a filter ensuring that max active vehicles constraints are enforced.
void AppendLightWeightDimensionFilters(const PathState *path_state, const std::vector< RoutingDimension * > &dimensions, std::vector< LocalSearchFilterManager::FilterEvent > *filters)
IntVarLocalSearchFilter * MakeNodeDisjunctionFilter(const RoutingModel &routing_model, bool filter_cost)
Returns a filter ensuring that node disjunction constraints are enforced.
int64_t CapSub(int64_t x, int64_t y)
LocalSearchFilter * MakePathEnergyCostFilter(Solver *solver, std::unique_ptr< PathEnergyCostChecker > checker, absl::string_view dimension_name)
IntVarLocalSearchFilter * MakeCumulBoundsPropagatorFilter(const RoutingDimension &dimension)
Returns a filter handling dimension cumul bounds.
IntVarLocalSearchFilter * MakeGlobalLPCumulFilter(GlobalDimensionCumulOptimizer *optimizer, GlobalDimensionCumulOptimizer *mp_optimizer, bool filter_objective_cost)
Returns a filter checking global linear constraints and costs.
IntVarLocalSearchFilter * MakeCPFeasibilityFilter(RoutingModel *routing_model)
Returns a filter checking the current solution using CP propagation.
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 std::vector< PickupDeliveryPair > &pairs, const std::vector< RoutingModel::PickupAndDeliveryPolicy > &vehicle_policies)
int64_t CapProd(int64_t x, int64_t y)
static const int kUnassigned
--— Routing model --—
Definition routing.cc:361
void AppendDimensionCumulFilters(const std::vector< RoutingDimension * > &dimensions, const RoutingSearchParameters &parameters, bool filter_objective_cost, bool use_chain_cumul_filter, std::vector< LocalSearchFilterManager::FilterEvent > *filters)
LocalSearchFilter * MakeResourceAssignmentFilter(LocalDimensionCumulOptimizer *optimizer, LocalDimensionCumulOptimizer *mp_optimizer, bool propagate_own_objective_value, bool filter_objective_cost)
int64_t ComputeBestVehicleToResourceAssignment(const std::vector< int > &vehicles, const absl::StrongVector< RoutingModel::ResourceClassIndex, std::vector< int > > &resource_indices_per_class, const absl::StrongVector< RoutingModel::ResourceClassIndex, absl::flat_hash_set< int > > &ignored_resources_per_class, std::function< const std::vector< int64_t > *(int)> vehicle_to_resource_class_assignment_costs, std::vector< int > *resource_indices)
IntVarLocalSearchFilter * MakeVehicleVarFilter(const RoutingModel &routing_model)
Returns a filter checking that vehicle variable domains are respected.
IntVarLocalSearchFilter * MakeTypeRegulationsFilter(const RoutingModel &routing_model)
Returns a filter ensuring type regulation constraints are enforced.
bool ComputeVehicleToResourceClassAssignmentCosts(int v, const RoutingModel::ResourceGroup &resource_group, const absl::StrongVector< RoutingModel::ResourceClassIndex, absl::flat_hash_set< int > > &ignored_resources_per_class, 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)
LocalSearchFilter * MakeDimensionFilter(Solver *solver, std::unique_ptr< DimensionChecker > checker, const std::string &dimension_name)
int64_t CapOpp(int64_t v)
Note(user): -kint64min != kint64max, but kint64max == ~kint64min.
STL namespace.
int64_t delta
Definition resource.cc:1709
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.")
Implementation of local search filters for routing models.
int64_t coefficient
std::vector< int64_t > path_values
int vehicle_class
double distance
std::optional< int64_t > end
int64_t start
static const int64_t kint64max
Definition types.h:32