Google OR-Tools v9.11
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) ==
1521 DimensionSchedulingStatus::INFEASIBLE) {
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) ==
1890 DimensionSchedulingStatus::INFEASIBLE) {
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
IntegerValue size
int64_t min
std::vector< int > dimensions
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
ChainRange Chains(int path) const
Returns the current range of chains of path.
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:878
void Set(IntegerType index)
Definition bitset.h:864
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:242
const MapUtilMappedT< Collection > & FindWithDefault(const Collection &collection, const KeyType &key, const MapUtilMappedT< Collection > &value)
Definition map_util.h:36
For infeasible and unbounded see Not checked if options check_solutions_if_inf_or_unbounded and the If options first_solution_only is false
problem is infeasible or unbounded (default).
Definition matchers.h:468
std::function< int64_t(const Model &)> Value(IntegerVariable v)
This checks that the variable is fixed.
Definition integer.h:1975
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.
bool ComputeVehicleToResourceClassAssignmentCosts(int v, const RoutingModel::ResourceGroup &resource_group, const util_intops::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)
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)
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.
int64_t ComputeBestVehicleToResourceAssignment(const std::vector< int > &vehicles, const util_intops::StrongVector< RoutingModel::ResourceClassIndex, std::vector< int > > &resource_indices_per_class, const util_intops::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)
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