32#include "absl/algorithm/container.h"
33#include "absl/container/btree_set.h"
34#include "absl/container/flat_hash_map.h"
35#include "absl/container/flat_hash_set.h"
36#include "absl/flags/flag.h"
37#include "absl/functional/any_invocable.h"
38#include "absl/log/check.h"
39#include "absl/log/log.h"
40#include "absl/strings/str_cat.h"
41#include "absl/strings/string_view.h"
42#include "absl/types/span.h"
60 "Run stronger checks in debug; these stronger tests might change "
61 "the complexity of the code in particular.");
68class RouteConstraintFilter :
public BasePathFilter {
70 explicit RouteConstraintFilter(
const RoutingModel& routing_model)
71 : BasePathFilter(routing_model.Nexts(),
72 routing_model.Size() + routing_model.vehicles(),
73 routing_model.GetPathsMetadata()),
74 routing_model_(routing_model),
75 current_vehicle_cost_(0),
76 delta_vehicle_cost_(0),
77 current_vehicle_costs_(routing_model.vehicles(), 0) {
78 start_to_vehicle_.resize(Size(), -1);
79 vehicle_to_start_.resize(routing_model.vehicles());
80 for (
int v = 0; v < routing_model.vehicles(); v++) {
81 const int64_t start = routing_model.Start(v);
82 start_to_vehicle_[start] = v;
83 vehicle_to_start_[v] = start;
86 ~RouteConstraintFilter()
override =
default;
87 std::string DebugString()
const override {
return "RouteConstraintFilter"; }
88 int64_t GetSynchronizedObjectiveValue()
const override {
89 return current_vehicle_cost_;
91 int64_t GetAcceptedObjectiveValue()
const override {
92 return lns_detected() ? 0 : delta_vehicle_cost_;
96 void OnSynchronizePathFromStart(int64_t start)
override {
99 while (node < Size()) {
100 route_.push_back(node);
103 route_.push_back(node);
104 std::optional<int64_t> route_cost = routing_model_.GetRouteCost(route_);
105 DCHECK(route_cost.has_value());
106 current_vehicle_costs_[start_to_vehicle_[start]] = route_cost.value();
108 void OnAfterSynchronizePaths()
override {
109 current_vehicle_cost_ = 0;
110 for (
int vehicle = 0; vehicle < vehicle_to_start_.size(); vehicle++) {
111 const int64_t start = vehicle_to_start_[vehicle];
112 DCHECK_EQ(vehicle, start_to_vehicle_[start]);
113 if (!IsVarSynced(start)) {
116 CapAddTo(current_vehicle_costs_[vehicle], ¤t_vehicle_cost_);
119 bool InitializeAcceptPath()
override {
120 delta_vehicle_cost_ = current_vehicle_cost_;
123 bool AcceptPath(int64_t path_start, int64_t ,
125 delta_vehicle_cost_ =
126 CapSub(delta_vehicle_cost_,
127 current_vehicle_costs_[start_to_vehicle_[path_start]]);
129 int64_t node = path_start;
130 while (node < Size()) {
131 route_.push_back(node);
132 node = GetNext(node);
134 route_.push_back(node);
135 std::optional<int64_t> route_cost = routing_model_.GetRouteCost(route_);
136 if (!route_cost.has_value()) {
139 CapAddTo(route_cost.value(), &delta_vehicle_cost_);
142 bool FinalizeAcceptPath(int64_t ,
143 int64_t objective_max)
override {
144 return delta_vehicle_cost_ <= objective_max;
147 const RoutingModel& routing_model_;
148 int64_t current_vehicle_cost_;
149 int64_t delta_vehicle_cost_;
150 std::vector<int64_t> current_vehicle_costs_;
151 std::vector<int64_t> vehicle_to_start_;
152 std::vector<int> start_to_vehicle_;
153 std::vector<int64_t> route_;
161 new RouteConstraintFilter(routing_model));
168class MaxActiveVehiclesFilter :
public IntVarLocalSearchFilter {
170 explicit MaxActiveVehiclesFilter(
const RoutingModel& routing_model)
171 : IntVarLocalSearchFilter(routing_model.Nexts()),
172 routing_model_(routing_model),
173 is_active_(routing_model.vehicles(), false),
174 active_vehicles_(0) {}
175 ~MaxActiveVehiclesFilter()
override =
default;
176 std::string DebugString()
const override {
return "MaxActiveVehiclesFilter"; }
177 bool Accept(
const Assignment* delta,
const Assignment* ,
178 int64_t , int64_t )
override {
181 int current_active_vehicles = active_vehicles_;
182 for (
const IntVarElement& new_element : container.elements()) {
183 IntVar*
const var = new_element.Var();
185 if (FindIndex(var, &index) && routing_model_.IsStart(index)) {
186 if (new_element.Min() != new_element.Max()) {
190 const int vehicle = routing_model_.VehicleIndex(index);
191 const bool is_active =
192 (new_element.Min() != routing_model_.End(vehicle));
193 if (is_active && !is_active_[vehicle]) {
194 ++current_active_vehicles;
195 }
else if (!is_active && is_active_[vehicle]) {
196 --current_active_vehicles;
200 return current_active_vehicles <=
201 routing_model_.GetMaximumNumberOfActiveVehicles();
205 void OnSynchronize(
const Assignment* )
override {
206 active_vehicles_ = 0;
207 for (
int i = 0;
i < routing_model_.vehicles(); ++
i) {
208 const int index = routing_model_.Start(i);
209 if (IsVarSynced(index) &&
Value(index) != routing_model_.End(i)) {
210 is_active_[
i] =
true;
213 is_active_[
i] =
false;
218 const RoutingModel& routing_model_;
219 std::vector<bool> is_active_;
220 int active_vehicles_;
227 new MaxActiveVehiclesFilter(routing_model));
232class SameActivityGroupManager {
234 explicit SameActivityGroupManager(
const RoutingModel& routing_model)
235 : routing_model_(routing_model) {}
236 int NumberOfGroups()
const {
237 return routing_model_.GetSameActivityGroupsCount();
239 absl::Span<const int> GetGroupsFromNode(
int node)
const {
240 return absl::MakeConstSpan(routing_model_.GetSameActivityGroups())
243 const std::vector<int>& GetGroupNodes(
int group)
const {
244 return routing_model_.GetSameActivityIndicesOfGroup(group);
247 bool CheckGroup(
int group,
int active,
int unknown,
248 const CommittableArray<bool>& ,
249 const CommittableArray<bool>& )
const {
250 const int group_size = GetGroupNodes(group).size();
253 if (active == 0)
return true;
254 if (active <= group_size && group_size <= active + unknown) {
261 const RoutingModel& routing_model_;
264class OrderedActivityGroupManager {
266 explicit OrderedActivityGroupManager(
const RoutingModel& routing_model)
267 : routing_model_(routing_model),
268 groups_(routing_model.GetOrderedActivityGroups()),
269 group_bounds_(routing_model.GetOrderedActivityGroups().size(), {0, 0}),
270 disjunction_is_active_(routing_model.GetNumberOfDisjunctions(),
false),
271 disjunction_is_inactive_(routing_model.GetNumberOfDisjunctions(),
273 group_nodes_.resize(groups_.size());
274 node_groups_.resize(routing_model.Size());
275 disjunction_groups_.resize(routing_model.GetNumberOfDisjunctions());
276 for (
int group = 0; group < groups_.size(); ++group) {
277 absl::flat_hash_map<RoutingModel::DisjunctionIndex, std::vector<int>>
278 disjunction_to_ranks;
279 for (
int rank = 0; rank < groups_[group].size(); ++rank) {
280 disjunction_to_ranks[groups_[group][rank]].push_back(rank);
284 routing_model.GetDisjunctionNodeIndices(disjunction_index)) {
285 node_groups_[node].push_back(group);
286 group_nodes_[group].push_back(node);
288 disjunction_groups_[disjunction_index].push_back({
290 .sorted_ranks = disjunction_to_ranks[disjunction_index],
293 group_bounds_.Set(group, std::make_pair(0, groups_[group].size() - 1));
295 group_bounds_.Commit();
297 int NumberOfGroups()
const {
return groups_.size(); }
298 absl::Span<const int> GetGroupsFromNode(
int node)
const {
299 return node_groups_[node];
301 const std::vector<int>& GetGroupNodes(
int group)
const {
302 return group_nodes_[group];
305 group_bounds_.Revert();
306 disjunction_is_active_.Revert();
307 disjunction_is_inactive_.Revert();
308 touched_disjunctions_.clear();
310 bool CheckGroup(
int group,
int active,
int unknown,
311 CommittableArray<bool>& node_is_active,
312 CommittableArray<bool>& node_is_unknown) {
313 if (active == 0)
return true;
314 auto& [min_rank, max_rank] = group_bounds_.GetMutable(group);
315 for (
int rank = min_rank; rank <= max_rank; ++rank) {
317 groups_[group][rank];
318 if (IsInactive(disjunction_index, node_is_active, node_is_unknown)) {
319 disjunction_is_inactive_.Set(disjunction_index.value(),
true);
320 touched_disjunctions_.push_back(disjunction_index);
324 for (
int rank = max_rank; rank >= min_rank; --rank) {
326 groups_[group][rank];
327 if (IsActive(disjunction_index, node_is_active, node_is_unknown)) {
328 disjunction_is_active_.Set(disjunction_index.value(),
true);
329 touched_disjunctions_.push_back(disjunction_index);
333 while (!touched_disjunctions_.empty()) {
335 touched_disjunctions_.back();
336 touched_disjunctions_.pop_back();
337 if (!Propagate(disjunction_index, node_is_active, node_is_unknown)) {
344 CommittableArray<bool>& node_is_active,
345 CommittableArray<bool>& node_is_unknown) {
346 for (
const auto& [group_index, ranks] :
347 disjunction_groups_[disjunction_index]) {
348 const std::vector<RoutingModel::DisjunctionIndex>& group =
349 groups_[group_index];
350 auto& [min_rank, max_rank] = group_bounds_.GetMutable(group_index);
351 if (max_rank < min_rank)
continue;
352 if (IsActive(disjunction_index, node_is_active, node_is_unknown)) {
355 int i = ranks.size() - 1;
356 while (i >= 0 && ranks[i] > max_rank) --
i;
357 if (i < 0 || ranks[i] < min_rank)
continue;
359 while (rank != ranks[i]) {
361 if (IsInactive(current, node_is_active, node_is_unknown)) {
364 if (!IsActive(current, node_is_active, node_is_unknown)) {
365 disjunction_is_active_.Set(current.value(),
true);
366 touched_disjunctions_.push_back(current);
375 while (i < ranks.size() && ranks[i] < min_rank) ++
i;
376 if (i >= ranks.size() || ranks[i] > max_rank)
continue;
378 while (rank != ranks[i]) {
380 if (IsActive(current, node_is_active, node_is_unknown)) {
383 if (!IsInactive(current, node_is_active, node_is_unknown)) {
384 disjunction_is_inactive_.Set(current.value(),
true);
385 touched_disjunctions_.push_back(current);
397 const CommittableArray<bool>& node_is_active,
398 const CommittableArray<bool>& node_is_unknown)
const {
399 if (disjunction_is_inactive_.Get(disjunction_index.value()))
return true;
403 routing_model_.GetDisjunctionNodeIndices(disjunction_index)) {
404 if (node_is_unknown.Get(node)) {
406 }
else if (node_is_active.Get(node)) {
411 routing_model_.GetDisjunctionMaxCardinality(disjunction_index) -
415 const CommittableArray<bool>& node_is_active,
416 const CommittableArray<bool>& node_is_unknown)
const {
417 if (disjunction_is_active_.Get(disjunction_index.value()))
return true;
420 routing_model_.GetDisjunctionNodeIndices(disjunction_index)) {
421 if (!node_is_unknown.Get(node) && node_is_active.Get(node)) {
426 routing_model_.GetDisjunctionMaxCardinality(disjunction_index);
429 const RoutingModel& routing_model_;
430 const std::vector<std::vector<RoutingModel::DisjunctionIndex>>& groups_;
431 std::vector<std::vector<int>> group_nodes_;
432 std::vector<std::vector<int>> node_groups_;
433 struct DisjunctionGroupInfo {
435 std::vector<int> sorted_ranks;
438 std::vector<DisjunctionGroupInfo>>
440 CommittableArray<std::pair<int, int>> group_bounds_;
441 std::vector<RoutingModel::DisjunctionIndex> touched_disjunctions_;
442 CommittableArray<bool> disjunction_is_active_;
443 CommittableArray<bool> disjunction_is_inactive_;
446template <
typename GroupAccessor>
449 explicit ActiveNodeGroupFilter(
const RoutingModel& routing_model)
450 : IntVarLocalSearchFilter(routing_model.Nexts()),
451 group_accessor_(routing_model),
452 active_count_per_group_(group_accessor_.NumberOfGroups(),
453 {.active = 0, .unknown = 0}),
454 node_is_active_(routing_model.Nexts().size(),
false),
455 node_is_unknown_(routing_model.Nexts().size(),
false) {}
457 bool Accept(
const Assignment* delta,
const Assignment* ,
458 int64_t , int64_t )
override {
459 active_count_per_group_.Revert();
460 node_is_active_.Revert();
461 node_is_unknown_.Revert();
462 group_accessor_.Revert();
465 for (
const IntVarElement& new_element : container.elements()) {
466 IntVar*
const var = new_element.Var();
468 if (!FindIndex(var, &index))
continue;
469 for (
const int group : group_accessor_.GetGroupsFromNode(index)) {
470 ActivityCounts counts = active_count_per_group_.Get(group);
472 if (node_is_unknown_.Get(index)) --counts.unknown;
473 if (node_is_active_.Get(index)) --counts.active;
474 if (new_element.Min() != new_element.Max()) {
476 }
else if (new_element.Min() != index) {
479 active_count_per_group_.Set(group, counts);
483 for (
const IntVarElement& new_element : container.elements()) {
484 IntVar*
const var = new_element.Var();
486 if (!FindIndex(var, &index))
continue;
487 node_is_unknown_.Set(index, new_element.Min() != new_element.Max());
488 node_is_active_.Set(index, new_element.Min() == new_element.Max() &&
489 new_element.Min() != index);
491 for (
const int group : active_count_per_group_.ChangedIndices()) {
492 const ActivityCounts counts = active_count_per_group_.Get(group);
493 if (!group_accessor_.CheckGroup(group, counts.active, counts.unknown,
494 node_is_active_, node_is_unknown_)) {
500 std::string DebugString()
const override {
return "ActiveNodeGroupFilter"; }
503 void OnSynchronize(
const Assignment* )
override {
504 const int num_groups = group_accessor_.NumberOfGroups();
505 for (
int group = 0; group < num_groups; ++group) {
506 ActivityCounts counts = {.active = 0, .unknown = 0};
507 for (
int node : group_accessor_.GetGroupNodes(group)) {
508 if (IsVarSynced(node)) {
509 const bool is_active = (
Value(node) != node);
510 node_is_active_.Set(node, is_active);
511 node_is_unknown_.Set(node,
false);
512 counts.active += is_active ? 1 : 0;
515 node_is_unknown_.Set(node,
true);
516 node_is_active_.Set(node,
false);
519 active_count_per_group_.Set(group, counts);
521 active_count_per_group_.Commit();
522 node_is_active_.Commit();
523 node_is_unknown_.Commit();
526 GroupAccessor group_accessor_;
527 struct ActivityCounts {
531 CommittableArray<ActivityCounts> active_count_per_group_;
534 CommittableArray<bool> node_is_active_;
537 CommittableArray<bool> node_is_unknown_;
545 new ActiveNodeGroupFilter<SameActivityGroupManager>(routing_model));
551 new ActiveNodeGroupFilter<OrderedActivityGroupManager>(routing_model));
557class NodeDisjunctionFilter :
public IntVarLocalSearchFilter {
559 explicit NodeDisjunctionFilter(
const RoutingModel& routing_model,
561 : IntVarLocalSearchFilter(routing_model.Nexts()),
562 routing_model_(routing_model),
563 count_per_disjunction_(routing_model.GetNumberOfDisjunctions(),
564 {.active = 0, .inactive = 0}),
565 synchronized_objective_value_(std::numeric_limits<int64_t>::min()),
566 accepted_objective_value_(std::numeric_limits<int64_t>::min()),
567 filter_cost_(filter_cost),
568 has_mandatory_disjunctions_(routing_model.HasMandatoryDisjunctions()) {}
572 bool Accept(
const Assignment* delta,
const Assignment* ,
573 int64_t , int64_t objective_max)
override {
574 count_per_disjunction_.Revert();
575 bool lns_detected =
false;
577 for (
const IntVarElement& element : delta->IntVarContainer().elements()) {
579 if (!FindIndex(element.Var(), &node))
continue;
580 lns_detected |= element.Min() != element.Max();
582 const bool is_var_synced = IsVarSynced(node);
583 const bool was_active = is_var_synced &&
Value(node) != node;
584 const bool is_active = node < element.Min() || element.Max() < node;
585 ActivityCount contribution_delta = {.active = 0, .inactive = 0};
587 contribution_delta.active -= was_active;
588 contribution_delta.inactive -= !was_active;
590 contribution_delta.active += is_active;
591 contribution_delta.inactive += !is_active;
593 if (contribution_delta.active == 0 && contribution_delta.inactive == 0) {
597 for (
const Disjunction disjunction :
598 routing_model_.GetDisjunctionIndices(node)) {
599 ActivityCount new_count =
600 count_per_disjunction_.Get(disjunction.value());
601 new_count.active += contribution_delta.active;
602 new_count.inactive += contribution_delta.inactive;
603 count_per_disjunction_.Set(disjunction.value(), new_count);
607 for (
const int index : count_per_disjunction_.ChangedIndices()) {
608 if (count_per_disjunction_.Get(index).active >
609 routing_model_.GetDisjunctionMaxCardinality(Disjunction(index))) {
613 if (lns_detected || (!filter_cost_ && !has_mandatory_disjunctions_)) {
614 accepted_objective_value_ = 0;
618 accepted_objective_value_ = synchronized_objective_value_;
619 for (
const int index : count_per_disjunction_.ChangedIndices()) {
621 const int old_inactives =
622 count_per_disjunction_.GetCommitted(index).inactive;
623 const int new_inactives = count_per_disjunction_.Get(index).inactive;
624 if (old_inactives == new_inactives)
continue;
626 const Disjunction disjunction(index);
627 const int64_t penalty = routing_model_.GetDisjunctionPenalty(disjunction);
628 if (penalty == 0)
continue;
631 const int max_inactives =
632 routing_model_.GetDisjunctionNodeIndices(disjunction).size() -
633 routing_model_.GetDisjunctionMaxCardinality(disjunction);
634 int new_violation = std::max(0, new_inactives - max_inactives);
635 int old_violation = std::max(0, old_inactives - max_inactives);
637 if (penalty < 0 && new_violation > 0)
return false;
638 if (routing_model_.GetDisjunctionPenaltyCostBehavior(disjunction) ==
640 new_violation = std::min(1, new_violation);
641 old_violation = std::min(1, old_violation);
644 &accepted_objective_value_);
647 return accepted_objective_value_ <= objective_max;
649 std::string DebugString()
const override {
return "NodeDisjunctionFilter"; }
650 int64_t GetSynchronizedObjectiveValue()
const override {
651 return synchronized_objective_value_;
653 int64_t GetAcceptedObjectiveValue()
const override {
654 return accepted_objective_value_;
658 void OnSynchronize(
const Assignment* )
override {
659 synchronized_objective_value_ = 0;
660 count_per_disjunction_.Revert();
661 const int num_disjunctions = routing_model_.GetNumberOfDisjunctions();
662 for (Disjunction disjunction(0); disjunction < num_disjunctions;
665 ActivityCount count = {.active = 0, .inactive = 0};
666 const auto& nodes = routing_model_.GetDisjunctionNodeIndices(disjunction);
667 for (
const int64_t node : nodes) {
668 if (!IsVarSynced(node))
continue;
669 const int is_active =
Value(node) != node;
670 count.active += is_active;
671 count.inactive += !is_active;
673 count_per_disjunction_.Set(disjunction.value(), count);
675 if (!filter_cost_)
continue;
676 const int64_t penalty = routing_model_.GetDisjunctionPenalty(disjunction);
677 const int max_actives =
678 routing_model_.GetDisjunctionMaxCardinality(disjunction);
679 int violation = count.inactive - (nodes.size() - max_actives);
680 if (violation > 0 && penalty > 0) {
681 if (routing_model_.GetDisjunctionPenaltyCostBehavior(disjunction) ==
683 violation = std::min(1, violation);
688 count_per_disjunction_.Commit();
689 accepted_objective_value_ = synchronized_objective_value_;
692 const RoutingModel& routing_model_;
693 struct ActivityCount {
697 CommittableArray<ActivityCount> count_per_disjunction_;
698 int64_t synchronized_objective_value_;
699 int64_t accepted_objective_value_;
700 const bool filter_cost_;
701 const bool has_mandatory_disjunctions_;
708 new NodeDisjunctionFilter(routing_model, filter_cost));
714 int next_domain_size,
717 paths_metadata_(paths_metadata),
719 new_synchronized_unperformed_nodes_(nexts.size()),
721 touched_paths_(nexts.size()),
724 lns_detected_(
false) {}
728 int64_t objective_min, int64_t objective_max) {
729 lns_detected_ =
false;
730 for (
const int touched : delta_touched_) {
733 delta_touched_.clear();
735 delta_touched_.reserve(container.
Size());
742 for (int64_t touched_path : touched_paths_.PositionsSetAtLeastOnce()) {
745 touched_paths_.ResetAllToFalse();
747 const auto update_touched_path_chain_start_end = [
this](int64_t index) {
748 const int64_t start = node_path_starts_[index];
750 touched_paths_.Set(start);
752 int64_t& chain_start = touched_path_chain_start_ends_[start].first;
753 if (chain_start ==
kUnassigned || paths_metadata_.IsStart(index) ||
754 ranks_[index] < ranks_[chain_start]) {
758 int64_t& chain_end = touched_path_chain_start_ends_[start].second;
759 if (chain_end ==
kUnassigned || paths_metadata_.IsEnd(index) ||
760 ranks_[index] > ranks_[chain_end]) {
769 if (!new_element.Bound()) {
771 lns_detected_ =
true;
774 new_nexts_[index] = new_element.Value();
775 delta_touched_.push_back(index);
776 update_touched_path_chain_start_end(index);
777 update_touched_path_chain_start_end(new_nexts_[index]);
781 if (!InitializeAcceptPath())
return false;
782 for (
const int64_t touched_start : touched_paths_.PositionsSetAtLeastOnce()) {
783 const std::pair<int64_t, int64_t> start_end =
784 touched_path_chain_start_ends_[touched_start];
785 if (!AcceptPath(touched_start, start_end.first, start_end.second)) {
791 return FinalizeAcceptPath(objective_min, objective_max);
794void BasePathFilter::ComputePathStarts(std::vector<int64_t>* path_starts,
795 std::vector<int>* index_to_path) {
796 path_starts->clear();
797 const int nexts_size =
Size();
800 for (
int i = 0; i < nexts_size; ++i) {
804 const int next =
Value(i);
805 if (next < nexts_size) {
810 for (
int i = 0;
i < nexts_size; ++
i) {
812 (*index_to_path)[
i] = path_starts->size();
813 path_starts->push_back(i);
818void BasePathFilter::SynchronizeFullAssignment() {
819 for (int64_t index = 0; index <
Size(); index++) {
823 new_synchronized_unperformed_nodes_.Set(index);
827 node_path_starts_.assign(node_path_starts_.size(),
kUnassigned);
829 const int nexts_size =
Size();
830 for (
int path = 0; path <
NumPaths(); ++path) {
831 const int64_t start =
Start(path);
832 node_path_starts_[start] = start;
834 int64_t next =
Value(start);
835 while (next < nexts_size) {
837 node_path_starts_[node] = start;
841 node_path_starts_[next] = start;
843 node_path_starts_[
End(path)] = start;
845 for (
const int touched : delta_touched_) {
848 delta_touched_.clear();
849 OnBeforeSynchronizePaths(
true);
851 OnAfterSynchronizePaths();
855 new_synchronized_unperformed_nodes_.ResetAllToFalse();
856 if (delta ==
nullptr || delta->
Empty() ||
857 absl::c_all_of(ranks_, [](
int rank) { return rank == kUnassigned; })) {
858 SynchronizeFullAssignment();
862 touched_paths_.ResetAllToFalse();
865 if (
FindIndex(new_element.Var(), &index)) {
866 const int64_t start = node_path_starts_[index];
868 touched_paths_.Set(start);
869 if (
Value(index) == index) {
871 DCHECK_LT(index, new_nexts_.size());
872 new_synchronized_unperformed_nodes_.Set(index);
878 for (
const int touched : delta_touched_) {
881 delta_touched_.clear();
882 OnBeforeSynchronizePaths(
false);
883 for (
const int64_t touched_start : touched_paths_.PositionsSetAtLeastOnce()) {
884 int64_t node = touched_start;
885 while (node <
Size()) {
886 node_path_starts_[node] = touched_start;
889 node_path_starts_[node] = touched_start;
890 UpdatePathRanksFromStart(touched_start);
891 OnSynchronizePathFromStart(touched_start);
893 OnAfterSynchronizePaths();
896void BasePathFilter::UpdateAllRanks() {
898 for (
int r = 0; r <
NumPaths(); ++r) {
899 const int64_t start =
Start(r);
901 UpdatePathRanksFromStart(start);
902 OnSynchronizePathFromStart(start);
906void BasePathFilter::UpdatePathRanksFromStart(
int start) {
908 int64_t node = start;
909 while (node <
Size()) {
921 explicit VehicleAmortizedCostFilter(
const RoutingModel& routing_model);
922 ~VehicleAmortizedCostFilter()
override =
default;
923 std::string DebugString()
const override {
924 return "VehicleAmortizedCostFilter";
926 int64_t GetSynchronizedObjectiveValue()
const override {
927 return current_vehicle_cost_;
929 int64_t GetAcceptedObjectiveValue()
const override {
930 return lns_detected() ? 0 : delta_vehicle_cost_;
934 void OnSynchronizePathFromStart(int64_t start)
override;
935 void OnAfterSynchronizePaths()
override;
936 bool InitializeAcceptPath()
override;
937 bool AcceptPath(int64_t path_start, int64_t chain_start,
938 int64_t chain_end)
override;
939 bool FinalizeAcceptPath(int64_t objective_min,
940 int64_t objective_max)
override;
942 int64_t current_vehicle_cost_;
943 int64_t delta_vehicle_cost_;
944 std::vector<int> current_route_lengths_;
945 std::vector<int64_t> start_to_end_;
946 std::vector<int> start_to_vehicle_;
947 std::vector<int64_t> vehicle_to_start_;
948 const std::vector<int64_t>& linear_cost_factor_of_vehicle_;
949 const std::vector<int64_t>& quadratic_cost_factor_of_vehicle_;
952VehicleAmortizedCostFilter::VehicleAmortizedCostFilter(
955 routing_model.Size() + routing_model.vehicles(),
956 routing_model.GetPathsMetadata()),
957 current_vehicle_cost_(0),
958 delta_vehicle_cost_(0),
959 current_route_lengths_(Size(), -1),
960 linear_cost_factor_of_vehicle_(
961 routing_model.GetAmortizedLinearCostFactorOfVehicles()),
962 quadratic_cost_factor_of_vehicle_(
963 routing_model.GetAmortizedQuadraticCostFactorOfVehicles()) {
964 start_to_end_.resize(
Size(), -1);
965 start_to_vehicle_.resize(
Size(), -1);
966 vehicle_to_start_.resize(routing_model.vehicles());
967 for (
int v = 0; v < routing_model.vehicles(); v++) {
968 const int64_t start = routing_model.Start(v);
969 start_to_vehicle_[start] = v;
970 start_to_end_[start] = routing_model.End(v);
971 vehicle_to_start_[v] = start;
975void VehicleAmortizedCostFilter::OnSynchronizePathFromStart(int64_t start) {
976 const int64_t
end = start_to_end_[start];
978 const int route_length = Rank(end) - 1;
979 CHECK_GE(route_length, 0);
980 current_route_lengths_[start] = route_length;
983void VehicleAmortizedCostFilter::OnAfterSynchronizePaths() {
984 current_vehicle_cost_ = 0;
985 for (
int vehicle = 0; vehicle < vehicle_to_start_.size(); vehicle++) {
986 const int64_t start = vehicle_to_start_[vehicle];
987 DCHECK_EQ(vehicle, start_to_vehicle_[start]);
988 if (!IsVarSynced(start)) {
991 const int route_length = current_route_lengths_[start];
992 DCHECK_GE(route_length, 0);
994 if (route_length == 0) {
999 const int64_t linear_cost_factor = linear_cost_factor_of_vehicle_[vehicle];
1000 const int64_t route_length_cost =
1001 CapProd(quadratic_cost_factor_of_vehicle_[vehicle],
1002 route_length * route_length);
1005 ¤t_vehicle_cost_);
1009bool VehicleAmortizedCostFilter::InitializeAcceptPath() {
1010 delta_vehicle_cost_ = current_vehicle_cost_;
1014bool VehicleAmortizedCostFilter::AcceptPath(int64_t path_start,
1015 int64_t chain_start,
1016 int64_t chain_end) {
1018 const int previous_chain_nodes = Rank(chain_end) - 1 - Rank(chain_start);
1019 CHECK_GE(previous_chain_nodes, 0);
1020 int new_chain_nodes = 0;
1021 int64_t node = GetNext(chain_start);
1022 while (node != chain_end) {
1024 node = GetNext(node);
1027 const int previous_route_length = current_route_lengths_[path_start];
1028 CHECK_GE(previous_route_length, 0);
1029 const int new_route_length =
1030 previous_route_length - previous_chain_nodes + new_chain_nodes;
1032 const int vehicle = start_to_vehicle_[path_start];
1033 CHECK_GE(vehicle, 0);
1034 DCHECK_EQ(path_start, vehicle_to_start_[vehicle]);
1038 if (previous_route_length == 0) {
1040 CHECK_GT(new_route_length, 0);
1041 CapAddTo(linear_cost_factor_of_vehicle_[vehicle], &delta_vehicle_cost_);
1042 }
else if (new_route_length == 0) {
1044 delta_vehicle_cost_ =
1045 CapSub(delta_vehicle_cost_, linear_cost_factor_of_vehicle_[vehicle]);
1049 const int64_t quadratic_cost_factor =
1050 quadratic_cost_factor_of_vehicle_[vehicle];
1052 previous_route_length * previous_route_length),
1053 &delta_vehicle_cost_);
1054 delta_vehicle_cost_ =
CapSub(
1055 delta_vehicle_cost_,
1056 CapProd(quadratic_cost_factor, new_route_length * new_route_length));
1061bool VehicleAmortizedCostFilter::FinalizeAcceptPath(int64_t ,
1062 int64_t objective_max) {
1063 return delta_vehicle_cost_ <= objective_max;
1071 new VehicleAmortizedCostFilter(routing_model));
1079class SameVehicleCostFilter :
public BasePathFilter {
1081 explicit SameVehicleCostFilter(
const RoutingModel& model)
1082 : BasePathFilter(model.Nexts(), model.Size() + model.vehicles(),
1083 model.GetPathsMetadata()),
1085 same_vehicle_costs_per_node_(model.Size()),
1086 nodes_per_vehicle_(model.GetNumberOfSoftSameVehicleConstraints()),
1087 new_nodes_per_vehicle_(model.GetNumberOfSoftSameVehicleConstraints()),
1088 current_vehicle_per_node_(model.Size()),
1090 for (
int i = 0; i < model.GetNumberOfSoftSameVehicleConstraints(); ++i) {
1091 for (
int node : model.GetSoftSameVehicleIndices(i)) {
1092 same_vehicle_costs_per_node_[node].push_back(i);
1095 start_to_vehicle_.resize(Size(), -1);
1096 for (
int v = 0; v < model.vehicles(); v++) {
1097 const int64_t start = model.Start(v);
1098 start_to_vehicle_[start] = v;
1101 int64_t GetSynchronizedObjectiveValue()
const override {
1102 return current_cost_;
1104 int64_t GetAcceptedObjectiveValue()
const override {
1105 return lns_detected() ? 0 : delta_cost_;
1107 std::string DebugString()
const override {
return "SameVehicleCostFilter"; }
1110 bool InitializeAcceptPath()
override {
1111 delta_cost_ = current_cost_;
1112 for (
int same_vehicle_cost_index : touched_) {
1113 new_nodes_per_vehicle_[same_vehicle_cost_index] =
1114 nodes_per_vehicle_[same_vehicle_cost_index];
1119 bool AcceptPath(int64_t path_start, int64_t chain_start,
1120 int64_t chain_end)
override {
1121 const int64_t vehicle = start_to_vehicle_[path_start];
1122 DCHECK_GE(vehicle, 0);
1123 if (chain_start == chain_end)
return true;
1124 for (int64_t node = GetNext(chain_start); node != chain_end;
1125 node = GetNext(node)) {
1126 if (vehicle == current_vehicle_per_node_[node])
continue;
1127 for (
int same_vehicle_cost_index : same_vehicle_costs_per_node_[node]) {
1128 auto& new_nodes = new_nodes_per_vehicle_[same_vehicle_cost_index];
1129 new_nodes[vehicle]++;
1130 new_nodes[current_vehicle_per_node_[node]]--;
1131 if (new_nodes[current_vehicle_per_node_[node]] == 0) {
1132 new_nodes.erase(current_vehicle_per_node_[node]);
1134 touched_.insert(same_vehicle_cost_index);
1139 bool FinalizeAcceptPath(int64_t ,
1140 int64_t objective_max)
override {
1141 for (
int same_vehicle_cost_index : touched_) {
1142 CapAddTo(
CapSub(GetCost(same_vehicle_cost_index, new_nodes_per_vehicle_),
1143 GetCost(same_vehicle_cost_index, nodes_per_vehicle_)),
1146 return delta_cost_ <= objective_max;
1149 void OnAfterSynchronizePaths()
override {
1152 for (
int same_vehicle_cost_index = 0;
1153 same_vehicle_cost_index < nodes_per_vehicle_.size();
1154 ++same_vehicle_cost_index) {
1155 nodes_per_vehicle_[same_vehicle_cost_index].clear();
1156 touched_.insert(same_vehicle_cost_index);
1158 current_vehicle_per_node_.assign(model_.
Size(), -1);
1159 for (
int v = 0; v < model_.
vehicles(); ++v) {
1160 int64_t node = GetNext(model_.
Start(v));
1161 DCHECK(model_.
IsEnd(node) || IsVarSynced(node));
1162 while (!model_.
IsEnd(node)) {
1163 for (
int same_vehicle_cost_index : same_vehicle_costs_per_node_[node]) {
1164 nodes_per_vehicle_[same_vehicle_cost_index][v]++;
1166 current_vehicle_per_node_[node] = v;
1167 node = GetNext(node);
1170 for (
int same_vehicle_cost_index = 0;
1171 same_vehicle_cost_index < nodes_per_vehicle_.size();
1172 ++same_vehicle_cost_index) {
1173 CapAddTo(GetCost(same_vehicle_cost_index, nodes_per_vehicle_),
1179 absl::Span<
const absl::flat_hash_map<int, int>> nodes_per_vehicle)
const {
1180 const int num_vehicles_used = nodes_per_vehicle[index].size();
1181 if (num_vehicles_used <= 1)
return 0;
1185 const RoutingModel& model_;
1186 std::vector<int> start_to_vehicle_;
1187 std::vector<std::vector<int>> same_vehicle_costs_per_node_;
1188 std::vector<absl::flat_hash_map<int, int>> nodes_per_vehicle_;
1189 std::vector<absl::flat_hash_map<int, int>> new_nodes_per_vehicle_;
1190 absl::flat_hash_set<int> touched_;
1191 std::vector<int> current_vehicle_per_node_;
1192 int64_t current_cost_;
1193 int64_t delta_cost_;
1201 new SameVehicleCostFilter(routing_model));
1206class TypeRegulationsFilter :
public BasePathFilter {
1208 explicit TypeRegulationsFilter(
const RoutingModel& model);
1209 ~TypeRegulationsFilter()
override =
default;
1210 std::string DebugString()
const override {
return "TypeRegulationsFilter"; }
1213 void OnSynchronizePathFromStart(int64_t start)
override;
1214 bool AcceptPath(int64_t path_start, int64_t chain_start,
1215 int64_t chain_end)
override;
1217 bool HardIncompatibilitiesRespected(
int vehicle, int64_t chain_start,
1220 const RoutingModel& routing_model_;
1221 std::vector<int> start_to_vehicle_;
1224 std::vector<std::vector<int>> hard_incompatibility_type_counts_per_vehicle_;
1226 TypeIncompatibilityChecker temporal_incompatibility_checker_;
1227 TypeRequirementChecker requirement_checker_;
1230TypeRegulationsFilter::TypeRegulationsFilter(
const RoutingModel& model)
1231 : BasePathFilter(model.Nexts(), model.Size() + model.vehicles(),
1232 model.GetPathsMetadata()),
1233 routing_model_(model),
1234 start_to_vehicle_(model.Size(), -1),
1235 temporal_incompatibility_checker_(model,
1237 requirement_checker_(model) {
1238 const int num_vehicles = model.vehicles();
1239 const bool has_hard_type_incompatibilities =
1240 model.HasHardTypeIncompatibilities();
1241 if (has_hard_type_incompatibilities) {
1242 hard_incompatibility_type_counts_per_vehicle_.resize(num_vehicles);
1244 const int num_visit_types = model.GetNumberOfVisitTypes();
1245 for (
int vehicle = 0; vehicle < num_vehicles; vehicle++) {
1246 const int64_t start = model.Start(vehicle);
1247 start_to_vehicle_[start] = vehicle;
1248 if (has_hard_type_incompatibilities) {
1249 hard_incompatibility_type_counts_per_vehicle_[vehicle].resize(
1250 num_visit_types, 0);
1255void TypeRegulationsFilter::OnSynchronizePathFromStart(int64_t start) {
1256 if (!routing_model_.HasHardTypeIncompatibilities())
return;
1258 const int vehicle = start_to_vehicle_[start];
1259 CHECK_GE(vehicle, 0);
1260 std::vector<int>& type_counts =
1261 hard_incompatibility_type_counts_per_vehicle_[vehicle];
1262 std::fill(type_counts.begin(), type_counts.end(), 0);
1263 const int num_types = type_counts.size();
1265 int64_t node = start;
1266 while (node < Size()) {
1267 DCHECK(IsVarSynced(node));
1268 const int type = routing_model_.GetVisitType(node);
1269 if (type >= 0 && routing_model_.GetVisitTypePolicy(node) !=
1270 RoutingModel::ADDED_TYPE_REMOVED_FROM_VEHICLE) {
1271 CHECK_LT(type, num_types);
1272 type_counts[type]++;
1278bool TypeRegulationsFilter::HardIncompatibilitiesRespected(
int vehicle,
1279 int64_t chain_start,
1280 int64_t chain_end) {
1281 if (!routing_model_.HasHardTypeIncompatibilities())
return true;
1283 const std::vector<int>& previous_type_counts =
1284 hard_incompatibility_type_counts_per_vehicle_[vehicle];
1286 absl::flat_hash_map< int,
int> new_type_counts;
1287 absl::flat_hash_set<int> types_to_check;
1290 int64_t node = GetNext(chain_start);
1291 while (node != chain_end) {
1292 const int type = routing_model_.GetVisitType(node);
1293 if (type >= 0 && routing_model_.GetVisitTypePolicy(node) !=
1294 RoutingModel::ADDED_TYPE_REMOVED_FROM_VEHICLE) {
1295 DCHECK_LT(type, previous_type_counts.size());
1297 previous_type_counts[type]);
1298 if (type_count++ == 0) {
1300 types_to_check.insert(type);
1303 node = GetNext(node);
1308 if (IsVarSynced(chain_start)) {
1309 node =
Value(chain_start);
1310 while (node != chain_end) {
1311 const int type = routing_model_.GetVisitType(node);
1312 if (type >= 0 && routing_model_.GetVisitTypePolicy(node) !=
1313 RoutingModel::ADDED_TYPE_REMOVED_FROM_VEHICLE) {
1314 DCHECK_LT(type, previous_type_counts.size());
1316 previous_type_counts[type]);
1317 CHECK_GE(type_count, 1);
1325 for (
int type : types_to_check) {
1326 for (
int incompatible_type :
1327 routing_model_.GetHardTypeIncompatibilitiesOfType(type)) {
1329 previous_type_counts[incompatible_type]) > 0) {
1337bool TypeRegulationsFilter::AcceptPath(int64_t path_start, int64_t chain_start,
1338 int64_t chain_end) {
1339 const int vehicle = start_to_vehicle_[path_start];
1340 CHECK_GE(vehicle, 0);
1341 const auto next_accessor = [
this](int64_t node) {
return GetNext(node); };
1342 return HardIncompatibilitiesRespected(vehicle, chain_start, chain_end) &&
1343 temporal_incompatibility_checker_.CheckVehicle(vehicle,
1345 requirement_checker_.CheckVehicle(vehicle, next_accessor);
1353 new TypeRegulationsFilter(routing_model));
1363class ChainCumulFilter :
public BasePathFilter {
1365 ChainCumulFilter(
const RoutingModel& routing_model,
1366 const RoutingDimension& dimension);
1367 ~ChainCumulFilter()
override =
default;
1368 std::string DebugString()
const override {
1369 return "ChainCumulFilter(" + name_ +
")";
1373 void OnSynchronizePathFromStart(int64_t start)
override;
1374 bool AcceptPath(int64_t path_start, int64_t chain_start,
1375 int64_t chain_end)
override;
1377 const std::vector<IntVar*> cumuls_;
1378 std::vector<int64_t> start_to_vehicle_;
1379 std::vector<int64_t> start_to_end_;
1380 std::vector<const RoutingModel::TransitCallback2*> evaluators_;
1381 const std::vector<int64_t> vehicle_capacities_;
1382 std::vector<int64_t> current_path_cumul_mins_;
1383 std::vector<int64_t> current_max_of_path_end_cumul_mins_;
1384 std::vector<int64_t> old_nexts_;
1385 std::vector<int> old_vehicles_;
1386 std::vector<int64_t> current_transits_;
1387 const std::string name_;
1390ChainCumulFilter::ChainCumulFilter(
const RoutingModel& routing_model,
1391 const RoutingDimension& dimension)
1392 : BasePathFilter(routing_model.Nexts(), dimension.cumuls().size(),
1393 routing_model.GetPathsMetadata()),
1394 cumuls_(dimension.cumuls()),
1395 evaluators_(routing_model.vehicles(), nullptr),
1396 vehicle_capacities_(dimension.vehicle_capacities()),
1397 current_path_cumul_mins_(dimension.cumuls().size(), 0),
1398 current_max_of_path_end_cumul_mins_(dimension.cumuls().size(), 0),
1399 old_nexts_(routing_model.Size(), kUnassigned),
1400 old_vehicles_(routing_model.Size(), kUnassigned),
1401 current_transits_(routing_model.Size(), 0),
1402 name_(dimension.name()) {
1403 start_to_vehicle_.resize(Size(), -1);
1404 start_to_end_.resize(Size(), -1);
1405 for (
int i = 0;
i < routing_model.vehicles(); ++
i) {
1406 start_to_vehicle_[routing_model.Start(i)] =
i;
1407 start_to_end_[routing_model.Start(i)] = routing_model.End(i);
1408 evaluators_[
i] = &dimension.transit_evaluator(i);
1415void ChainCumulFilter::OnSynchronizePathFromStart(int64_t start) {
1416 const int vehicle = start_to_vehicle_[start];
1417 std::vector<int64_t> path_nodes;
1418 int64_t node = start;
1419 int64_t cumul = cumuls_[node]->Min();
1420 while (node < Size()) {
1421 path_nodes.push_back(node);
1422 current_path_cumul_mins_[node] = cumul;
1423 const int64_t next =
Value(node);
1424 if (next != old_nexts_[node] || vehicle != old_vehicles_[node]) {
1425 old_nexts_[node] = next;
1426 old_vehicles_[node] = vehicle;
1427 current_transits_[node] = (*evaluators_[vehicle])(node, next);
1429 CapAddTo(current_transits_[node], &cumul);
1430 cumul = std::max(cumuls_[next]->Min(), cumul);
1433 path_nodes.push_back(node);
1434 current_path_cumul_mins_[node] = cumul;
1435 int64_t max_cumuls = cumul;
1436 for (
int i = path_nodes.size() - 1; i >= 0; --i) {
1437 const int64_t node = path_nodes[
i];
1438 max_cumuls = std::max(max_cumuls, current_path_cumul_mins_[node]);
1439 current_max_of_path_end_cumul_mins_[node] = max_cumuls;
1444bool ChainCumulFilter::AcceptPath(int64_t path_start, int64_t chain_start,
1445 int64_t chain_end) {
1446 const int vehicle = start_to_vehicle_[path_start];
1447 const int64_t capacity = vehicle_capacities_[vehicle];
1448 int64_t node = chain_start;
1449 int64_t cumul = current_path_cumul_mins_[node];
1450 while (node != chain_end) {
1451 const int64_t next = GetNext(node);
1452 if (IsVarSynced(node) && next ==
Value(node) &&
1453 vehicle == old_vehicles_[node]) {
1454 CapAddTo(current_transits_[node], &cumul);
1456 CapAddTo((*evaluators_[vehicle])(node, next), &cumul);
1458 cumul = std::max(cumuls_[next]->Min(), cumul);
1459 if (cumul > capacity)
return false;
1462 const int64_t
end = start_to_end_[path_start];
1463 const int64_t end_cumul_delta =
1464 CapSub(current_path_cumul_mins_[end], current_path_cumul_mins_[node]);
1465 const int64_t after_chain_cumul_delta =
1466 CapSub(current_max_of_path_end_cumul_mins_[node],
1467 current_path_cumul_mins_[node]);
1468 return CapAdd(cumul, after_chain_cumul_delta) <= capacity &&
1469 CapAdd(cumul, end_cumul_delta) <= cumuls_[
end]->Max();
1475 int path, int64_t capacity, int64_t span_upper_bound,
1476 absl::Span<const DimensionValues::Interval> cumul_of_node,
1477 absl::Span<const DimensionValues::Interval> slack_of_node,
1478 absl::AnyInvocable<int64_t(int64_t, int64_t)
const> evaluator,
1482 const int num_nodes = dimension_values.
NumNodes(path);
1483 absl::Span<const int> nodes = dimension_values.
Nodes(path);
1484 absl::Span<Interval> cumuls = dimension_values.
MutableCumuls(path);
1485 for (
int r = 0; r < num_nodes; ++r) {
1486 const int node = nodes[r];
1487 Interval cumul = cumul_of_node[node];
1488 if (!cumul.DecreaseMax(capacity))
return false;
1495 absl::Span<int64_t> travels = dimension_values.
MutableTravels(path);
1497 absl::Span<const int> cnodes = dimension_values.
CommittedNodes(path);
1498 absl::Span<const int64_t> ctravels =
1506 const int i_limit = std::min(cnodes.size(), nodes.size());
1507 DCHECK(cnodes.empty() || cnodes[0] == nodes[0]);
1509 while (i < i_limit && cnodes[i] == nodes[i]) {
1510 travels[i - 1] = ctravels[i - 1];
1513 DCHECK(cnodes.empty() || cnodes.back() == nodes.back());
1514 int j = num_nodes - 2;
1515 const int delta = cnodes.size() - num_nodes;
1516 const int j_limit = i + std::max(0, -delta);
1517 while (j_limit <= j && nodes[j] == cnodes[j + delta]) {
1518 travels[j] = ctravels[j + delta];
1522 for (
int r = i; r <= j; ++r) {
1523 const int64_t travel = evaluator(nodes[r - 1], nodes[r]);
1524 travels[r - 1] = travel;
1528 absl::Span<Interval> transits = dimension_values.
MutableTransits(path);
1530 int64_t total_travel = 0;
1532 for (
int r = 1; r < num_nodes; ++r) {
1533 const int64_t travel = travels[r - 1];
1535 travel_sums[r] = total_travel;
1536 Interval transit{.min = travel, .max = travel};
1537 transit.Add(slack_of_node[nodes[r - 1]]);
1538 transits[r - 1] = transit;
1540 if (travel_sums.back() > span_upper_bound)
return false;
1541 dimension_values.
MutableSpan(path) = {.min = travel_sums.back(),
1542 .max = span_upper_bound};
1556 std::optional<absl::AnyInvocable<int64_t(int64_t, int64_t)
const>>
1557 pre_travel_evaluator,
1558 std::optional<absl::AnyInvocable<int64_t(int64_t, int64_t)
const>>
1559 post_travel_evaluator,
1561 const int num_nodes = dimension_values.
NumNodes(path);
1565 absl::Span<const int> nodes = dimension_values.
Nodes(path);
1566 if (pre_travel_evaluator.has_value()) {
1567 for (
int i = 0; i < num_nodes - 1; ++i) {
1568 post_visits[i] = (*pre_travel_evaluator)(nodes[i], nodes[i + 1]);
1570 post_visits.back() = 0;
1572 absl::c_fill(post_visits, 0);
1574 if (post_travel_evaluator.has_value()) {
1576 for (
int i = 1; i < num_nodes; ++i) {
1577 pre_visits[i] = (*post_travel_evaluator)(nodes[i - 1], nodes[i]);
1580 absl::c_fill(pre_visits, 0);
1586 absl::Span<
const std::pair<int64_t, int64_t>> interbreaks) {
1589 const int64_t total_travel = dimension_values.
TravelSums(path).back();
1593 int64_t lb_span_tw = total_travel;
1594 const absl::Span<Interval> cumuls = dimension_values.
MutableCumuls(path);
1595 Interval& start = cumuls.front();
1596 Interval&
end = cumuls.back();
1601 if (br.is_performed.min == 0)
continue;
1602 if (br.end.min <= start.max ||
end.min <= br.start.max)
continue;
1604 CapAddTo(br.duration.min, &lb_span_tw);
1605 if (!start.DecreaseMax(br.start.max))
return false;
1606 if (!
end.IncreaseMin(br.end.min))
return false;
1608 Interval& span = dimension_values.
MutableSpan(path);
1609 if (!span.IncreaseMin(std::max(lb_span_tw,
CapSub(
end.min, start.max)))) {
1617 if (!start.IncreaseMin(
CapSub(
end.min, span.max)))
return false;
1618 if (!
end.DecreaseMax(
CapAdd(start.max, span.max)))
return false;
1620 int num_feasible_breaks = 0;
1622 if (start.min <= br.start.max && br.end.min <=
end.max) {
1623 break_start_min = std::min(break_start_min, br.start.min);
1624 break_end_max = std::max(break_end_max, br.end.max);
1625 ++num_feasible_breaks;
1632 for (
const auto& [max_interbreak, min_break_duration] : interbreaks) {
1637 if (max_interbreak == 0) {
1638 if (total_travel > 0)
return false;
1641 int64_t min_num_breaks =
1642 std::max<int64_t>(0, (total_travel - 1) / max_interbreak);
1643 if (span.min > max_interbreak) {
1644 min_num_breaks = std::max<int64_t>(min_num_breaks, 1);
1646 if (min_num_breaks > num_feasible_breaks)
return false;
1647 if (!span.IncreaseMin(
CapAdd(
1648 total_travel,
CapProd(min_num_breaks, min_break_duration)))) {
1651 if (min_num_breaks > 0) {
1652 if (!start.IncreaseMin(
CapSub(break_start_min, max_interbreak))) {
1655 if (!
end.DecreaseMax(
CapAdd(break_end_max, max_interbreak))) {
1660 return start.DecreaseMax(
CapSub(
end.max, span.min)) &&
1661 end.IncreaseMin(
CapAdd(start.min, span.min));
1666class PathCumulFilter :
public BasePathFilter {
1668 PathCumulFilter(
const RoutingModel& routing_model,
1669 const RoutingDimension& dimension,
1670 bool propagate_own_objective_value,
1671 bool filter_objective_cost,
bool may_use_optimizers);
1672 ~PathCumulFilter()
override =
default;
1673 std::string DebugString()
const override {
1674 return "PathCumulFilter(" + name_ +
")";
1676 int64_t GetSynchronizedObjectiveValue()
const override {
1677 return propagate_own_objective_value_ ? synchronized_objective_value_ : 0;
1679 int64_t GetAcceptedObjectiveValue()
const override {
1680 return lns_detected() || !propagate_own_objective_value_
1682 : accepted_objective_value_;
1684 bool UsesDimensionOptimizers() {
1685 if (!may_use_optimizers_)
return false;
1686 for (
int vehicle = 0; vehicle < routing_model_.vehicles(); ++vehicle) {
1687 if (FilterWithDimensionCumulOptimizerForVehicle(vehicle))
return true;
1693 using Interval = DimensionValues::Interval;
1696 int64_t coefficient = 0;
1700 std::vector<Interval> ExtractInitialCumulIntervals();
1701 std::vector<Interval> ExtractInitialSlackIntervals();
1702 std::vector<std::vector<RoutingDimension::NodePrecedence>>
1703 ExtractNodeIndexToPrecedences()
const;
1704 std::vector<SoftBound> ExtractCumulSoftUpperBounds()
const;
1705 std::vector<SoftBound> ExtractCumulSoftLowerBounds()
const;
1706 std::vector<const PiecewiseLinearFunction*> ExtractCumulPiecewiseLinearCosts()
1708 std::vector<const RoutingModel::TransitCallback2*> ExtractEvaluators()
const;
1709 using VehicleBreak = DimensionValues::VehicleBreak;
1710 std::vector<std::vector<VehicleBreak>> ExtractInitialVehicleBreaks()
const;
1712 bool InitializeAcceptPath()
override {
1713 accepted_objective_value_ = synchronized_objective_value_;
1714 dimension_values_.Revert();
1715 cost_of_path_.Revert();
1716 global_span_cost_.Revert();
1717 location_of_node_.Revert();
1720 bool AcceptPath(int64_t path_start, int64_t chain_start,
1721 int64_t chain_end)
override;
1722 bool FinalizeAcceptPath(int64_t objective_min,
1723 int64_t objective_max)
override;
1725 void OnBeforeSynchronizePaths(
bool synchronizing_all_paths)
override;
1726 void OnSynchronizePathFromStart(int64_t start)
override;
1727 void OnAfterSynchronizePaths()
override;
1732 bool FillDimensionValues(
int path);
1735 bool PropagateTransitsAndSpans(
int path);
1737 bool PropagateTransitsWithForbiddenIntervals(
int path);
1739 bool PropagateSpan(
int path);
1741 bool PropagatePickupToDeliveryLimits(
int path);
1743 bool PropagateVehicleBreaks(
int path);
1746 bool FilterPrecedences()
const {
return !node_index_to_precedences_.empty(); }
1748 bool FilterGlobalSpanCost()
const {
1749 return global_span_cost_coefficient_ != 0;
1752 bool FilterVehicleBreaks(
int path)
const {
1753 return dimension_.HasBreakConstraints() &&
1754 (!dimension_.GetBreakIntervalsOfVehicle(path).empty() ||
1755 !dimension_.GetBreakDistanceDurationOfVehicle(path).empty());
1758 bool FilterSoftSpanCost(
int path)
const {
1759 return dimension_.HasSoftSpanUpperBounds() &&
1760 dimension_.GetSoftSpanUpperBoundForVehicle(path).cost > 0;
1763 bool FilterSoftSpanQuadraticCost(
int path)
const {
1764 if (!dimension_.HasQuadraticCostSoftSpanUpperBounds())
return false;
1765 const auto [
bound, cost] =
1766 dimension_.GetQuadraticCostSoftSpanUpperBoundForVehicle(path);
1770 bool FilterWithDimensionCumulOptimizerForVehicle(
int vehicle)
const {
1771 if (!may_use_optimizers_)
return false;
1772 if (!cumul_piecewise_linear_costs_.empty())
return false;
1774 int num_linear_constraints = 0;
1775 if (dimension_.GetSpanCostCoefficientForVehicle(vehicle) > 0 ||
1776 dimension_.GetSlackCostCoefficientForVehicle(vehicle) > 0) {
1777 ++num_linear_constraints;
1779 if (FilterSoftSpanCost(vehicle)) ++num_linear_constraints;
1780 if (!cumul_soft_upper_bounds_.empty()) ++num_linear_constraints;
1781 if (!cumul_soft_lower_bounds_.empty()) ++num_linear_constraints;
1782 if (path_span_upper_bounds_[vehicle] <
kint64max) {
1783 ++num_linear_constraints;
1785 const bool has_breaks = FilterVehicleBreaks(vehicle);
1786 if (has_breaks) ++num_linear_constraints;
1794 return num_linear_constraints >= 2 &&
1795 (has_breaks || filter_objective_cost_);
1799 const RoutingModel& routing_model_;
1800 const RoutingDimension& dimension_;
1801 const std::vector<Interval> initial_cumul_;
1802 const std::vector<Interval> initial_slack_;
1803 const std::vector<std::vector<VehicleBreak>> initial_vehicle_breaks_;
1805 const std::vector<const RoutingModel::TransitCallback2*> evaluators_;
1806 const std::vector<int64_t> path_capacities_;
1807 const std::vector<int64_t> path_span_upper_bounds_;
1808 const std::vector<int64_t> path_total_slack_cost_coefficients_;
1833 const int64_t global_span_cost_coefficient_;
1837 bool operator<(
const ValuedPath& other)
const {
1838 return std::tie(value, path) < std::tie(other.value, other.path);
1840 bool operator>(
const ValuedPath& other)
const {
1841 return std::tie(value, path) > std::tie(other.value, other.path);
1845 std::vector<ValuedPath> paths_by_decreasing_span_min_;
1847 std::vector<ValuedPath> paths_by_decreasing_end_min_;
1849 std::vector<ValuedPath> paths_by_increasing_start_max_;
1851 CommittableValue<int64_t> global_span_cost_;
1854 const std::vector<SoftBound> cumul_soft_upper_bounds_;
1856 const std::vector<SoftBound> cumul_soft_lower_bounds_;
1858 const std::vector<const PiecewiseLinearFunction*>
1859 cumul_piecewise_linear_costs_;
1861 const bool has_forbidden_intervals_;
1865 DimensionValues dimension_values_;
1866 PrePostVisitValues visits_;
1867 BreakPropagator break_propagator_;
1871 CommittableArray<int64_t> cost_of_path_;
1872 int64_t synchronized_objective_value_;
1873 int64_t accepted_objective_value_;
1875 struct RankAndIndex {
1883 CommittableArray<RankAndIndex> pickup_rank_and_alternative_index_of_pair_;
1888 const std::vector<std::vector<RoutingDimension::NodePrecedence>>
1889 node_index_to_precedences_;
1890 absl::flat_hash_map<std::pair<int, int>, int64_t> precedence_offsets_;
1891 struct PathAndRank {
1897 CommittableArray<PathAndRank> location_of_node_;
1900 const std::string name_;
1902 LocalDimensionCumulOptimizer* lp_optimizer_;
1903 LocalDimensionCumulOptimizer* mp_optimizer_;
1904 const std::function<int64_t(int64_t)> path_accessor_;
1905 const bool filter_objective_cost_;
1907 const bool may_use_optimizers_;
1908 const bool propagate_own_objective_value_;
1912template <
typename T>
1913std::vector<T> SumOfVectors(
const std::vector<T>& v1,
1914 const std::vector<T>& v2) {
1915 DCHECK_EQ(v1.size(), v2.size());
1916 std::vector<T> sum(v1.size());
1917 absl::c_transform(v1, v2, sum.begin(), [](T a, T b) { return CapAdd(a, b); });
1922std::vector<PathCumulFilter::Interval>
1923PathCumulFilter::ExtractInitialCumulIntervals() {
1924 std::vector<Interval> intervals;
1925 intervals.reserve(dimension_.cumuls().size());
1926 for (
const IntVar* cumul : dimension_.cumuls()) {
1927 intervals.push_back({cumul->Min(), cumul->Max()});
1932std::vector<PathCumulFilter::Interval>
1933PathCumulFilter::ExtractInitialSlackIntervals() {
1934 std::vector<Interval> intervals;
1935 intervals.reserve(dimension_.slacks().size());
1936 for (
const IntVar* slack : dimension_.slacks()) {
1937 intervals.push_back({slack->Min(), slack->Max()});
1942std::vector<PathCumulFilter::SoftBound>
1943PathCumulFilter::ExtractCumulSoftUpperBounds()
const {
1944 const int num_cumuls = dimension_.cumuls().size();
1945 std::vector<SoftBound> bounds(num_cumuls,
1946 {.bound =
kint64max, .coefficient = 0});
1947 bool has_some_bound =
false;
1948 for (
int i = 0;
i < num_cumuls; ++
i) {
1949 if (!dimension_.HasCumulVarSoftUpperBound(i))
continue;
1950 const int64_t
bound = dimension_.GetCumulVarSoftUpperBound(i);
1951 const int64_t coeff = dimension_.GetCumulVarSoftUpperBoundCoefficient(i);
1952 bounds[
i] = {.bound =
bound, .coefficient = coeff};
1955 if (!has_some_bound) bounds.clear();
1959std::vector<PathCumulFilter::SoftBound>
1960PathCumulFilter::ExtractCumulSoftLowerBounds()
const {
1961 const int num_cumuls = dimension_.cumuls().size();
1962 std::vector<SoftBound> bounds(num_cumuls, {.bound = 0, .coefficient = 0});
1963 bool has_some_bound =
false;
1964 for (
int i = 0;
i < num_cumuls; ++
i) {
1965 if (!dimension_.HasCumulVarSoftLowerBound(i))
continue;
1966 const int64_t
bound = dimension_.GetCumulVarSoftLowerBound(i);
1967 const int64_t coeff = dimension_.GetCumulVarSoftLowerBoundCoefficient(i);
1968 bounds[
i] = {.bound =
bound, .coefficient = coeff};
1969 has_some_bound |=
bound > 0 && coeff != 0;
1971 if (!has_some_bound) bounds.clear();
1975std::vector<const PiecewiseLinearFunction*>
1976PathCumulFilter::ExtractCumulPiecewiseLinearCosts()
const {
1977 const int num_cumuls = dimension_.cumuls().size();
1978 std::vector<const PiecewiseLinearFunction*> costs(num_cumuls,
nullptr);
1979 bool has_some_cost =
false;
1980 for (
int i = 0;
i < dimension_.cumuls().size(); ++
i) {
1981 if (!dimension_.HasCumulVarPiecewiseLinearCost(i))
continue;
1982 const PiecewiseLinearFunction*
const cost =
1983 dimension_.GetCumulVarPiecewiseLinearCost(i);
1984 if (cost ==
nullptr)
continue;
1985 has_some_cost =
true;
1988 if (!has_some_cost) costs.clear();
1992std::vector<const RoutingModel::TransitCallback2*>
1993PathCumulFilter::ExtractEvaluators()
const {
1994 const int num_paths = NumPaths();
1995 std::vector<const RoutingModel::TransitCallback2*> evaluators(num_paths);
1996 for (
int i = 0;
i < num_paths; ++
i) {
1997 evaluators[
i] = &dimension_.transit_evaluator(i);
2002std::vector<std::vector<RoutingDimension::NodePrecedence>>
2003PathCumulFilter::ExtractNodeIndexToPrecedences()
const {
2004 std::vector<std::vector<RoutingDimension::NodePrecedence>>
2005 node_index_to_precedences;
2006 const std::vector<RoutingDimension::NodePrecedence>& node_precedences =
2007 dimension_.GetNodePrecedences();
2008 if (!node_precedences.empty()) {
2009 node_index_to_precedences.resize(initial_cumul_.size());
2010 for (
const auto& node_precedence : node_precedences) {
2011 node_index_to_precedences[node_precedence.first_node].push_back(
2013 node_index_to_precedences[node_precedence.second_node].push_back(
2017 return node_index_to_precedences;
2020std::vector<std::vector<DimensionValues::VehicleBreak>>
2021PathCumulFilter::ExtractInitialVehicleBreaks()
const {
2022 const int num_vehicles = routing_model_.vehicles();
2023 std::vector<std::vector<VehicleBreak>> vehicle_breaks(num_vehicles);
2024 if (!dimension_.HasBreakConstraints())
return vehicle_breaks;
2025 for (
int v = 0; v < num_vehicles; ++v) {
2026 for (
const IntervalVar* br : dimension_.GetBreakIntervalsOfVehicle(v)) {
2027 vehicle_breaks[v].push_back(
2028 {.start = {.min = br->StartMin(), .max = br->StartMax()},
2029 .end = {.min = br->EndMin(), .max = br->EndMax()},
2030 .duration = {.min = br->DurationMin(), .max = br->DurationMax()},
2031 .is_performed = {.min = br->MustBePerformed(),
2032 .max = br->MayBePerformed()}});
2035 return vehicle_breaks;
2038PathCumulFilter::PathCumulFilter(
const RoutingModel& routing_model,
2039 const RoutingDimension& dimension,
2040 bool propagate_own_objective_value,
2041 bool filter_objective_cost,
2042 bool may_use_optimizers)
2043 : BasePathFilter(routing_model.Nexts(), dimension.cumuls().size(),
2044 routing_model.GetPathsMetadata()),
2045 routing_model_(routing_model),
2046 dimension_(dimension),
2047 initial_cumul_(ExtractInitialCumulIntervals()),
2048 initial_slack_(ExtractInitialSlackIntervals()),
2049 initial_vehicle_breaks_(ExtractInitialVehicleBreaks()),
2050 evaluators_(ExtractEvaluators()),
2052 path_capacities_(dimension.vehicle_capacities()),
2053 path_span_upper_bounds_(dimension.vehicle_span_upper_bounds()),
2054 path_total_slack_cost_coefficients_(
2055 SumOfVectors(dimension.vehicle_span_cost_coefficients(),
2056 dimension.vehicle_slack_cost_coefficients())),
2057 global_span_cost_coefficient_(dimension.global_span_cost_coefficient()),
2058 global_span_cost_(0),
2059 cumul_soft_upper_bounds_(ExtractCumulSoftUpperBounds()),
2060 cumul_soft_lower_bounds_(ExtractCumulSoftLowerBounds()),
2061 cumul_piecewise_linear_costs_(ExtractCumulPiecewiseLinearCosts()),
2062 has_forbidden_intervals_(
2063 absl::c_any_of(dimension.forbidden_intervals(),
2064 [](const SortedDisjointIntervalList& intervals) {
2065 return intervals.NumIntervals() > 0;
2068 dimension_values_(routing_model.vehicles(), dimension.cumuls().size()),
2069 visits_(routing_model.vehicles(), dimension.cumuls().size()),
2070 break_propagator_(dimension.cumuls().size()),
2071 cost_of_path_(NumPaths(), 0),
2072 synchronized_objective_value_(0),
2073 accepted_objective_value_(0),
2074 pickup_rank_and_alternative_index_of_pair_(
2075 routing_model_.GetPickupAndDeliveryPairs().size(), {-1, -1}),
2076 node_index_to_precedences_(ExtractNodeIndexToPrecedences()),
2077 location_of_node_(dimension.cumuls().size(), {-1, -1}),
2078 name_(dimension.name()),
2079 lp_optimizer_(routing_model.GetMutableLocalCumulLPOptimizer(dimension)),
2080 mp_optimizer_(routing_model.GetMutableLocalCumulMPOptimizer(dimension)),
2081 path_accessor_([
this](int64_t node) {
return GetNext(node); }),
2082 filter_objective_cost_(filter_objective_cost),
2083 may_use_optimizers_(may_use_optimizers),
2084 propagate_own_objective_value_(propagate_own_objective_value) {
2085 for (
int node = 0; node < node_index_to_precedences_.size(); ++node) {
2086 for (
const auto [first_node, second_node, offset,
2087 unused_performed_constraint] :
2088 node_index_to_precedences_[node]) {
2090 &precedence_offsets_, {first_node, second_node}, offset);
2091 current_offset = std::max(current_offset, offset);
2095 for (
int vehicle = 0; vehicle < routing_model.vehicles(); vehicle++) {
2096 if (FilterWithDimensionCumulOptimizerForVehicle(vehicle)) {
2097 DCHECK_NE(lp_optimizer_,
nullptr);
2098 DCHECK_NE(mp_optimizer_,
nullptr);
2104bool PathCumulFilter::PropagateTransitsAndSpans(
int path) {
2105 if (has_forbidden_intervals_) {
2106 return PropagateSpan(path) &&
2107 PropagateTransitsWithForbiddenIntervals(path) && PropagateSpan(path);
2113bool PathCumulFilter::PropagateTransitsWithForbiddenIntervals(
int path) {
2114 DCHECK_LT(0, dimension_values_.NumNodes(path));
2115 absl::Span<Interval> cumuls = dimension_values_.MutableCumuls(path);
2116 absl::Span<const Interval> transits = dimension_values_.Transits(path);
2117 absl::Span<const int> nodes = dimension_values_.Nodes(path);
2118 const int num_nodes = dimension_values_.NumNodes(path);
2119 auto reduce_to_allowed_interval = [
this](Interval& interval,
2121 DCHECK(!interval.IsEmpty());
2122 interval.min = dimension_.GetFirstPossibleGreaterOrEqualValueForNode(
2123 node, interval.min);
2124 if (interval.IsEmpty())
return false;
2126 dimension_.GetLastPossibleLessOrEqualValueForNode(node, interval.max);
2127 return !interval.IsEmpty();
2130 if (!reduce_to_allowed_interval(cumuls[0], nodes[0]))
return false;
2131 Interval cumul = cumuls[0];
2132 for (
int r = 1; r < num_nodes; ++r) {
2133 cumul.Add(transits[r - 1]);
2134 if (!cumul.IntersectWith(cumuls[r]))
return false;
2135 if (!reduce_to_allowed_interval(cumul, nodes[r]))
return false;
2139 for (
int r = num_nodes - 2; r >= 0; --r) {
2140 cumul.Subtract(transits[r]);
2141 if (!cumul.IntersectWith(cumuls[r]))
return false;
2142 if (!reduce_to_allowed_interval(cumul, nodes[r]))
return false;
2148bool PathCumulFilter::PropagateSpan(
int path) {
2149 absl::Span<const int64_t> travel_sums = dimension_values_.TravelSums(path);
2150 absl::Span<Interval> cumuls = dimension_values_.MutableCumuls(path);
2153 Interval start = cumuls.front();
2154 Interval
end = cumuls.back();
2155 Interval span = dimension_values_.Span(path);
2156 if (!span.IncreaseMin(travel_sums.back()))
return false;
2160 if (!
end.DecreaseMax(
CapAdd(start.max, span.max)))
return false;
2161 if (!start.IncreaseMin(
CapSub(
end.min, span.max)))
return false;
2162 if (!span.IncreaseMin(
CapSub(
end.min, start.max)))
return false;
2165 if (!span.DecreaseMax(
CapSub(
end.max, start.min)))
return false;
2166 if (!
end.IncreaseMin(
CapAdd(start.min, span.min)))
return false;
2167 if (!start.DecreaseMax(
CapSub(
end.max, span.min)))
return false;
2169 cumuls.front() = start;
2170 cumuls.back() =
end;
2171 dimension_values_.MutableSpan(path) = span;
2175bool PathCumulFilter::FillDimensionValues(
int path) {
2177 int node = Start(path);
2178 dimension_values_.PushNode(node);
2179 while (node < Size()) {
2180 const int next = GetNext(node);
2181 dimension_values_.PushNode(next);
2184 dimension_values_.MakePathFromNewNodes(path);
2186 path, path_capacities_[path], path_span_upper_bounds_[path],
2187 initial_cumul_, initial_slack_, *evaluators_[path],
2188 dimension_values_)) {
2191 dimension_values_.MutableVehicleBreaks(path) = initial_vehicle_breaks_[path];
2195bool PathCumulFilter::PropagatePickupToDeliveryLimits(
int path) {
2196 const int num_nodes = dimension_values_.NumNodes(path);
2197 absl::Span<const int> nodes = dimension_values_.Nodes(path);
2198 absl::Span<const int64_t> travel_sums = dimension_values_.TravelSums(path);
2199 absl::Span<Interval> cumuls = dimension_values_.MutableCumuls(path);
2206 pickup_rank_and_alternative_index_of_pair_.Revert();
2207 for (
int rank = 1; rank < num_nodes - 1; ++rank) {
2208 const int node = nodes[rank];
2209 const std::optional<RoutingModel::PickupDeliveryPosition> pickup_pos =
2210 routing_model_.GetPickupPosition(node);
2212 if (pickup_pos.has_value()) {
2213 const auto [pair_index, pickup_alternative_index] = *pickup_pos;
2214 pickup_rank_and_alternative_index_of_pair_.Set(
2215 pair_index, {.rank = rank, .index = pickup_alternative_index});
2222 const std::optional<RoutingModel::PickupDeliveryPosition> delivery_pos =
2223 routing_model_.GetDeliveryPosition(node);
2224 if (!delivery_pos.has_value())
continue;
2225 const auto [pair_index, delivery_alt_index] = *delivery_pos;
2226 const auto [pickup_rank, pickup_alt_index] =
2227 pickup_rank_and_alternative_index_of_pair_.Get(pair_index);
2228 if (pickup_rank == -1)
continue;
2230 const int64_t limit = dimension_.GetPickupToDeliveryLimitForPair(
2231 pair_index, pickup_alt_index, delivery_alt_index);
2236 const int64_t total_travel =
2237 CapSub(travel_sums[rank], travel_sums[pickup_rank]);
2238 if (total_travel > limit)
return false;
2241 if (!cumuls[rank].DecreaseMax(
CapAdd(cumuls[pickup_rank].max, limit))) {
2244 if (!cumuls[pickup_rank].IncreaseMin(
CapSub(cumuls[rank].min, limit))) {
2251bool PathCumulFilter::PropagateVehicleBreaks(
int path) {
2253 path, dimension_values_,
2254 dimension_.GetBreakDistanceDurationOfVehicle(path));
2257bool PathCumulFilter::AcceptPath(int64_t path_start, int64_t ,
2259 const int path = GetPath(path_start);
2260 if (!FillDimensionValues(path))
return false;
2265 if (!PropagateTransitsAndSpans(path))
return false;
2266 if (dimension_.HasPickupToDeliveryLimits()) {
2267 if (!PropagatePickupToDeliveryLimits(path))
return false;
2269 if (!PropagateTransitsAndSpans(path))
return false;
2271 if (FilterVehicleBreaks(path)) {
2274 const auto& interbreaks =
2275 dimension_.GetBreakDistanceDurationOfVehicle(path);
2276 if (!PropagateVehicleBreaks(path) ||
2277 break_propagator_.PropagateInterbreak(path, dimension_values_,
2279 BreakPropagator::PropagationResult::kInfeasible ||
2280 !PropagateTransitsAndSpans(path)) {
2285 auto any_invocable = [
this](
int evaluator_index)
2286 -> std::optional<absl::AnyInvocable<int64_t(int64_t, int64_t)
const>> {
2287 const auto& evaluator =
2288 evaluator_index == -1
2290 : dimension_.model()->TransitCallback(evaluator_index);
2291 if (evaluator ==
nullptr)
return std::nullopt;
2295 path, dimension_values_,
2296 any_invocable(dimension_.GetPreTravelEvaluatorOfVehicle(path)),
2297 any_invocable(dimension_.GetPostTravelEvaluatorOfVehicle(path)),
2300 BreakPropagator::PropagationResult result = BreakPropagator::kChanged;
2301 int num_iterations = 2;
2302 while (result == BreakPropagator::kChanged && num_iterations-- > 0) {
2304 break_propagator_.FastPropagations(path, dimension_values_, visits_);
2305 if (result == BreakPropagator::kChanged) {
2306 if (!PropagateVehicleBreaks(path) ||
2307 break_propagator_.PropagateInterbreak(path, dimension_values_,
2309 BreakPropagator::PropagationResult::kInfeasible ||
2310 !PropagateTransitsAndSpans(path)) {
2315 if (result == BreakPropagator::kInfeasible)
return false;
2320 if (!filter_objective_cost_)
return true;
2322 CapSubFrom(cost_of_path_.Get(path), &accepted_objective_value_);
2323 if (routing_model_.IsEnd(GetNext(path_start)) &&
2324 !routing_model_.IsVehicleUsedWhenEmpty(path)) {
2325 cost_of_path_.Set(path, 0);
2329 const absl::Span<const Interval> cumuls = dimension_values_.Cumuls(path);
2330 const absl::Span<const int> nodes = dimension_values_.Nodes(path);
2331 const Interval span = dimension_values_.Span(path);
2332 const int64_t total_travel = dimension_values_.TravelSums(path).back();
2333 const int64_t min_total_slack =
CapSub(span.min, total_travel);
2335 int64_t new_path_cost = 0;
2337 CapAddTo(
CapProd(path_total_slack_cost_coefficients_[path], min_total_slack),
2340 if (dimension_.HasSoftSpanUpperBounds()) {
2341 const auto [
bound, cost] = dimension_.GetSoftSpanUpperBoundForVehicle(path);
2345 if (dimension_.HasQuadraticCostSoftSpanUpperBounds()) {
2346 const auto [
bound, cost] =
2347 dimension_.GetQuadraticCostSoftSpanUpperBoundForVehicle(path);
2348 const int64_t violation = std::max<int64_t>(0,
CapSub(span.min, bound));
2352 const int num_path_nodes = dimension_values_.NumNodes(path);
2353 if (!cumul_soft_lower_bounds_.empty()) {
2354 for (
int r = 0; r < num_path_nodes; ++r) {
2355 const auto [
bound, coef] = cumul_soft_lower_bounds_[nodes[r]];
2357 CapProd(coef, std::max<int64_t>(0,
CapSub(bound, cumuls[r].max))),
2362 if (!cumul_soft_upper_bounds_.empty()) {
2363 for (
int r = 0; r < num_path_nodes; ++r) {
2364 const auto [
bound, coef] = cumul_soft_upper_bounds_[nodes[r]];
2366 CapProd(coef, std::max<int64_t>(0,
CapSub(cumuls[r].min, bound))),
2373 if (!cumul_piecewise_linear_costs_.empty()) {
2374 for (
int r = 0; r < num_path_nodes; ++r) {
2375 const PiecewiseLinearFunction* cost =
2376 cumul_piecewise_linear_costs_[nodes[r]];
2377 if (cost ==
nullptr)
continue;
2378 CapAddTo(cost->Value(cumuls[r].min), &new_path_cost);
2382 CapAddTo(new_path_cost, &accepted_objective_value_);
2383 cost_of_path_.Set(path, new_path_cost);
2387bool PathCumulFilter::FinalizeAcceptPath(int64_t ,
2388 int64_t objective_max) {
2389 if (lns_detected())
return true;
2390 if (FilterPrecedences()) {
2396 for (
const int path : dimension_values_.ChangedPaths()) {
2397 const absl::Span<const int64_t> travel_sums =
2398 dimension_values_.TravelSums(path);
2401 for (
const int node : dimension_values_.Nodes(path)) {
2402 int64_t offset = std::numeric_limits<int64_t>::min();
2404 if (
gtl::FindCopy(precedence_offsets_, std::pair<int, int>{node, prev},
2406 CapSub(travel_sums[rank], travel_sums[rank + 1]) < offset) {
2415 for (
const int path : dimension_values_.ChangedPaths()) {
2416 for (
const int node : dimension_values_.CommittedNodes(path)) {
2417 location_of_node_.Set(node, {.path = -1, .rank = -1});
2420 for (
const int path : dimension_values_.ChangedPaths()) {
2421 const absl::Span<const int> nodes = dimension_values_.Nodes(path);
2422 const int num_nodes = nodes.size();
2423 for (
int rank = 0; rank < num_nodes; ++rank) {
2424 location_of_node_.Set(nodes[rank], {.path = path, .rank = rank});
2428 for (
const int path : dimension_values_.ChangedPaths()) {
2429 const absl::Span<const int> nodes = dimension_values_.Nodes(path);
2430 const int num_nodes = nodes.size();
2431 for (
int rank = 0; rank < num_nodes; ++rank) {
2432 const int node = nodes[rank];
2433 for (
const auto [first_node, second_node, offset,
2434 performed_constraint] :
2435 node_index_to_precedences_[node]) {
2436 const auto [path1, rank1] = location_of_node_.Get(first_node);
2437 const auto [path2, rank2] = location_of_node_.Get(second_node);
2438 if (path1 == -1 && !IsVarSynced(first_node))
continue;
2439 if (path2 == -1 && !IsVarSynced(second_node))
continue;
2440 switch (RoutingDimension::GetPrecedenceStatus(
2441 path1 == -1, path2 == -1, performed_constraint)) {
2442 case RoutingDimension::PrecedenceStatus::kActive:
2444 case RoutingDimension::PrecedenceStatus::kInactive:
2446 case RoutingDimension::PrecedenceStatus::kInvalid:
2449 DCHECK(node == first_node || node == second_node);
2450 DCHECK_EQ(first_node, dimension_values_.Nodes(path1)[rank1]);
2451 DCHECK_EQ(second_node, dimension_values_.Nodes(path2)[rank2]);
2454 if (path1 == path2 && rank2 < rank1) {
2455 absl::Span<const int64_t> travel_sums =
2456 dimension_values_.TravelSums(path);
2459 if (
CapSub(travel_sums[rank2], travel_sums[rank1]) < offset) {
2464 if (
CapAdd(dimension_values_.Cumuls(path1)[rank1].min, offset) >
2465 dimension_values_.Cumuls(path2)[rank2].max)
2471 if (filter_objective_cost_ && FilterGlobalSpanCost()) {
2474 int64_t global_span_min = 0;
2476 for (
const int path : dimension_values_.ChangedPaths()) {
2477 if (dimension_values_.NumNodes(path) == 0)
continue;
2478 if (dimension_values_.NumNodes(path) == 2 &&
2479 !routing_model_.IsVehicleUsedWhenEmpty(path)) {
2483 std::max(global_span_min, dimension_values_.Span(path).min);
2484 const int64_t end_min = dimension_values_.Cumuls(path).back().min;
2485 global_end_min = std::max(global_end_min, end_min);
2486 const int64_t start_max = dimension_values_.Cumuls(path).front().max;
2487 global_start_max = std::min(global_start_max, start_max);
2490 for (
const auto& [span_min, path] : paths_by_decreasing_span_min_) {
2491 if (dimension_values_.PathHasChanged(path))
continue;
2492 global_span_min = std::max(global_span_min, span_min);
2495 for (
const auto& [end_min, path] : paths_by_decreasing_end_min_) {
2496 if (dimension_values_.PathHasChanged(path))
continue;
2497 global_end_min = std::max(global_end_min, end_min);
2500 for (
const auto& [start_max, path] : paths_by_increasing_start_max_) {
2501 if (dimension_values_.PathHasChanged(path))
continue;
2502 global_start_max = std::min(global_start_max, start_max);
2507 std::max(global_span_min,
CapSub(global_end_min, global_start_max));
2508 const int64_t global_span_cost =
2509 CapProd(global_span_min, global_span_cost_coefficient_);
2510 CapSubFrom(global_span_cost_.Get(), &accepted_objective_value_);
2511 global_span_cost_.Set(global_span_cost);
2512 CapAddTo(global_span_cost, &accepted_objective_value_);
2516 if (may_use_optimizers_ && lp_optimizer_ !=
nullptr &&
2517 accepted_objective_value_ <= objective_max) {
2518 std::vector<int> paths_requiring_mp_optimizer;
2521 int solve_duration_shares = dimension_values_.ChangedPaths().size();
2522 for (
const int vehicle : dimension_values_.ChangedPaths()) {
2523 if (!FilterWithDimensionCumulOptimizerForVehicle(vehicle)) {
2526 int64_t path_cost_with_lp = 0;
2528 lp_optimizer_->ComputeRouteCumulCostWithoutFixedTransits(
2529 vehicle, 1.0 / solve_duration_shares,
2530 path_accessor_,
nullptr,
2531 filter_objective_cost_ ? &path_cost_with_lp :
nullptr);
2532 solve_duration_shares--;
2533 if (status == DimensionSchedulingStatus::INFEASIBLE)
return false;
2535 if (filter_objective_cost_ &&
2536 (status == DimensionSchedulingStatus::OPTIMAL ||
2537 status == DimensionSchedulingStatus::RELAXED_OPTIMAL_ONLY) &&
2538 path_cost_with_lp > cost_of_path_.Get(vehicle)) {
2539 CapSubFrom(cost_of_path_.Get(vehicle), &accepted_objective_value_);
2540 CapAddTo(path_cost_with_lp, &accepted_objective_value_);
2541 if (accepted_objective_value_ > objective_max)
return false;
2542 cost_of_path_.Set(vehicle, path_cost_with_lp);
2545 DCHECK_NE(mp_optimizer_,
nullptr);
2546 if (FilterVehicleBreaks(vehicle) ||
2547 (filter_objective_cost_ &&
2548 (FilterSoftSpanQuadraticCost(vehicle) ||
2549 status == DimensionSchedulingStatus::RELAXED_OPTIMAL_ONLY))) {
2550 paths_requiring_mp_optimizer.push_back(vehicle);
2554 DCHECK_LE(accepted_objective_value_, objective_max);
2556 solve_duration_shares = paths_requiring_mp_optimizer.size();
2557 for (
const int vehicle : paths_requiring_mp_optimizer) {
2558 int64_t path_cost_with_mp = 0;
2560 mp_optimizer_->ComputeRouteCumulCostWithoutFixedTransits(
2561 vehicle, 1.0 / solve_duration_shares,
2562 path_accessor_,
nullptr,
2563 filter_objective_cost_ ? &path_cost_with_mp :
nullptr);
2564 solve_duration_shares--;
2565 if (status == DimensionSchedulingStatus::INFEASIBLE) {
2568 if (filter_objective_cost_ &&
2569 status == DimensionSchedulingStatus::OPTIMAL &&
2570 path_cost_with_mp > cost_of_path_.Get(vehicle)) {
2571 CapSubFrom(cost_of_path_.Get(vehicle), &accepted_objective_value_);
2572 CapAddTo(path_cost_with_mp, &accepted_objective_value_);
2573 if (accepted_objective_value_ > objective_max)
return false;
2574 cost_of_path_.Set(vehicle, path_cost_with_mp);
2578 return accepted_objective_value_ <= objective_max;
2581void PathCumulFilter::OnBeforeSynchronizePaths(
bool synchronizing_all_paths) {
2582 if (synchronizing_all_paths) {
2586 dimension_values_.Reset();
2587 cost_of_path_.SetAllAndCommit(0);
2588 location_of_node_.SetAllAndCommit({-1, -1});
2589 global_span_cost_.SetAndCommit(0);
2590 synchronized_objective_value_ = 0;
2593 accepted_objective_value_ = synchronized_objective_value_;
2594 if (HasAnySyncedPath()) {
2597 InitializeAcceptPath();
2601void PathCumulFilter::OnSynchronizePathFromStart(int64_t start) {
2602 DCHECK(IsVarSynced(start));
2605 const int path = GetPath(start);
2606 const bool is_accepted = AcceptPath(start, start, End(path));
2607 DCHECK(is_accepted);
2610void PathCumulFilter::OnAfterSynchronizePaths() {
2611 if (filter_objective_cost_ && FilterGlobalSpanCost()) {
2614 paths_by_decreasing_span_min_.clear();
2615 paths_by_decreasing_end_min_.clear();
2616 paths_by_increasing_start_max_.clear();
2617 for (
int path = 0; path < NumPaths(); ++path) {
2618 const int num_path_nodes = dimension_values_.Nodes(path).size();
2619 if (num_path_nodes == 0 ||
2620 (num_path_nodes == 2 &&
2621 !routing_model_.IsVehicleUsedWhenEmpty(path))) {
2624 paths_by_decreasing_span_min_.push_back(
2625 {.value = dimension_values_.Span(path).min, .path = path});
2626 paths_by_decreasing_end_min_.push_back(
2627 {.value = dimension_values_.Cumuls(path).back().min, .path = path});
2628 paths_by_increasing_start_max_.push_back(
2629 {.value = dimension_values_.Cumuls(path)[0].max, .path = path});
2631 absl::c_sort(paths_by_decreasing_span_min_, std::greater<ValuedPath>());
2632 absl::c_sort(paths_by_decreasing_end_min_, std::greater<ValuedPath>());
2633 absl::c_sort(paths_by_increasing_start_max_);
2637 dimension_values_.Commit();
2638 cost_of_path_.Commit();
2639 global_span_cost_.Commit();
2640 location_of_node_.Commit();
2641 synchronized_objective_value_ = accepted_objective_value_;
2647 bool propagate_own_objective_value,
2648 bool filter_objective_cost,
2649 bool may_use_optimizers) {
2652 new PathCumulFilter(model, dimension, propagate_own_objective_value,
2653 filter_objective_cost, may_use_optimizers));
2658bool DimensionHasCumulCost(
const RoutingDimension& dimension) {
2659 if (dimension.global_span_cost_coefficient() != 0)
return true;
2660 if (dimension.HasSoftSpanUpperBounds())
return true;
2661 if (dimension.HasQuadraticCostSoftSpanUpperBounds())
return true;
2662 if (absl::c_any_of(dimension.vehicle_span_cost_coefficients(),
2663 [](int64_t coefficient) { return coefficient != 0; })) {
2666 if (absl::c_any_of(dimension.vehicle_slack_cost_coefficients(),
2667 [](int64_t coefficient) { return coefficient != 0; })) {
2670 for (
int i = 0;
i < dimension.cumuls().size(); ++
i) {
2671 if (dimension.HasCumulVarSoftUpperBound(i))
return true;
2672 if (dimension.HasCumulVarSoftLowerBound(i))
return true;
2673 if (dimension.HasCumulVarPiecewiseLinearCost(i))
return true;
2678bool DimensionHasPathCumulConstraint(
const RoutingDimension& dimension) {
2679 if (dimension.HasBreakConstraints())
return true;
2680 if (dimension.HasPickupToDeliveryLimits())
return true;
2682 dimension.vehicle_span_upper_bounds(), [](int64_t upper_bound) {
2683 return upper_bound != std::numeric_limits<int64_t>::max();
2687 if (absl::c_any_of(dimension.slacks(),
2688 [](IntVar* slack) { return slack->Min() > 0; })) {
2691 const std::vector<IntVar*>& cumuls = dimension.cumuls();
2692 for (
int i = 0;
i < cumuls.size(); ++
i) {
2693 IntVar*
const cumul_var = cumuls[
i];
2694 if (cumul_var->Min() > 0 &&
2695 cumul_var->Max() < std::numeric_limits<int64_t>::max() &&
2696 !dimension.model()->IsEnd(i)) {
2699 if (dimension.forbidden_intervals()[i].NumIntervals() > 0)
return true;
2708 const std::vector<RoutingDimension*>& dimensions,
2709 std::vector<LocalSearchFilterManager::FilterEvent>* filters) {
2715 const int num_vehicles = dimension->model()->vehicles();
2716 std::vector<Interval> path_capacity(num_vehicles);
2717 std::vector<int> path_class(num_vehicles);
2718 for (
int v = 0; v < num_vehicles; ++v) {
2719 const auto& vehicle_capacities = dimension->vehicle_capacities();
2720 path_capacity[v] = {0, vehicle_capacities[v]};
2721 path_class[v] = dimension->vehicle_to_class(v);
2728 const int num_vehicle_classes =
2729 1 + *std::max_element(path_class.begin(), path_class.end());
2730 const int num_cumuls = dimension->cumuls().size();
2731 const int num_slacks = dimension->slacks().size();
2732 std::vector<std::function<Interval(int64_t, int64_t)>> transits(
2733 num_vehicle_classes,
nullptr);
2734 for (
int vehicle = 0; vehicle < num_vehicles; ++vehicle) {
2735 const int vehicle_class = path_class[vehicle];
2736 if (transits[vehicle_class] !=
nullptr)
continue;
2737 const auto& unary_evaluator =
2738 dimension->GetUnaryTransitEvaluator(vehicle);
2739 if (unary_evaluator !=
nullptr) {
2740 transits[vehicle_class] = [&unary_evaluator, dimension, num_slacks](
2741 int64_t node, int64_t) -> Interval {
2742 if (node >= num_slacks)
return {0, 0};
2743 const int64_t min_transit = unary_evaluator(node);
2744 const int64_t max_transit =
2745 CapAdd(min_transit, dimension->SlackVar(node)->Max());
2746 return {min_transit, max_transit};
2749 const auto& binary_evaluator =
2750 dimension->GetBinaryTransitEvaluator(vehicle);
2752 transits[vehicle_class] = [&binary_evaluator, dimension, num_slacks](
2753 int64_t node, int64_t next) -> Interval {
2754 if (node >= num_slacks)
return {0, 0};
2755 const int64_t min_transit = binary_evaluator(node, next);
2756 const int64_t max_transit =
2757 CapAdd(min_transit, dimension->SlackVar(node)->Max());
2758 return {min_transit, max_transit};
2763 std::vector<Interval> node_capacity(num_cumuls);
2764 for (
int node = 0; node < num_cumuls; ++node) {
2765 const IntVar* cumul = dimension->CumulVar(node);
2766 node_capacity[node] = {cumul->
Min(), cumul->
Max()};
2769 auto checker = std::make_unique<DimensionChecker>(
2770 path_state, std::move(path_capacity), std::move(path_class),
2771 std::move(transits), std::move(node_capacity));
2774 dimension->model()->solver(), std::move(checker), dimension->name());
2775 filters->push_back({filter, kAccept});
2780 const std::vector<RoutingDimension*>& dimensions,
2782 bool use_chain_cumul_filter,
2783 std::vector<LocalSearchFilterManager::FilterEvent>* filters) {
2792 const int num_dimensions = dimensions.size();
2794 const bool has_dimension_optimizers =
2796 std::vector<bool> use_path_cumul_filter(num_dimensions);
2797 std::vector<bool> use_cumul_bounds_propagator_filter(num_dimensions);
2798 std::vector<bool> use_global_lp_filter(num_dimensions);
2799 std::vector<bool> use_resource_assignment_filter(num_dimensions);
2800 for (
int d = 0; d < num_dimensions; d++) {
2802 const bool has_cumul_cost = DimensionHasCumulCost(dimension);
2803 use_path_cumul_filter[d] =
2804 has_cumul_cost || DimensionHasPathCumulConstraint(dimension);
2806 const int num_dimension_resource_groups =
2808 const bool can_use_cumul_bounds_propagator_filter =
2810 num_dimension_resource_groups == 0 &&
2811 (!filter_objective_cost || !has_cumul_cost);
2813 use_global_lp_filter[d] =
2814 has_dimension_optimizers &&
2815 ((has_precedences && !can_use_cumul_bounds_propagator_filter) ||
2816 (filter_objective_cost &&
2818 num_dimension_resource_groups > 1);
2820 use_cumul_bounds_propagator_filter[d] =
2821 has_precedences && !use_global_lp_filter[d];
2823 use_resource_assignment_filter[d] =
2824 has_dimension_optimizers && num_dimension_resource_groups > 0;
2827 for (
int d = 0; d < num_dimensions; d++) {
2834 const bool use_global_lp = use_global_lp_filter[d];
2835 const bool filter_resource_assignment = use_resource_assignment_filter[d];
2836 if (use_path_cumul_filter[d]) {
2837 PathCumulFilter* filter = model.
solver()->
RevAlloc(
new PathCumulFilter(
2838 model, dimension, !use_global_lp &&
2839 !filter_resource_assignment,
2840 filter_objective_cost, has_dimension_optimizers));
2841 const int priority = filter->UsesDimensionOptimizers() ? 1 : 0;
2842 filters->push_back({filter, kAccept, priority});
2843 }
else if (use_chain_cumul_filter) {
2845 {model.
solver()->
RevAlloc(
new ChainCumulFilter(model, dimension)),
2849 if (use_cumul_bounds_propagator_filter[d]) {
2850 DCHECK(!use_global_lp);
2851 DCHECK(!filter_resource_assignment);
2856 if (filter_resource_assignment) {
2861 filter_objective_cost),
2865 if (use_global_lp) {
2869 filter_objective_cost),
2878class PickupDeliveryFilter :
public LocalSearchFilter {
2880 PickupDeliveryFilter(
const PathState* path_state,
2881 absl::Span<const PickupDeliveryPair> pairs,
2882 const std::vector<RoutingModel::PickupAndDeliveryPolicy>&
2884 ~PickupDeliveryFilter()
override =
default;
2885 bool Accept(
const Assignment* ,
const Assignment* ,
2886 int64_t , int64_t )
override;
2888 void Reset()
override;
2889 void Synchronize(
const Assignment* ,
2890 const Assignment* )
override;
2891 std::string DebugString()
const override {
return "PickupDeliveryFilter"; }
2894 template <
bool check_as
signed_pairs>
2895 bool AcceptPathDispatch();
2896 template <
bool check_as
signed_pairs>
2897 bool AcceptPathDefault(
int path);
2898 template <
bool lifo,
bool check_as
signed_pairs>
2899 bool AcceptPathOrdered(
int path);
2900 void AssignAllVisitedPairsAndLoopNodes();
2902 const PathState*
const path_state_;
2908 int pair_index : 30;
2909 PairInfo() : is_paired(
false), pair_index(-1) {}
2910 PairInfo(
bool is_paired,
bool is_pickup,
int pair_index)
2911 : is_paired(is_paired), is_pickup(is_pickup), pair_index(pair_index) {}
2913 std::vector<PairInfo> pair_info_of_node_;
2919 PairStatus() : pickup(
false), delivery(
false) {}
2921 CommittableArray<PairStatus> assigned_status_of_pair_;
2922 SparseBitset<int> pair_is_open_;
2923 CommittableValue<int> num_assigned_pairs_;
2924 std::deque<int> visited_deque_;
2925 const std::vector<RoutingModel::PickupAndDeliveryPolicy> vehicle_policies_;
2928PickupDeliveryFilter::PickupDeliveryFilter(
2929 const PathState* path_state, absl::Span<const PickupDeliveryPair> pairs,
2930 const std::vector<RoutingModel::PickupAndDeliveryPolicy>& vehicle_policies)
2931 : path_state_(path_state),
2932 pair_info_of_node_(path_state->NumNodes()),
2933 assigned_status_of_pair_(pairs.size(), {}),
2934 pair_is_open_(pairs.size()),
2935 num_assigned_pairs_(0),
2936 vehicle_policies_(vehicle_policies) {
2937 for (
int pair_index = 0; pair_index < pairs.size(); ++pair_index) {
2938 const auto& [pickups, deliveries] = pairs[pair_index];
2939 for (
const int pickup : pickups) {
2940 pair_info_of_node_[pickup] =
2943 PairInfo{
true,
true,
2946 for (
const int delivery : deliveries) {
2947 pair_info_of_node_[delivery] =
2950 PairInfo{
true,
false,
2956void PickupDeliveryFilter::Reset() {
2957 assigned_status_of_pair_.SetAllAndCommit({});
2958 num_assigned_pairs_.SetAndCommit(0);
2961void PickupDeliveryFilter::AssignAllVisitedPairsAndLoopNodes() {
2962 assigned_status_of_pair_.Revert();
2963 num_assigned_pairs_.Revert();
2964 int num_assigned_pairs = num_assigned_pairs_.Get();
2965 if (num_assigned_pairs == assigned_status_of_pair_.Size())
return;
2968 auto assign_node = [
this](
int node) ->
bool {
2969 const auto [is_paired, is_pickup, pair_index] = pair_info_of_node_[node];
2970 if (!is_paired)
return false;
2971 bool assigned_pair =
false;
2972 PairStatus assigned_status = assigned_status_of_pair_.Get(pair_index);
2973 if (is_pickup && !assigned_status.pickup) {
2974 assigned_pair = assigned_status.delivery;
2975 assigned_status.pickup =
true;
2976 assigned_status_of_pair_.Set(pair_index, assigned_status);
2978 if (!is_pickup && !assigned_status.delivery) {
2979 assigned_pair = assigned_status.pickup;
2980 assigned_status.delivery =
true;
2981 assigned_status_of_pair_.Set(pair_index, assigned_status);
2983 return assigned_pair;
2985 for (
const int path : path_state_->ChangedPaths()) {
2986 for (
const int node : path_state_->Nodes(path)) {
2987 num_assigned_pairs += assign_node(node) ? 1 : 0;
2990 for (
const int loop : path_state_->ChangedLoops()) {
2991 num_assigned_pairs += assign_node(loop) ? 1 : 0;
2993 num_assigned_pairs_.Set(num_assigned_pairs);
2996void PickupDeliveryFilter::Synchronize(
const Assignment* ,
2997 const Assignment* ) {
2998 AssignAllVisitedPairsAndLoopNodes();
2999 assigned_status_of_pair_.Commit();
3000 num_assigned_pairs_.Commit();
3003bool PickupDeliveryFilter::Accept(
const Assignment* ,
3007 if (path_state_->IsInvalid())
return true;
3008 AssignAllVisitedPairsAndLoopNodes();
3009 const bool check_assigned_pairs =
3010 num_assigned_pairs_.Get() < assigned_status_of_pair_.Size();
3011 if (check_assigned_pairs) {
3012 return AcceptPathDispatch<true>();
3014 return AcceptPathDispatch<false>();
3018template <
bool check_as
signed_pairs>
3019bool PickupDeliveryFilter::AcceptPathDispatch() {
3020 for (
const int path : path_state_->ChangedPaths()) {
3021 switch (vehicle_policies_[path]) {
3022 case RoutingModel::PICKUP_AND_DELIVERY_NO_ORDER:
3023 if (!AcceptPathDefault<check_assigned_pairs>(path))
return false;
3025 case RoutingModel::PICKUP_AND_DELIVERY_LIFO:
3026 if (!AcceptPathOrdered<true, check_assigned_pairs>(path))
return false;
3028 case RoutingModel::PICKUP_AND_DELIVERY_FIFO:
3029 if (!AcceptPathOrdered<false, check_assigned_pairs>(path))
return false;
3038template <
bool check_as
signed_pairs>
3039bool PickupDeliveryFilter::AcceptPathDefault(
int path) {
3040 pair_is_open_.ResetAllToFalse();
3041 int num_opened_pairs = 0;
3042 for (
const int node : path_state_->Nodes(path)) {
3043 const auto [is_paired, is_pickup, pair_index] = pair_info_of_node_[node];
3044 if (!is_paired)
continue;
3045 if constexpr (check_assigned_pairs) {
3046 const PairStatus status = assigned_status_of_pair_.Get(pair_index);
3047 if (!status.pickup || !status.delivery)
continue;
3050 pair_is_open_.Set(pair_index);
3053 if (!pair_is_open_[pair_index])
return false;
3054 pair_is_open_.Clear(pair_index);
3060 if (num_opened_pairs > 0)
return false;
3061 pair_is_open_.NotifyAllClear();
3065template <
bool lifo,
bool check_as
signed_pairs>
3066bool PickupDeliveryFilter::AcceptPathOrdered(
int path) {
3067 visited_deque_.clear();
3068 for (
const int node : path_state_->Nodes(path)) {
3069 const auto [is_paired, is_pickup, pair_index] = pair_info_of_node_[node];
3070 if (!is_paired)
continue;
3071 if constexpr (check_assigned_pairs) {
3072 const PairStatus status = assigned_status_of_pair_.Get(pair_index);
3073 if (!status.pickup || !status.delivery)
continue;
3076 visited_deque_.emplace_back(pair_index);
3078 if (visited_deque_.empty())
return false;
3079 if constexpr (lifo) {
3080 const int last_pair_index = visited_deque_.back();
3081 if (last_pair_index != pair_index)
return false;
3082 visited_deque_.pop_back();
3084 const int first_pair_index = visited_deque_.front();
3085 if (first_pair_index != pair_index)
return false;
3086 visited_deque_.pop_front();
3090 return visited_deque_.empty();
3097 absl::Span<const PickupDeliveryPair> pairs,
3098 const std::vector<RoutingModel::PickupAndDeliveryPolicy>&
3101 new PickupDeliveryFilter(path_state, pairs, vehicle_policies));
3107class VehicleVarFilter :
public LocalSearchFilter {
3109 VehicleVarFilter(
const RoutingModel& routing_model,
3110 const PathState* path_state);
3111 bool Accept(
const Assignment* delta,
const Assignment* deltadelta,
3112 int64_t objective_min, int64_t objective_max)
override;
3113 void Synchronize(
const Assignment* ,
3114 const Assignment* )
override;
3115 std::string DebugString()
const override {
return "VehicleVariableFilter"; }
3118 bool HasConstrainedVehicleVars()
const;
3120 const PathState* path_state_;
3121 std::vector<IntVar*> vehicle_vars_;
3122 const int num_vehicles_;
3126VehicleVarFilter::VehicleVarFilter(
const RoutingModel& routing_model,
3127 const PathState* path_state)
3128 : path_state_(path_state),
3129 vehicle_vars_(routing_model.VehicleVars()),
3130 num_vehicles_(routing_model.vehicles()),
3131 is_disabled_(!HasConstrainedVehicleVars()) {}
3133bool VehicleVarFilter::HasConstrainedVehicleVars()
const {
3134 for (
const IntVar* var : vehicle_vars_) {
3135 const int unconstrained_size = num_vehicles_ + ((var->Min() >= 0) ? 0 : 1);
3136 if (var->Size() != unconstrained_size)
return true;
3141void VehicleVarFilter::Synchronize(
const Assignment* ,
3142 const Assignment* ) {
3143 is_disabled_ = !HasConstrainedVehicleVars();
3146bool VehicleVarFilter::Accept(
const Assignment* ,
3150 if (is_disabled_)
return true;
3151 for (
const int path : path_state_->ChangedPaths()) {
3153 for (
const PathState::Chain chain :
3154 path_state_->Chains(path).DropFirstChain().DropLastChain()) {
3155 for (
const int node : chain) {
3156 if (!vehicle_vars_[node]->Contains(path))
return false;
3168 new VehicleVarFilter(routing_model, path_state));
3173class CumulBoundsPropagatorFilter :
public IntVarLocalSearchFilter {
3175 explicit CumulBoundsPropagatorFilter(
const RoutingDimension& dimension);
3176 bool Accept(
const Assignment* delta,
const Assignment* deltadelta,
3177 int64_t objective_min, int64_t objective_max)
override;
3178 std::string DebugString()
const override {
3179 return "CumulBoundsPropagatorFilter(" + propagator_.dimension().name() +
3184 CumulBoundsPropagator propagator_;
3185 const int64_t cumul_offset_;
3186 SparseBitset<int64_t> delta_touched_;
3187 std::vector<int64_t> delta_nexts_;
3190CumulBoundsPropagatorFilter::CumulBoundsPropagatorFilter(
3191 const RoutingDimension& dimension)
3192 : IntVarLocalSearchFilter(dimension.model()->Nexts()),
3193 propagator_(&dimension),
3194 cumul_offset_(dimension.GetGlobalOptimizerOffset()),
3195 delta_touched_(Size()),
3196 delta_nexts_(Size()) {}
3198bool CumulBoundsPropagatorFilter::Accept(
const Assignment* delta,
3202 delta_touched_.ResetAllToFalse();
3203 for (
const IntVarElement& delta_element :
3204 delta->IntVarContainer().elements()) {
3206 if (FindIndex(delta_element.Var(), &index)) {
3207 if (!delta_element.Bound()) {
3211 delta_touched_.Set(index);
3212 delta_nexts_[index] = delta_element.Value();
3215 const auto& next_accessor = [
this](int64_t index) -> int64_t {
3216 return delta_touched_[index] ? delta_nexts_[index]
3217 : !IsVarSynced(index) ? -1
3221 return propagator_.PropagateCumulBounds(next_accessor, cumul_offset_);
3229 new CumulBoundsPropagatorFilter(dimension));
3234class LPCumulFilter :
public IntVarLocalSearchFilter {
3236 LPCumulFilter(
const std::vector<IntVar*>& nexts,
3237 GlobalDimensionCumulOptimizer* optimizer,
3238 GlobalDimensionCumulOptimizer* mp_optimizer,
3239 bool filter_objective_cost);
3240 bool Accept(
const Assignment* delta,
const Assignment* deltadelta,
3241 int64_t objective_min, int64_t objective_max)
override;
3242 int64_t GetAcceptedObjectiveValue()
const override;
3243 void OnSynchronize(
const Assignment* delta)
override;
3244 int64_t GetSynchronizedObjectiveValue()
const override;
3245 std::string DebugString()
const override {
3246 return "LPCumulFilter(" + lp_optimizer_.dimension()->name() +
")";
3250 GlobalDimensionCumulOptimizer& lp_optimizer_;
3251 GlobalDimensionCumulOptimizer& mp_optimizer_;
3252 const bool filter_objective_cost_;
3253 int64_t synchronized_cost_without_transit_;
3254 int64_t delta_cost_without_transit_;
3255 SparseBitset<int64_t> delta_touched_;
3256 std::vector<int64_t> delta_nexts_;
3259LPCumulFilter::LPCumulFilter(
const std::vector<IntVar*>& nexts,
3260 GlobalDimensionCumulOptimizer* lp_optimizer,
3261 GlobalDimensionCumulOptimizer* mp_optimizer,
3262 bool filter_objective_cost)
3263 : IntVarLocalSearchFilter(nexts),
3264 lp_optimizer_(*lp_optimizer),
3265 mp_optimizer_(*mp_optimizer),
3266 filter_objective_cost_(filter_objective_cost),
3267 synchronized_cost_without_transit_(-1),
3268 delta_cost_without_transit_(-1),
3269 delta_touched_(Size()),
3270 delta_nexts_(Size()) {}
3272bool LPCumulFilter::Accept(
const Assignment* delta,
3274 int64_t , int64_t objective_max) {
3275 delta_touched_.ResetAllToFalse();
3276 for (
const IntVarElement& delta_element :
3277 delta->IntVarContainer().elements()) {
3279 if (FindIndex(delta_element.Var(), &index)) {
3280 if (!delta_element.Bound()) {
3284 delta_touched_.Set(index);
3285 delta_nexts_[index] = delta_element.Value();
3288 const auto& next_accessor = [
this](int64_t index) {
3289 return delta_touched_[index] ? delta_nexts_[index]
3290 : !IsVarSynced(index) ? -1
3294 if (!filter_objective_cost_) {
3296 delta_cost_without_transit_ = 0;
3298 next_accessor, {},
nullptr,
nullptr,
nullptr);
3299 if (status == DimensionSchedulingStatus::RELAXED_OPTIMAL_ONLY) {
3300 status = mp_optimizer_.ComputeCumuls(next_accessor, {},
nullptr,
nullptr,
3303 DCHECK(status != DimensionSchedulingStatus::FEASIBLE)
3304 <<
"FEASIBLE without filtering objective cost should be OPTIMAL";
3305 return status == DimensionSchedulingStatus::OPTIMAL;
3309 lp_optimizer_.ComputeCumulCostWithoutFixedTransits(
3310 next_accessor, &delta_cost_without_transit_);
3312 if (status == DimensionSchedulingStatus::RELAXED_OPTIMAL_ONLY ||
3313 status == DimensionSchedulingStatus::FEASIBLE) {
3316 status = mp_optimizer_.ComputeCumulCostWithoutFixedTransits(next_accessor,
3318 if (lp_status == DimensionSchedulingStatus::RELAXED_OPTIMAL_ONLY &&
3319 status == DimensionSchedulingStatus::OPTIMAL) {
3323 delta_cost_without_transit_ = mp_cost;
3324 }
else if (lp_status == DimensionSchedulingStatus::FEASIBLE &&
3325 status != DimensionSchedulingStatus::INFEASIBLE) {
3328 delta_cost_without_transit_ =
3329 std::min(delta_cost_without_transit_, mp_cost);
3333 if (status == DimensionSchedulingStatus::INFEASIBLE) {
3334 delta_cost_without_transit_ = std::numeric_limits<int64_t>::max();
3337 return delta_cost_without_transit_ <= objective_max;
3340int64_t LPCumulFilter::GetAcceptedObjectiveValue()
const {
3341 return delta_cost_without_transit_;
3344void LPCumulFilter::OnSynchronize(
const Assignment* ) {
3347 const RoutingModel& model = *lp_optimizer_.dimension()->model();
3348 const auto& next_accessor = [
this, &model](int64_t index) {
3349 return IsVarSynced(index) ?
Value(index)
3350 : model.IsStart(index) ? model.End(model.VehicleIndex(index))
3354 if (!filter_objective_cost_) {
3355 synchronized_cost_without_transit_ = 0;
3358 filter_objective_cost_
3359 ? lp_optimizer_.ComputeCumulCostWithoutFixedTransits(
3360 next_accessor, &synchronized_cost_without_transit_)
3361 : lp_optimizer_.ComputeCumuls(next_accessor, {},
nullptr,
nullptr,
3363 if (status == DimensionSchedulingStatus::RELAXED_OPTIMAL_ONLY) {
3364 status = filter_objective_cost_
3365 ? mp_optimizer_.ComputeCumulCostWithoutFixedTransits(
3366 next_accessor, &synchronized_cost_without_transit_)
3367 : mp_optimizer_.ComputeCumuls(next_accessor, {},
nullptr,
3370 if (status == DimensionSchedulingStatus::INFEASIBLE) {
3373 synchronized_cost_without_transit_ = 0;
3377int64_t LPCumulFilter::GetSynchronizedObjectiveValue()
const {
3378 return synchronized_cost_without_transit_;
3386 DCHECK_NE(lp_optimizer,
nullptr);
3387 DCHECK_NE(mp_optimizer,
nullptr);
3390 model.
Nexts(), lp_optimizer, mp_optimizer, filter_objective_cost));
3395using ResourceGroup = RoutingModel::ResourceGroup;
3397class ResourceGroupAssignmentFilter :
public BasePathFilter {
3399 ResourceGroupAssignmentFilter(
const std::vector<IntVar*>& nexts,
3400 const ResourceGroup* resource_group,
3401 LocalDimensionCumulOptimizer* lp_optimizer,
3402 LocalDimensionCumulOptimizer* mp_optimizer,
3403 bool filter_objective_cost);
3404 bool InitializeAcceptPath()
override;
3405 bool AcceptPath(int64_t path_start, int64_t, int64_t)
override;
3406 bool FinalizeAcceptPath(int64_t objective_min,
3407 int64_t objective_max)
override;
3408 void OnBeforeSynchronizePaths(
bool synchronizing_all_paths)
override;
3409 void OnSynchronizePathFromStart(int64_t start)
override;
3410 void OnAfterSynchronizePaths()
override;
3412 int64_t GetAcceptedObjectiveValue()
const override {
3413 return lns_detected() ? 0 : delta_cost_without_transit_;
3415 int64_t GetSynchronizedObjectiveValue()
const override {
3416 return synchronized_cost_without_transit_;
3418 std::string DebugString()
const override {
3419 return "ResourceGroupAssignmentFilter(" + dimension_.name() +
")";
3423 using RCIndex = RoutingModel::ResourceClassIndex;
3425 bool VehicleRequiresResourceAssignment(
3426 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
3427 bool* is_infeasible);
3428 int64_t ComputeRouteCumulCostWithoutResourceAssignment(
3429 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor)
const;
3431 const RoutingModel& model_;
3432 const RoutingDimension& dimension_;
3433 const ResourceGroup& resource_group_;
3434 LocalDimensionCumulOptimizer* lp_optimizer_;
3435 LocalDimensionCumulOptimizer* mp_optimizer_;
3436 const bool filter_objective_cost_;
3437 bool current_synch_failed_;
3438 int64_t synchronized_cost_without_transit_;
3439 int64_t delta_cost_without_transit_;
3440 std::vector<std::vector<int64_t>> vehicle_to_resource_class_assignment_costs_;
3441 std::vector<int> vehicles_requiring_resource_assignment_;
3442 std::vector<bool> vehicle_requires_resource_assignment_;
3443 std::vector<std::vector<int64_t>>
3444 delta_vehicle_to_resource_class_assignment_costs_;
3445 std::vector<int> delta_vehicles_requiring_resource_assignment_;
3446 std::vector<bool> delta_vehicle_requires_resource_assignment_;
3448 std::vector<int> bound_resource_index_of_vehicle_;
3449 util_intops::StrongVector<RCIndex, absl::flat_hash_set<int>>
3450 ignored_resources_per_class_;
3453ResourceGroupAssignmentFilter::ResourceGroupAssignmentFilter(
3454 const std::vector<IntVar*>& nexts,
const ResourceGroup* resource_group,
3455 LocalDimensionCumulOptimizer* lp_optimizer,
3456 LocalDimensionCumulOptimizer* mp_optimizer,
bool filter_objective_cost)
3457 : BasePathFilter(nexts, lp_optimizer->dimension()->cumuls().size(),
3458 lp_optimizer->dimension()->model()->GetPathsMetadata()),
3459 model_(*lp_optimizer->dimension()->model()),
3460 dimension_(*lp_optimizer->dimension()),
3461 resource_group_(*resource_group),
3462 lp_optimizer_(lp_optimizer),
3463 mp_optimizer_(mp_optimizer),
3464 filter_objective_cost_(filter_objective_cost),
3465 current_synch_failed_(
false),
3466 synchronized_cost_without_transit_(-1),
3467 delta_cost_without_transit_(-1) {
3468 vehicle_to_resource_class_assignment_costs_.resize(model_.vehicles());
3469 delta_vehicle_to_resource_class_assignment_costs_.resize(model_.vehicles());
3472bool ResourceGroupAssignmentFilter::InitializeAcceptPath() {
3473 delta_vehicle_to_resource_class_assignment_costs_.assign(model_.vehicles(),
3475 if (current_synch_failed_) {
3480 int num_used_vehicles = 0;
3481 const int num_resources = resource_group_.Size();
3482 for (
int v : resource_group_.GetVehiclesRequiringAResource()) {
3483 if (GetNext(model_.Start(v)) != model_.End(v) ||
3484 model_.IsVehicleUsedWhenEmpty(v)) {
3485 if (++num_used_vehicles > num_resources) {
3490 delta_vehicle_requires_resource_assignment_ =
3491 vehicle_requires_resource_assignment_;
3495bool ResourceGroupAssignmentFilter::AcceptPath(int64_t path_start, int64_t,
3497 if (current_synch_failed_) {
3500 const int vehicle = model_.VehicleIndex(path_start);
3501 bool is_infeasible =
false;
3502 delta_vehicle_requires_resource_assignment_[vehicle] =
3503 VehicleRequiresResourceAssignment(
3504 vehicle, [
this](int64_t n) {
return GetNext(n); }, &is_infeasible);
3505 return !is_infeasible;
3508bool ResourceGroupAssignmentFilter::FinalizeAcceptPath(
3509 int64_t , int64_t objective_max) {
3510 delta_cost_without_transit_ = 0;
3511 if (current_synch_failed_) {
3514 const auto& next_accessor = [
this](int64_t index) {
return GetNext(index); };
3515 delta_vehicles_requiring_resource_assignment_.clear();
3518 for (
int v = 0; v < model_.vehicles(); ++v) {
3519 if (delta_vehicle_requires_resource_assignment_[v]) {
3520 delta_vehicles_requiring_resource_assignment_.push_back(v);
3523 int64_t route_cost = 0;
3524 int64_t start = model_.Start(v);
3525 if (PathStartTouched(start)) {
3527 ComputeRouteCumulCostWithoutResourceAssignment(v, next_accessor);
3528 if (route_cost < 0) {
3531 }
else if (IsVarSynced(start)) {
3532 DCHECK_EQ(vehicle_to_resource_class_assignment_costs_[v].size(), 1);
3533 route_cost = vehicle_to_resource_class_assignment_costs_[v][0];
3535 CapAddTo(route_cost, &delta_cost_without_transit_);
3536 if (delta_cost_without_transit_ > objective_max) {
3542 for (int64_t start : GetTouchedPathStarts()) {
3543 const int vehicle = model_.VehicleIndex(start);
3544 if (!delta_vehicle_requires_resource_assignment_[vehicle]) {
3549 vehicle, 1.0, resource_group_,
3550 ignored_resources_per_class_, next_accessor,
3551 dimension_.transit_evaluator(vehicle), filter_objective_cost_,
3552 lp_optimizer_, mp_optimizer_,
3553 &delta_vehicle_to_resource_class_assignment_costs_[vehicle],
3554 nullptr,
nullptr)) {
3559 delta_vehicles_requiring_resource_assignment_,
3560 resource_group_.GetResourceIndicesPerClass(),
3561 ignored_resources_per_class_,
3564 return PathStartTouched(model_.Start(v))
3565 ? &delta_vehicle_to_resource_class_assignment_costs_[v]
3566 : &vehicle_to_resource_class_assignment_costs_[v];
3569 CapAddTo(assignment_cost, &delta_cost_without_transit_);
3570 return assignment_cost >= 0 && delta_cost_without_transit_ <= objective_max;
3573void ResourceGroupAssignmentFilter::OnBeforeSynchronizePaths(
bool) {
3574 if (!HasAnySyncedPath()) {
3575 vehicle_to_resource_class_assignment_costs_.assign(model_.vehicles(), {});
3577 bound_resource_index_of_vehicle_.assign(model_.vehicles(), -1);
3578 vehicles_requiring_resource_assignment_.clear();
3579 vehicles_requiring_resource_assignment_.reserve(
3580 resource_group_.GetVehiclesRequiringAResource().size());
3581 vehicle_requires_resource_assignment_.assign(model_.vehicles(),
false);
3582 ignored_resources_per_class_.assign(resource_group_.GetResourceClassesCount(),
3583 absl::flat_hash_set<int>());
3585 for (
int v : resource_group_.GetVehiclesRequiringAResource()) {
3586 const int64_t start = model_.Start(v);
3587 if (!IsVarSynced(start)) {
3590 vehicle_requires_resource_assignment_[v] =
3591 VehicleRequiresResourceAssignment(
3592 v, [
this](int64_t n) {
return Value(n); }, ¤t_synch_failed_);
3593 if (vehicle_requires_resource_assignment_[v]) {
3594 vehicles_requiring_resource_assignment_.push_back(v);
3596 if (current_synch_failed_) {
3600 synchronized_cost_without_transit_ = 0;
3603void ResourceGroupAssignmentFilter::OnSynchronizePathFromStart(int64_t start) {
3604 if (current_synch_failed_)
return;
3605 DCHECK(IsVarSynced(start));
3606 const int v = model_.VehicleIndex(start);
3607 const auto& next_accessor = [
this](int64_t index) {
3608 return IsVarSynced(index) ?
Value(index) : -1;
3610 if (!vehicle_requires_resource_assignment_[v]) {
3611 const int64_t route_cost =
3612 ComputeRouteCumulCostWithoutResourceAssignment(v, next_accessor);
3613 if (route_cost < 0) {
3614 current_synch_failed_ =
true;
3617 CapAddTo(route_cost, &synchronized_cost_without_transit_);
3618 vehicle_to_resource_class_assignment_costs_[v] = {route_cost};
3628 v, 1.0, resource_group_,
3629 ignored_resources_per_class_, next_accessor,
3630 dimension_.transit_evaluator(v), filter_objective_cost_,
3631 lp_optimizer_, mp_optimizer_,
3632 &vehicle_to_resource_class_assignment_costs_[v],
nullptr,
nullptr)) {
3633 vehicle_to_resource_class_assignment_costs_[v].assign(
3634 resource_group_.GetResourceClassesCount(), -1);
3635 current_synch_failed_ =
true;
3639void ResourceGroupAssignmentFilter::OnAfterSynchronizePaths() {
3640 if (current_synch_failed_) {
3641 synchronized_cost_without_transit_ = 0;
3644 if (!filter_objective_cost_) {
3645 DCHECK_EQ(synchronized_cost_without_transit_, 0);
3649 vehicles_requiring_resource_assignment_,
3650 resource_group_.GetResourceIndicesPerClass(),
3651 ignored_resources_per_class_,
3652 [
this](
int v) { return &vehicle_to_resource_class_assignment_costs_[v]; },
3654 if (assignment_cost < 0) {
3655 synchronized_cost_without_transit_ = 0;
3656 current_synch_failed_ =
true;
3658 DCHECK_GE(synchronized_cost_without_transit_, 0);
3659 CapAddTo(assignment_cost, &synchronized_cost_without_transit_);
3663bool ResourceGroupAssignmentFilter::VehicleRequiresResourceAssignment(
3664 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
3665 bool* is_infeasible) {
3666 *is_infeasible =
false;
3667 if (!resource_group_.VehicleRequiresAResource(vehicle))
return false;
3668 const IntVar* res_var = model_.ResourceVar(vehicle, resource_group_.Index());
3669 if (!model_.IsVehicleUsedWhenEmpty(vehicle) &&
3670 next_accessor(model_.Start(vehicle)) == model_.End(vehicle)) {
3671 if (res_var->Bound() && res_var->Value() >= 0) {
3673 *is_infeasible =
true;
3678 if (res_var->Bound()) {
3680 const int res = res_var->Value();
3683 *is_infeasible =
true;
3685 bound_resource_index_of_vehicle_[vehicle] = res;
3686 const RCIndex rc = resource_group_.GetResourceClassIndex(res);
3687 ignored_resources_per_class_[rc].insert(res);
3696ResourceGroupAssignmentFilter::ComputeRouteCumulCostWithoutResourceAssignment(
3697 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor)
const {
3698 if (next_accessor(model_.Start(vehicle)) == model_.End(vehicle) &&
3699 !model_.IsVehicleUsedWhenEmpty(vehicle)) {
3702 using Resource = RoutingModel::ResourceGroup::Resource;
3703 const Resource* resource =
nullptr;
3704 if (resource_group_.VehicleRequiresAResource(vehicle)) {
3705 DCHECK_GE(bound_resource_index_of_vehicle_[vehicle], 0);
3707 &resource_group_.GetResource(bound_resource_index_of_vehicle_[vehicle]);
3709 int64_t route_cost = 0;
3711 lp_optimizer_->ComputeRouteCumulCostWithoutFixedTransits(
3712 vehicle, 1.0, next_accessor, resource,
3713 filter_objective_cost_ ? &route_cost :
nullptr);
3715 case DimensionSchedulingStatus::INFEASIBLE:
3717 case DimensionSchedulingStatus::RELAXED_OPTIMAL_ONLY:
3718 if (mp_optimizer_->ComputeRouteCumulCostWithoutFixedTransits(
3719 vehicle, 1.0, next_accessor, resource,
3720 filter_objective_cost_ ? &route_cost :
nullptr) ==
3721 DimensionSchedulingStatus::INFEASIBLE) {
3726 DCHECK(status == DimensionSchedulingStatus::OPTIMAL ||
3727 status == DimensionSchedulingStatus::FEASIBLE);
3733class ResourceAssignmentFilter :
public LocalSearchFilter {
3735 ResourceAssignmentFilter(
const std::vector<IntVar*>& nexts,
3736 LocalDimensionCumulOptimizer* optimizer,
3737 LocalDimensionCumulOptimizer* mp_optimizer,
3738 bool propagate_own_objective_value,
3739 bool filter_objective_cost);
3740 bool Accept(
const Assignment* delta,
const Assignment* deltadelta,
3741 int64_t objective_min, int64_t objective_max)
override;
3742 void Synchronize(
const Assignment* assignment,
3743 const Assignment* delta)
override;
3745 int64_t GetAcceptedObjectiveValue()
const override {
3746 return propagate_own_objective_value_ ? delta_cost_ : 0;
3748 int64_t GetSynchronizedObjectiveValue()
const override {
3749 return propagate_own_objective_value_ ? synchronized_cost_ : 0;
3751 std::string DebugString()
const override {
3752 return "ResourceAssignmentFilter(" + dimension_name_ +
")";
3756 std::vector<IntVarLocalSearchFilter*> resource_group_assignment_filters_;
3757 int64_t synchronized_cost_;
3758 int64_t delta_cost_;
3759 const bool propagate_own_objective_value_;
3760 const std::string dimension_name_;
3763ResourceAssignmentFilter::ResourceAssignmentFilter(
3764 const std::vector<IntVar*>& nexts,
3765 LocalDimensionCumulOptimizer* lp_optimizer,
3766 LocalDimensionCumulOptimizer* mp_optimizer,
3767 bool propagate_own_objective_value,
bool filter_objective_cost)
3768 : propagate_own_objective_value_(propagate_own_objective_value),
3769 dimension_name_(lp_optimizer->dimension()->name()) {
3770 const RoutingModel& model = *lp_optimizer->dimension()->model();
3771 for (
const auto& resource_group : model.GetResourceGroups()) {
3772 resource_group_assignment_filters_.push_back(
3773 model.solver()->RevAlloc(new ResourceGroupAssignmentFilter(
3774 nexts, resource_group.get(), lp_optimizer, mp_optimizer,
3775 filter_objective_cost)));
3779bool ResourceAssignmentFilter::Accept(
const Assignment* delta,
3780 const Assignment* deltadelta,
3781 int64_t objective_min,
3782 int64_t objective_max) {
3784 for (LocalSearchFilter* group_filter : resource_group_assignment_filters_) {
3785 if (!group_filter->Accept(delta, deltadelta, objective_min,
3790 std::max(delta_cost_, group_filter->GetAcceptedObjectiveValue());
3791 DCHECK_LE(delta_cost_, objective_max)
3792 <<
"ResourceGroupAssignmentFilter should return false when the "
3793 "objective_max is exceeded.";
3798void ResourceAssignmentFilter::Synchronize(
const Assignment* assignment,
3799 const Assignment* delta) {
3800 synchronized_cost_ = 0;
3801 for (LocalSearchFilter* group_filter : resource_group_assignment_filters_) {
3802 group_filter->Synchronize(assignment, delta);
3803 synchronized_cost_ = std::max(
3804 synchronized_cost_, group_filter->GetSynchronizedObjectiveValue());
3813 bool propagate_own_objective_value,
bool filter_objective_cost) {
3815 DCHECK_NE(lp_optimizer,
nullptr);
3816 DCHECK_NE(mp_optimizer,
nullptr);
3818 model.
Nexts(), lp_optimizer, mp_optimizer, propagate_own_objective_value,
3819 filter_objective_cost));
3836class CPFeasibilityFilter :
public IntVarLocalSearchFilter {
3838 explicit CPFeasibilityFilter(RoutingModel* routing_model);
3839 ~CPFeasibilityFilter()
override =
default;
3840 std::string DebugString()
const override {
return "CPFeasibilityFilter"; }
3841 bool Accept(
const Assignment* delta,
const Assignment* deltadelta,
3842 int64_t objective_min, int64_t objective_max)
override;
3843 void OnSynchronize(
const Assignment* delta)
override;
3846 void AddDeltaToAssignment(
const Assignment* delta, Assignment* assignment);
3848 static const int64_t kUnassigned;
3849 const RoutingModel*
const model_;
3850 Solver*
const solver_;
3851 Assignment*
const assignment_;
3852 Assignment*
const temp_assignment_;
3853 DecisionBuilder*
const restore_;
3854 SearchLimit*
const limit_;
3857const int64_t CPFeasibilityFilter::kUnassigned = -1;
3859CPFeasibilityFilter::CPFeasibilityFilter(RoutingModel* routing_model)
3860 : IntVarLocalSearchFilter(routing_model->Nexts()),
3861 model_(routing_model),
3862 solver_(routing_model->solver()),
3863 assignment_(solver_->MakeAssignment()),
3864 temp_assignment_(solver_->MakeAssignment()),
3865 restore_(solver_->MakeRestoreAssignment(temp_assignment_)),
3866 limit_(solver_->MakeCustomLimit(
3867 [routing_model]() {
return routing_model->CheckLimit(); })) {
3868 assignment_->Add(routing_model->Nexts());
3871bool CPFeasibilityFilter::Accept(
const Assignment* delta,
3875 temp_assignment_->Copy(assignment_);
3876 AddDeltaToAssignment(delta, temp_assignment_);
3878 return solver_->Solve(restore_, limit_);
3881void CPFeasibilityFilter::OnSynchronize(
const Assignment* delta) {
3882 AddDeltaToAssignment(delta, assignment_);
3885void CPFeasibilityFilter::AddDeltaToAssignment(
const Assignment* delta,
3886 Assignment* assignment) {
3887 if (delta ==
nullptr) {
3890 Assignment::IntContainer*
const container =
3891 assignment->MutableIntVarContainer();
3892 for (
const IntVarElement& delta_element :
3893 delta->IntVarContainer().elements()) {
3894 IntVar*
const var = delta_element.Var();
3895 int64_t index = kUnassigned;
3898 if (!FindIndex(var, &index))
continue;
3899 DCHECK_EQ(var, Var(index));
3900 const int64_t value = delta_element.Value();
3902 container->AddAtPosition(var, index)->SetValue(value);
3903 if (model_->IsStart(index)) {
3904 if (model_->IsEnd(value)) {
3906 container->MutableElement(index)->Deactivate();
3909 container->MutableElement(index)->Activate();
3919 new CPFeasibilityFilter(routing_model));
3923 std::vector<int> path_end)
3924 : num_nodes_(num_nodes),
3925 num_paths_(path_start.size()),
3926 num_nodes_threshold_(
std::max(16, 4 * num_nodes_))
3928 DCHECK_EQ(path_start.size(), num_paths_);
3929 DCHECK_EQ(path_end.size(), num_paths_);
3930 for (
int p = 0; p < num_paths_; ++p) {
3931 path_start_end_.push_back({path_start[p], path_end[p]});
3937 is_invalid_ =
false;
3939 committed_index_.assign(num_nodes_, -1);
3941 committed_nodes_.assign(2 * num_paths_, -1);
3942 chains_.assign(num_paths_ + 1, {-1, -1});
3943 paths_.assign(num_paths_, {-1, -1});
3944 for (
int path = 0; path < num_paths_; ++path) {
3945 const int index = 2 * path;
3946 const auto& [start,
end] = path_start_end_[path];
3947 committed_index_[start] = index;
3948 committed_index_[
end] = index + 1;
3950 committed_nodes_[index] = start;
3951 committed_nodes_[index + 1] =
end;
3953 committed_paths_[start] = path;
3954 committed_paths_[
end] = path;
3956 chains_[path] = {index, index + 2};
3957 paths_[path] = {path, path + 1};
3959 chains_[num_paths_] = {0, 0};
3962 for (
int node = 0; node < num_nodes_; ++node) {
3963 if (committed_index_[node] != -1)
continue;
3964 committed_index_[node] = committed_nodes_.size();
3965 committed_nodes_.push_back(node);
3970 const PathBounds bounds = paths_[path];
3972 chains_.data() + bounds.end_index,
3973 committed_nodes_.data());
3977 const PathBounds bounds = paths_[path];
3979 chains_.data() + bounds.end_index,
3980 committed_nodes_.data());
3984 changed_paths_.push_back(path);
3985 const int path_begin_index = chains_.size();
3986 chains_.insert(chains_.end(), chains.begin(), chains.end());
3987 const int path_end_index = chains_.size();
3988 paths_[path] = {path_begin_index, path_end_index};
3989 chains_.emplace_back(0, 0);
3993 for (
const int loop : new_loops) {
3997 changed_loops_.push_back(loop);
4003 if (committed_nodes_.size() < num_nodes_threshold_) {
4004 IncrementalCommit();
4011 is_invalid_ =
false;
4012 chains_.resize(num_paths_ + 1);
4013 for (
const int path : changed_paths_) {
4014 paths_[path] = {path, path + 1};
4016 changed_paths_.clear();
4017 changed_loops_.clear();
4020void PathState::CopyNewPathAtEndOfNodes(
int path) {
4022 const PathBounds path_bounds = paths_[path];
4023 for (
int i = path_bounds.begin_index; i < path_bounds.end_index; ++i) {
4024 const ChainBounds chain_bounds = chains_[i];
4025 committed_nodes_.insert(committed_nodes_.end(),
4026 committed_nodes_.data() + chain_bounds.begin_index,
4027 committed_nodes_.data() + chain_bounds.end_index);
4028 if (committed_paths_[committed_nodes_.back()] == path)
continue;
4029 for (
int i = chain_bounds.begin_index; i < chain_bounds.end_index; ++i) {
4030 const int node = committed_nodes_[i];
4031 committed_paths_[node] = path;
4038void PathState::IncrementalCommit() {
4039 const int new_nodes_begin = committed_nodes_.size();
4041 const int chain_begin = committed_nodes_.size();
4042 CopyNewPathAtEndOfNodes(path);
4043 const int chain_end = committed_nodes_.size();
4044 chains_[path] = {chain_begin, chain_end};
4047 const int new_nodes_end = committed_nodes_.size();
4048 for (
int i = new_nodes_begin;
i < new_nodes_end; ++
i) {
4049 const int node = committed_nodes_[
i];
4050 committed_index_[node] =
i;
4055 committed_paths_[loop] =
kLoop;
4061void PathState::FullCommit() {
4064 const int old_num_nodes = committed_nodes_.size();
4065 for (
int path = 0; path < num_paths_; ++path) {
4066 const int new_path_begin = committed_nodes_.size() - old_num_nodes;
4067 CopyNewPathAtEndOfNodes(path);
4068 const int new_path_end = committed_nodes_.size() - old_num_nodes;
4069 chains_[path] = {new_path_begin, new_path_end};
4071 committed_nodes_.erase(committed_nodes_.begin(),
4072 committed_nodes_.begin() + old_num_nodes);
4075 constexpr int kUnindexed = -1;
4076 committed_index_.assign(num_nodes_, kUnindexed);
4078 for (
const int node : committed_nodes_) {
4079 committed_index_[node] = index++;
4081 for (
int node = 0; node < num_nodes_; ++node) {
4082 if (committed_index_[node] != kUnindexed)
continue;
4083 committed_index_[node] = index++;
4084 committed_nodes_.push_back(node);
4087 committed_paths_[loop] =
kLoop;
4097 std::string DebugString()
const override {
return "PathStateFilter"; }
4098 PathStateFilter(std::unique_ptr<PathState> path_state,
4099 const std::vector<IntVar*>& nexts);
4100 void Relax(
const Assignment* delta,
const Assignment* deltadelta)
override;
4101 bool Accept(
const Assignment*,
const Assignment*, int64_t, int64_t)
override {
4104 void Synchronize(
const Assignment*,
const Assignment*)
override {};
4105 void Commit(
const Assignment* assignment,
const Assignment* delta)
override;
4106 void Revert()
override;
4107 void Reset()
override;
4111 struct TailHeadIndices {
4118 bool operator<(
const IndexArc& other)
const {
return index < other.index; }
4125 void MakeChainsFromChangedPathsAndArcsWithSelectionAlgorithm();
4128 void MakeChainsFromChangedPathsAndArcsWithGenericAlgorithm();
4130 const std::unique_ptr<PathState> path_state_;
4132 std::vector<int> variable_index_to_node_;
4136 std::vector<int> changed_paths_;
4137 std::vector<bool> path_has_changed_;
4138 std::vector<std::pair<int, int>> changed_arcs_;
4139 std::vector<int> changed_loops_;
4140 std::vector<TailHeadIndices> tail_head_indices_;
4141 std::vector<IndexArc> arcs_by_tail_index_;
4142 std::vector<IndexArc> arcs_by_head_index_;
4143 std::vector<int> next_arc_;
4144 std::vector<PathState::ChainBounds> path_chains_;
4147PathStateFilter::PathStateFilter(std::unique_ptr<PathState> path_state,
4148 const std::vector<IntVar*>& nexts)
4149 : path_state_(std::move(path_state)) {
4151 int min_index = std::numeric_limits<int>::max();
4152 int max_index = std::numeric_limits<int>::min();
4153 for (
const IntVar* next : nexts) {
4154 const int index = next->index();
4155 min_index = std::min<int>(min_index, index);
4156 max_index = std::max<int>(max_index, index);
4158 variable_index_to_node_.resize(max_index - min_index + 1, -1);
4159 index_offset_ = min_index;
4162 for (
int node = 0; node < nexts.size(); ++node) {
4163 const int index = nexts[node]->index() - index_offset_;
4164 variable_index_to_node_[index] = node;
4166 path_has_changed_.assign(path_state_->NumPaths(),
false);
4170 path_state_->Revert();
4171 changed_arcs_.clear();
4172 for (
const IntVarElement& var_value : delta->IntVarContainer().elements()) {
4173 if (var_value.Var() ==
nullptr)
continue;
4174 const int index = var_value.Var()->index() - index_offset_;
4175 if (index < 0 || variable_index_to_node_.size() <= index)
continue;
4176 const int node = variable_index_to_node_[index];
4177 if (node == -1)
continue;
4178 if (var_value.Bound()) {
4179 changed_arcs_.emplace_back(node, var_value.Value());
4181 path_state_->Revert();
4182 path_state_->SetInvalid();
4189void PathStateFilter::Reset() { path_state_->Reset(); }
4194void PathStateFilter::Commit(
const Assignment* assignment,
4195 const Assignment* delta) {
4196 path_state_->Revert();
4197 if (delta ==
nullptr || delta->Empty()) {
4198 Relax(assignment,
nullptr);
4200 Relax(delta,
nullptr);
4202 path_state_->Commit();
4205void PathStateFilter::Revert() { path_state_->Revert(); }
4207void PathStateFilter::CutChains() {
4211 for (
const int path : changed_paths_) path_has_changed_[path] =
false;
4212 changed_paths_.clear();
4213 tail_head_indices_.clear();
4214 changed_loops_.clear();
4215 int num_changed_arcs = 0;
4216 for (
const auto [node, next] : changed_arcs_) {
4217 const int node_index = path_state_->CommittedIndex(node);
4218 const int next_index = path_state_->CommittedIndex(next);
4219 const int node_path = path_state_->Path(node);
4221 (next_index != node_index + 1 || node_path < 0)) {
4222 tail_head_indices_.push_back({node_index, next_index});
4223 changed_arcs_[num_changed_arcs++] = {node, next};
4224 if (node_path >= 0 && !path_has_changed_[node_path]) {
4225 path_has_changed_[node_path] =
true;
4226 changed_paths_.push_back(node_path);
4228 }
else if (node == next && node_path != PathState::kLoop) {
4229 changed_loops_.push_back(node);
4232 changed_arcs_.resize(num_changed_arcs);
4234 path_state_->ChangeLoops(changed_loops_);
4235 if (tail_head_indices_.size() + changed_paths_.size() <= 8) {
4236 MakeChainsFromChangedPathsAndArcsWithSelectionAlgorithm();
4238 MakeChainsFromChangedPathsAndArcsWithGenericAlgorithm();
4242void PathStateFilter::
4243 MakeChainsFromChangedPathsAndArcsWithSelectionAlgorithm() {
4244 int num_visited_changed_arcs = 0;
4245 const int num_changed_arcs = tail_head_indices_.size();
4247 for (
const int path : changed_paths_) {
4248 path_chains_.clear();
4249 const auto [start_index, end_index] = path_state_->CommittedPathRange(path);
4250 int current_index = start_index;
4254 int selected_arc = -1;
4255 int selected_tail_index = std::numeric_limits<int>::max();
4256 for (
int i = num_visited_changed_arcs;
i < num_changed_arcs; ++
i) {
4257 const int tail_index = tail_head_indices_[
i].tail_index;
4258 if (current_index <= tail_index && tail_index < selected_tail_index) {
4260 selected_tail_index = tail_index;
4268 if (start_index <= current_index && current_index < end_index &&
4269 end_index <= selected_tail_index) {
4270 path_chains_.emplace_back(current_index, end_index);
4273 path_chains_.emplace_back(current_index, selected_tail_index + 1);
4274 current_index = tail_head_indices_[selected_arc].head_index;
4275 std::swap(tail_head_indices_[num_visited_changed_arcs],
4276 tail_head_indices_[selected_arc]);
4277 ++num_visited_changed_arcs;
4280 path_state_->ChangePath(path, path_chains_);
4284void PathStateFilter::MakeChainsFromChangedPathsAndArcsWithGenericAlgorithm() {
4300 for (
const int path : changed_paths_) {
4301 const auto [start_index, end_index] = path_state_->CommittedPathRange(path);
4302 tail_head_indices_.push_back({end_index - 1, start_index});
4307 const int num_arc_indices = tail_head_indices_.size();
4308 arcs_by_tail_index_.resize(num_arc_indices);
4309 arcs_by_head_index_.resize(num_arc_indices);
4310 for (
int i = 0;
i < num_arc_indices; ++
i) {
4311 arcs_by_tail_index_[
i] = {tail_head_indices_[
i].tail_index,
i};
4312 arcs_by_head_index_[
i] = {tail_head_indices_[
i].head_index,
i};
4314 std::sort(arcs_by_tail_index_.begin(), arcs_by_tail_index_.end());
4315 std::sort(arcs_by_head_index_.begin(), arcs_by_head_index_.end());
4317 next_arc_.resize(num_arc_indices);
4318 for (
int i = 0;
i < num_arc_indices; ++
i) {
4319 next_arc_[arcs_by_head_index_[
i].arc] = arcs_by_tail_index_[
i].arc;
4325 const int first_fake_arc = num_arc_indices - changed_paths_.size();
4326 for (
int fake_arc = first_fake_arc; fake_arc < num_arc_indices; ++fake_arc) {
4327 path_chains_.clear();
4328 int32_t arc = fake_arc;
4330 const int chain_begin = tail_head_indices_[arc].head_index;
4331 arc = next_arc_[arc];
4332 const int chain_end = tail_head_indices_[arc].tail_index + 1;
4333 path_chains_.emplace_back(chain_begin, chain_end);
4334 }
while (arc != fake_arc);
4335 const int path = changed_paths_[fake_arc - first_fake_arc];
4336 path_state_->ChangePath(path, path_chains_);
4343 std::unique_ptr<PathState> path_state,
4344 const std::vector<IntVar*>& nexts) {
4345 PathStateFilter* filter =
new PathStateFilter(std::move(path_state), nexts);
4350using EInterval = DimensionChecker::ExtendedInterval;
4352constexpr int64_t
kint64min = std::numeric_limits<int64_t>::min();
4353constexpr int64_t
kint64max = std::numeric_limits<int64_t>::max();
4355EInterval operator&(
const EInterval& i1,
const EInterval& i2) {
4356 return {std::max(i1.num_negative_infinity == 0 ? i1.min :
kint64min,
4357 i2.num_negative_infinity == 0 ? i2.min :
kint64min),
4358 std::min(i1.num_positive_infinity == 0 ? i1.max :
kint64max,
4359 i2.num_positive_infinity == 0 ? i2.max :
kint64max),
4360 std::min(i1.num_negative_infinity, i2.num_negative_infinity),
4361 std::min(i1.num_positive_infinity, i2.num_positive_infinity)};
4364EInterval operator&=(EInterval& i1,
const EInterval& i2) {
4369bool IsEmpty(
const EInterval& interval) {
4370 const int64_t minimum_value =
4371 interval.num_negative_infinity == 0 ? interval.min :
kint64min;
4372 const int64_t maximum_value =
4373 interval.num_positive_infinity == 0 ? interval.max :
kint64max;
4374 return minimum_value > maximum_value;
4377EInterval
operator+(
const EInterval& i1,
const EInterval& i2) {
4378 return {
CapAdd(i1.min, i2.min),
CapAdd(i1.max, i2.max),
4379 i1.num_negative_infinity + i2.num_negative_infinity,
4380 i1.num_positive_infinity + i2.num_positive_infinity};
4383EInterval& operator+=(EInterval& i1,
const EInterval& i2) {
4388EInterval
operator-(
const EInterval& i1,
const EInterval& i2) {
4389 return {
CapSub(i1.min, i2.max),
CapSub(i1.max, i2.min),
4390 i1.num_negative_infinity + i2.num_positive_infinity,
4391 i1.num_positive_infinity + i2.num_negative_infinity};
4396EInterval Delta(
const EInterval& from,
const EInterval&
to) {
4398 to.num_negative_infinity - from.num_negative_infinity,
4399 to.num_positive_infinity - from.num_positive_infinity};
4402EInterval ToExtendedInterval(DimensionChecker::Interval interval) {
4403 const bool is_neg_infinity = interval.min ==
kint64min;
4404 const bool is_pos_infinity = interval.max ==
kint64max;
4405 return {is_neg_infinity ? 0 : interval.min,
4406 is_pos_infinity ? 0 : interval.max, is_neg_infinity ? 1 : 0,
4407 is_pos_infinity ? 1 : 0};
4410std::vector<EInterval> ToExtendedIntervals(
4411 absl::Span<const DimensionChecker::Interval> intervals) {
4412 std::vector<EInterval> extended_intervals;
4413 extended_intervals.reserve(intervals.size());
4414 for (
const auto& interval : intervals) {
4415 extended_intervals.push_back(ToExtendedInterval(interval));
4417 return extended_intervals;
4422 const PathState* path_state, std::vector<Interval> path_capacity,
4423 std::vector<int> path_class,
4424 std::vector<std::function<
Interval(int64_t, int64_t)>>
4425 demand_per_path_class,
4426 std::vector<Interval> node_capacity,
int min_range_size_for_riq)
4427 : path_state_(path_state),
4428 path_capacity_(ToExtendedIntervals(path_capacity)),
4429 path_class_(
std::move(path_class)),
4430 demand_per_path_class_(
std::move(demand_per_path_class)),
4431 node_capacity_(ToExtendedIntervals(node_capacity)),
4432 index_(path_state_->NumNodes(), 0),
4433 maximum_riq_layer_size_(
std::max(
4434 16, 4 * path_state_->NumNodes())),
4435 min_range_size_for_riq_(min_range_size_for_riq) {
4436 const int num_nodes = path_state_->NumNodes();
4437 cached_demand_.resize(num_nodes);
4438 const int num_paths = path_state_->NumPaths();
4439 DCHECK_EQ(num_paths, path_capacity_.size());
4440 DCHECK_EQ(num_paths, path_class_.size());
4442 riq_.resize(maximum_riq_exponent + 1);
4447 if (path_state_->IsInvalid())
return true;
4448 for (
const int path : path_state_->ChangedPaths()) {
4449 const EInterval path_capacity = path_capacity_[path];
4450 const int path_class = path_class_[path];
4453 int prev_node = path_state_->Start(path);
4454 EInterval cumul = node_capacity_[prev_node] & path_capacity;
4455 if (IsEmpty(cumul))
return false;
4457 for (
const auto chain : path_state_->Chains(path)) {
4458 const int first_node = chain.First();
4459 const int last_node = chain.Last();
4461 if (prev_node != first_node) {
4464 const EInterval demand = ToExtendedInterval(
4465 demand_per_path_class_[path_class](prev_node, first_node));
4467 cumul &= path_capacity;
4468 cumul &= node_capacity_[first_node];
4469 if (IsEmpty(cumul))
return false;
4470 prev_node = first_node;
4474 const int first_index = index_[first_node];
4475 const int last_index = index_[last_node];
4476 const int chain_path = path_state_->Path(first_node);
4477 const int chain_path_class =
4478 chain_path < 0 ? -1 : path_class_[chain_path];
4482 const bool chain_is_cached = chain_path_class == path_class;
4483 if (last_index - first_index > min_range_size_for_riq_ &&
4485 UpdateCumulUsingChainRIQ(first_index, last_index, path_capacity, cumul);
4486 if (IsEmpty(cumul))
return false;
4487 prev_node = chain.Last();
4489 for (
const int node : chain.WithoutFirstNode()) {
4490 const EInterval demand =
4492 ? cached_demand_[prev_node]
4493 : ToExtendedInterval(
4494 demand_per_path_class_[path_class](prev_node, node));
4496 cumul &= node_capacity_[node];
4497 cumul &= path_capacity;
4498 if (IsEmpty(cumul))
return false;
4508 const int current_layer_size = riq_[0].size();
4509 int change_size = path_state_->ChangedPaths().size();
4510 for (
const int path : path_state_->ChangedPaths()) {
4511 for (
const auto chain : path_state_->Chains(path)) {
4512 change_size += chain.NumNodes();
4515 if (current_layer_size + change_size <= maximum_riq_layer_size_) {
4516 IncrementalCommit();
4522void DimensionChecker::IncrementalCommit() {
4524 const int begin_index = riq_[0].size();
4525 AppendPathDemandsToSums(path);
4526 UpdateRIQStructure(begin_index, riq_[0].size());
4530void DimensionChecker::FullCommit() {
4532 for (
auto& layer : riq_) layer.clear();
4534 const int num_paths = path_state_->NumPaths();
4535 for (
int path = 0; path < num_paths; ++path) {
4536 const int begin_index = riq_[0].size();
4537 AppendPathDemandsToSums(path);
4538 UpdateRIQStructure(begin_index, riq_[0].size());
4542void DimensionChecker::AppendPathDemandsToSums(
int path) {
4545 const int path_class = path_class_[path];
4546 EInterval demand_sum = {0, 0, 0, 0};
4547 int prev = path_state_->Start(path);
4548 int index = riq_[0].size();
4549 for (
const int node : path_state_->Nodes(path)) {
4551 const EInterval demand =
4552 prev == node ? EInterval{0, 0, 0, 0}
4553 : ToExtendedInterval(
4554 demand_per_path_class_[path_class](prev, node));
4555 demand_sum += demand;
4556 cached_demand_[prev] = demand;
4559 index_[node] = index++;
4560 riq_[0].push_back({.cumuls_to_fst = node_capacity_[node],
4561 .tightest_tsum = demand_sum,
4562 .cumuls_to_lst = node_capacity_[node],
4563 .tsum_at_fst = demand_sum,
4564 .tsum_at_lst = demand_sum});
4566 cached_demand_[path_state_->End(path)] = {0, 0, 0, 0};
4569void DimensionChecker::UpdateRIQStructure(
int begin_index,
int end_index) {
4572 const int max_layer =
4574 for (
int layer = 1, half_window = 1; layer <= max_layer;
4575 ++layer, half_window *= 2) {
4576 riq_[layer].resize(end_index);
4577 for (
int i = begin_index + 2 * half_window - 1;
i < end_index; ++
i) {
4583 const RIQNode& fw = riq_[layer - 1][
i - half_window];
4584 const RIQNode& lw = riq_[layer - 1][
i];
4585 const EInterval lst_to_lst = Delta(fw.tsum_at_lst, lw.tsum_at_lst);
4586 const EInterval fst_to_fst = Delta(fw.tsum_at_fst, lw.tsum_at_fst);
4589 .cumuls_to_fst = fw.cumuls_to_fst & lw.cumuls_to_fst - fst_to_fst,
4590 .tightest_tsum = fw.tightest_tsum & lw.tightest_tsum,
4591 .cumuls_to_lst = fw.cumuls_to_lst + lst_to_lst & lw.cumuls_to_lst,
4592 .tsum_at_fst = fw.tsum_at_fst,
4593 .tsum_at_lst = lw.tsum_at_lst};
4602void DimensionChecker::UpdateCumulUsingChainRIQ(
4603 int first_index,
int last_index,
const ExtendedInterval& path_capacity,
4604 ExtendedInterval& cumul)
const {
4605 DCHECK_LE(0, first_index);
4606 DCHECK_LT(first_index, last_index);
4607 DCHECK_LT(last_index, riq_[0].size());
4609 const int window = 1 << layer;
4610 const RIQNode& fw = riq_[layer][first_index + window - 1];
4611 const RIQNode& lw = riq_[layer][last_index];
4614 cumul &= fw.cumuls_to_fst;
4615 cumul &= lw.cumuls_to_fst - Delta(fw.tsum_at_fst, lw.tsum_at_fst);
4616 cumul &= path_capacity -
4617 Delta(fw.tsum_at_fst, fw.tightest_tsum & lw.tightest_tsum);
4620 if (IsEmpty(cumul))
return;
4623 cumul += Delta(fw.tsum_at_fst, lw.tsum_at_lst);
4626 cumul &= fw.cumuls_to_lst + Delta(fw.tsum_at_lst, lw.tsum_at_lst);
4627 cumul &= lw.cumuls_to_lst;
4634 std::string DebugString()
const override {
return name_; }
4635 DimensionFilter(std::unique_ptr<DimensionChecker> checker,
4636 absl::string_view dimension_name)
4637 : checker_(std::move(checker)),
4638 name_(absl::StrCat(
"DimensionFilter(", dimension_name,
")")) {}
4640 bool Accept(
const Assignment*,
const Assignment*, int64_t, int64_t)
override {
4641 return checker_->Check();
4644 void Synchronize(
const Assignment*,
const Assignment*)
override {
4649 std::unique_ptr<DimensionChecker> checker_;
4650 const std::string name_;
4656 Solver* solver, std::unique_ptr<DimensionChecker> checker,
4657 absl::string_view dimension_name) {
4658 DimensionFilter* filter =
4659 new DimensionFilter(std::move(checker), dimension_name);
4664 PathState* path_state, std::vector<PathData> path_data)
4665 : path_state_(path_state), path_data_(
std::move(path_data)) {}
4668 for (
const int path : path_state_->ChangedPaths()) {
4669 path_data_[path].start_cumul.Relax();
4670 path_data_[path].end_cumul.Relax();
4671 path_data_[path].span.Relax();
4676 for (
const int path : path_state_->ChangedPaths()) {
4677 if (!path_data_[path].span.Exists())
continue;
4678 const PathData& data = path_data_[path];
4680 int64_t lb_span = data.
span.
Min();
4684 int64_t lb_span_tw = total_transit;
4688 if (!br.is_performed_min)
continue;
4689 if (br.start_max < end_min && start_max < br.end_min) {
4690 CapAddTo(br.duration_min, &lb_span_tw);
4691 start_max = std::min(start_max, br.start_max);
4692 end_min = std::max(end_min, br.end_min);
4695 lb_span = std::max({lb_span, lb_span_tw,
CapSub(end_min, start_max)});
4701 start_min = std::max(start_min,
CapSub(end_min, data.
span.
Max()));
4703 end_max = std::min(end_max,
CapAdd(start_max, data.
span.
Max()));
4704 int num_feasible_breaks = 0;
4706 if (start_min <= br.start_max && br.end_min <= end_max) {
4707 break_start_min = std::min(break_start_min, br.start_min);
4708 break_end_max = std::max(break_end_max, br.end_max);
4709 ++num_feasible_breaks;
4716 for (
const auto& [max_interbreak, min_break_duration] :
4722 if (max_interbreak == 0) {
4723 if (total_transit > 0)
return false;
4726 int64_t min_num_breaks =
4727 std::max<int64_t>(0, (total_transit - 1) / max_interbreak);
4728 if (lb_span > max_interbreak) {
4729 min_num_breaks = std::max<int64_t>(min_num_breaks, 1);
4731 if (min_num_breaks > num_feasible_breaks)
return false;
4734 CapAdd(total_transit,
CapProd(min_num_breaks, min_break_duration)));
4735 if (min_num_breaks > 0) {
4744 if (!data.
span.
SetMin(lb_span))
return false;
4746 start_max = std::min(start_max,
CapSub(end_max, lb_span));
4748 end_min = std::max(end_min,
CapAdd(start_min, lb_span));
4758 LightVehicleBreaksFilter(std::unique_ptr<LightVehicleBreaksChecker> checker,
4759 absl::string_view dimension_name)
4760 : checker_(
std::move(checker)),
4761 name_(
absl::StrCat(
"LightVehicleBreaksFilter(", dimension_name,
")")) {}
4763 std::string DebugString()
const override {
return name_; }
4765 void Relax(
const Assignment*,
const Assignment*)
override {
4769 bool Accept(
const Assignment*,
const Assignment*, int64_t, int64_t)
override {
4770 return checker_->Check();
4773 void Synchronize(
const Assignment*,
const Assignment*)
override {
4778 std::unique_ptr<LightVehicleBreaksChecker> checker_;
4779 const std::string name_;
4785 Solver* solver, std::unique_ptr<LightVehicleBreaksChecker> checker,
4786 absl::string_view dimension_name) {
4787 LightVehicleBreaksFilter* filter =
4788 new LightVehicleBreaksFilter(std::move(checker), dimension_name);
4794 tree_location_.clear();
4796 for (
auto& layer : tree_layers_) layer.clear();
4801 const int begin_index = tree_location_.size();
4802 const int end_index = elements_.size();
4803 DCHECK_LE(begin_index, end_index);
4804 if (begin_index >= end_index)
return;
4811 const int old_node_size = nodes_.size();
4812 for (
int i = begin_index; i < end_index; ++i) {
4813 nodes_.push_back({.pivot_height = elements_[i].height, .pivot_index = -1});
4815 std::sort(nodes_.begin() + old_node_size, nodes_.end());
4816 nodes_.erase(std::unique(nodes_.begin() + old_node_size, nodes_.end()),
4821 const int new_node_size = nodes_.size();
4822 tree_location_.resize(end_index, {.node_begin = old_node_size,
4823 .node_end = new_node_size,
4824 .sequence_first = begin_index});
4828 const int num_layers =
4830 if (tree_layers_.size() <= num_layers) tree_layers_.resize(num_layers);
4831 for (
int l = 0; l < num_layers; ++l) {
4832 tree_layers_[l].resize(end_index,
4833 {.prefix_sum = 0, .left_index = -1, .is_left = 0});
4838 const auto fill_subtree = [
this](
auto& fill_subtree,
int layer,
4839 int node_begin,
int node_end,
4840 int range_begin,
int range_end) {
4841 DCHECK_LT(node_begin, node_end);
4842 DCHECK_LT(range_begin, range_end);
4845 for (
int i = range_begin; i < range_end; ++i) {
4846 sum += elements_[i].weight;
4847 tree_layers_[layer][i].prefix_sum = sum;
4849 if (node_begin + 1 == node_end)
return;
4854 const int node_mid = node_begin + (node_end - node_begin) / 2;
4855 const int64_t pivot_height = nodes_[node_mid].pivot_height;
4856 int pivot_index = range_begin;
4857 for (
int i = range_begin; i < range_end; ++i) {
4858 tree_layers_[layer][i].left_index = pivot_index;
4859 tree_layers_[layer][i].is_left = elements_[i].height < pivot_height;
4860 if (elements_[i].height < pivot_height) ++pivot_index;
4862 nodes_[node_mid].pivot_index = pivot_index;
4865 std::stable_partition(
4866 elements_.begin() + range_begin, elements_.begin() + range_end,
4867 [pivot_height](
const auto& el) { return el.height < pivot_height; });
4869 fill_subtree(fill_subtree, layer + 1, node_begin, node_mid, range_begin,
4871 fill_subtree(fill_subtree, layer + 1, node_mid, node_end, pivot_index,
4874 fill_subtree(fill_subtree, 0, old_node_size, new_node_size, begin_index,
4880 int end_index)
const {
4881 DCHECK_LE(begin_index, end_index);
4882 DCHECK_LE(end_index, tree_location_.size());
4883 DCHECK_EQ(tree_location_.size(), elements_.size());
4884 if (begin_index >= end_index)
return 0;
4885 auto [node_begin, node_end, sequence_first_index] =
4886 tree_location_[begin_index];
4887 DCHECK_EQ(tree_location_[end_index - 1].sequence_first,
4888 sequence_first_index);
4890 .range_first_index = begin_index,
4891 .range_last_index = end_index - 1,
4892 .range_first_is_node_first = begin_index == sequence_first_index};
4894 if (nodes_[node_end - 1].pivot_height < threshold_height)
return 0;
4897 int64_t min_height_of_current_node = nodes_[node_begin].pivot_height;
4898 for (
int l = 0; !range.Empty(); ++l) {
4899 const ElementInfo* elements = tree_layers_[l].data();
4900 if (threshold_height <= min_height_of_current_node) {
4903 sum += range.Sum(elements);
4905 }
else if (node_begin + 1 == node_end) {
4910 const int node_mid = node_begin + (node_end - node_begin) / 2;
4911 const auto [pivot_height, pivot_index] = nodes_[node_mid];
4912 const ElementRange right = range.RightSubRange(elements, pivot_index);
4913 if (threshold_height < pivot_height) {
4916 if (!right.Empty()) sum += right.Sum(tree_layers_[l + 1].data());
4918 range = range.LeftSubRange(elements);
4919 node_end = node_mid;
4923 node_begin = node_mid;
4924 min_height_of_current_node = pivot_height;
4931 const PathState* path_state, std::vector<int64_t> force_start_min,
4932 std::vector<int64_t> force_end_min, std::vector<int> force_class,
4933 std::vector<
const std::function<int64_t(int64_t)>*> force_per_class,
4934 std::vector<int> distance_class,
4935 std::vector<
const std::function<int64_t(int64_t, int64_t)>*>
4937 std::vector<EnergyCost> path_energy_cost,
4938 std::vector<bool> path_has_cost_when_empty)
4939 : path_state_(path_state),
4940 force_start_min_(
std::move(force_start_min)),
4941 force_end_min_(
std::move(force_end_min)),
4942 force_class_(
std::move(force_class)),
4943 distance_class_(
std::move(distance_class)),
4944 force_per_class_(
std::move(force_per_class)),
4945 distance_per_class_(
std::move(distance_per_class)),
4946 path_energy_cost_(
std::move(path_energy_cost)),
4947 path_has_cost_when_empty_(
std::move(path_has_cost_when_empty)),
4948 maximum_range_query_size_(4 * path_state_->NumNodes()),
4949 force_rmq_index_of_node_(path_state_->NumNodes()),
4950 threshold_query_index_of_node_(path_state_->NumNodes()) {
4951 const int num_nodes = path_state_->NumNodes();
4952 cached_force_.resize(num_nodes);
4953 cached_distance_.resize(num_nodes);
4954 FullCacheAndPrecompute();
4955 committed_total_cost_ = 0;
4956 committed_path_cost_.assign(path_state_->NumPaths(), 0);
4957 const int num_paths = path_state_->NumPaths();
4958 for (
int path = 0; path < num_paths; ++path) {
4959 committed_path_cost_[path] = ComputePathCost(path);
4960 CapAddTo(committed_path_cost_[path], &committed_total_cost_);
4962 accepted_total_cost_ = committed_total_cost_;
4966 if (path_state_->IsInvalid())
return true;
4967 accepted_total_cost_ = committed_total_cost_;
4968 for (
const int path : path_state_->ChangedPaths()) {
4969 accepted_total_cost_ =
4970 CapSub(accepted_total_cost_, committed_path_cost_[path]);
4971 CapAddTo(ComputePathCost(path), &accepted_total_cost_);
4972 if (accepted_total_cost_ ==
kint64max)
return false;
4977void PathEnergyCostChecker::CacheAndPrecomputeRangeQueriesOfPath(
int path) {
4980 const auto& force_evaluator = *force_per_class_[force_class_[path]];
4981 const auto& distance_evaluator = *distance_per_class_[distance_class_[path]];
4982 int force_index = force_rmq_.
TableSize();
4983 int threshold_index = energy_query_.
TreeSize();
4984 int64_t total_force = 0;
4986 const int start_node = path_state_->
Start(path);
4987 int prev_node = start_node;
4989 for (
const int node : path_state_->
Nodes(path)) {
4990 if (prev_node != node) {
4991 const int64_t distance = distance_evaluator(prev_node, node);
4992 cached_distance_[prev_node] = distance;
4993 energy_query_.
PushBack(total_force, total_force * distance);
4994 distance_query_.
PushBack(total_force, distance);
4997 threshold_query_index_of_node_[node] = threshold_index++;
4999 force_rmq_index_of_node_[node] = force_index++;
5000 const int64_t force = force_evaluator(node);
5001 cached_force_[node] = force;
5002 total_force += force;
5004 force_rmq_.MakeTableFromNewElements();
5005 energy_query_.MakeTreeFromNewElements();
5006 distance_query_.MakeTreeFromNewElements();
5009void PathEnergyCostChecker::IncrementalCacheAndPrecompute() {
5010 for (
const int path : path_state_->ChangedPaths()) {
5011 CacheAndPrecomputeRangeQueriesOfPath(path);
5015void PathEnergyCostChecker::FullCacheAndPrecompute() {
5018 const int num_paths = path_state_->NumPaths();
5019 for (
int path = 0; path < num_paths; ++path) {
5020 CacheAndPrecomputeRangeQueriesOfPath(path);
5025 int change_size = path_state_->ChangedPaths().size();
5026 for (
const int path : path_state_->ChangedPaths()) {
5027 for (
const auto chain : path_state_->Chains(path)) {
5028 change_size += chain.NumNodes();
5030 committed_total_cost_ =
5031 CapSub(committed_total_cost_, committed_path_cost_[path]);
5032 committed_path_cost_[path] = ComputePathCost(path);
5033 CapAddTo(committed_path_cost_[path], &committed_total_cost_);
5036 const int current_layer_size = force_rmq_.TableSize();
5037 if (current_layer_size + change_size <= maximum_range_query_size_) {
5038 IncrementalCacheAndPrecompute();
5040 FullCacheAndPrecompute();
5044int64_t PathEnergyCostChecker::ComputePathCost(int64_t path)
const {
5045 const int path_force_class = force_class_[path];
5046 const auto& force_evaluator = *force_per_class_[path_force_class];
5049 int64_t total_force = force_start_min_[path];
5050 int64_t min_force = total_force;
5051 int num_path_nodes = 0;
5052 int prev_node = path_state_->
Start(path);
5053 for (
const auto chain : path_state_->
Chains(path)) {
5054 num_path_nodes += chain.NumNodes();
5056 if (chain.First() != prev_node) {
5057 const int64_t force_to_node = force_evaluator(prev_node);
5058 CapAddTo(force_to_node, &total_force);
5059 min_force = std::min(min_force, total_force);
5060 prev_node = chain.First();
5064 const int chain_path = path_state_->
Path(chain.First());
5065 const int chain_force_class =
5066 chain_path < 0 ? -1 : force_class_[chain_path];
5067 const bool force_is_cached = chain_force_class == path_force_class;
5068 if (force_is_cached && chain.NumNodes() >= 2) {
5069 const int first_index = force_rmq_index_of_node_[chain.First()];
5070 const int last_index = force_rmq_index_of_node_[chain.Last()];
5072 const int64_t first_total_force = force_rmq_.
array()[first_index];
5073 const int64_t last_total_force = force_rmq_.
array()[last_index];
5074 const int64_t min_total_force =
5077 min_force = std::min(min_force,
5078 total_force - first_total_force + min_total_force);
5079 CapAddTo(last_total_force - first_total_force, &total_force);
5080 prev_node = chain.Last();
5082 for (
const int node : chain.WithoutFirstNode()) {
5083 const int64_t force = force_is_cached ? cached_force_[prev_node]
5084 : force_evaluator(prev_node);
5086 min_force = std::min(min_force, total_force);
5091 if (num_path_nodes == 2 && !path_has_cost_when_empty_[path])
return 0;
5097 total_force = std::max<int64_t>(
5098 {0,
CapOpp(min_force),
CapSub(force_end_min_[path], total_force)});
5099 CapAddTo(force_start_min_[path], &total_force);
5102 const int path_distance_class = distance_class_[path];
5103 const auto& distance_evaluator = *distance_per_class_[path_distance_class];
5104 const EnergyCost& cost = path_energy_cost_[path];
5105 int64_t energy_below = 0;
5106 int64_t energy_above = 0;
5107 prev_node = path_state_->Start(path);
5108 for (
const auto chain : path_state_->Chains(path)) {
5110 if (chain.First() != prev_node) {
5111 const int64_t distance = distance_evaluator(prev_node, chain.First());
5112 CapAddTo(force_evaluator(prev_node), &total_force);
5115 const int64_t force_above =
5116 std::max<int64_t>(0,
CapSub(total_force, cost.threshold));
5118 prev_node = chain.First();
5123 const int chain_path = path_state_->Path(chain.First());
5124 const int chain_force_class =
5125 chain_path < 0 ? -1 : force_class_[chain_path];
5126 const int chain_distance_class =
5127 chain_path < 0 ? -1 : distance_class_[chain_path];
5128 const bool force_is_cached = chain_force_class == path_force_class;
5129 const bool distance_is_cached = chain_distance_class == path_distance_class;
5131 if (force_is_cached && distance_is_cached && chain.NumNodes() >= 2) {
5132 const int first_index = threshold_query_index_of_node_[chain.First()];
5133 const int last_index = threshold_query_index_of_node_[chain.Last()];
5135 const int64_t zero_total_energy = energy_query_.RangeSumWithThreshold(
5137 const int64_t total_distance = distance_query_.RangeSumWithThreshold(
5146 const int64_t zero_total_force_first =
5147 force_rmq_.array()[force_rmq_index_of_node_[chain.First()]];
5148 const int64_t zero_threshold =
5149 CapSub(cost.threshold,
CapSub(total_force, zero_total_force_first));
5153 const int64_t zero_high_energy = energy_query_.RangeSumWithThreshold(
5154 zero_threshold, first_index, last_index);
5155 const int64_t zero_high_distance = distance_query_.RangeSumWithThreshold(
5156 zero_threshold, first_index, last_index);
5160 const int64_t zero_energy_above =
5161 CapSub(zero_high_energy,
CapProd(zero_high_distance, zero_threshold));
5167 CapAddTo(zero_energy_above, &energy_above);
5170 CapSub(cost.threshold, zero_threshold))),
5174 const int64_t zero_total_force_last =
5175 force_rmq_.array()[force_rmq_index_of_node_[chain.Last()]];
5178 prev_node = chain.Last();
5180 for (
const int node : chain.WithoutFirstNode()) {
5181 const int64_t force = force_is_cached ? cached_force_[prev_node]
5182 : force_evaluator(prev_node);
5183 const int64_t distance = distance_is_cached
5184 ? cached_distance_[prev_node]
5185 : distance_evaluator(prev_node, node);
5189 const int64_t force_above =
5190 std::max<int64_t>(0,
CapSub(total_force, cost.threshold));
5197 return CapAdd(
CapProd(energy_below, cost.cost_per_unit_below_threshold),
5198 CapProd(energy_above, cost.cost_per_unit_above_threshold));
5205 std::string DebugString()
const override {
return name_; }
5206 PathEnergyCostFilter(std::unique_ptr<PathEnergyCostChecker> checker,
5207 absl::string_view energy_name)
5208 : checker_(std::move(checker)),
5209 name_(absl::StrCat(
"PathEnergyCostFilter(", energy_name,
")")) {}
5211 bool Accept(
const Assignment*,
const Assignment*, int64_t objective_min,
5212 int64_t objective_max)
override {
5213 if (objective_max >
kint64max / 2)
return true;
5214 if (!checker_->Check())
return false;
5215 const int64_t cost = checker_->AcceptedCost();
5216 return objective_min <= cost && cost <= objective_max;
5219 void Synchronize(
const Assignment*,
const Assignment*)
override {
5223 int64_t GetSynchronizedObjectiveValue()
const override {
5224 return checker_->CommittedCost();
5226 int64_t GetAcceptedObjectiveValue()
const override {
5227 return checker_->AcceptedCost();
5231 std::unique_ptr<PathEnergyCostChecker> checker_;
5232 const std::string name_;
5238 Solver* solver, std::unique_ptr<PathEnergyCostChecker> checker,
5239 absl::string_view dimension_name) {
5240 PathEnergyCostFilter* filter =
5241 new PathEnergyCostFilter(std::move(checker), dimension_name);
5249 const std::vector<std::vector<double>>& rows)
5250 : num_variables_(rows.empty() ? 0 : rows[0].size()),
5251 num_rows_(rows.size()) {
5252 if (num_variables_ == 0)
return;
5254 const int64_t num_block_rows = (num_rows_ + kBlockSize - 1) / kBlockSize;
5255 blocks_.resize(num_block_rows * num_variables_);
5256 const int64_t num_full_block_rows = num_rows_ / kBlockSize;
5257 for (int64_t br = 0; br < num_full_block_rows; ++br) {
5258 for (int64_t v = 0; v < num_variables_; ++v) {
5259 Block& block = blocks_[br * num_variables_ + v];
5260 for (
int c = 0; c < kBlockSize; ++c) {
5261 block.coefficients[c] = rows[br * kBlockSize + c][v];
5268 if (num_full_block_rows == num_block_rows)
return;
5269 DCHECK_EQ(num_full_block_rows + 1, num_block_rows);
5270 Block* last_block_row = &blocks_[num_full_block_rows * num_variables_];
5271 const int first_coefficient_to_pad =
5272 num_rows_ - num_full_block_rows * kBlockSize;
5273 for (int64_t v = 0; v < num_variables_; ++v) {
5274 Block& block = last_block_row[v];
5276 for (
int c = 0; c < first_coefficient_to_pad; ++c) {
5277 const int64_t r = num_full_block_rows * kBlockSize + c;
5278 block.coefficients[c] = rows[r][v];
5281 for (
int c = first_coefficient_to_pad; c < kBlockSize; ++c) {
5282 block.coefficients[c] = block.coefficients[0];
5288 absl::Span<const double> values)
const {
5289 DCHECK_EQ(values.size(), num_variables_);
5290 constexpr double kInfinity = std::numeric_limits<double>::infinity();
5292 if (num_variables_ == 0)
return 0.0;
5293 DCHECK_EQ(blocks_.size() % num_variables_, 0);
5295 absl::c_fill(maximums.coefficients, -
kInfinity);
5296 for (
auto row = blocks_.begin(); row != blocks_.end();
5297 row += num_variables_) {
5299 absl::c_fill(evaluations.coefficients, 0.0);
5300 for (int64_t v = 0; v < num_variables_; ++v) {
5301 evaluations.BlockMultiplyAdd(row[v], values[v]);
5303 maximums.MaximumWith(evaluations);
5305 return maximums.Maximum();
const std::vector< E > & elements() const
AssignmentContainer< IntVar, IntVarElement > IntContainer
const IntContainer & IntVarContainer() const
Generic path-based filter class.
static const int64_t kUnassigned
BasePathFilter(const std::vector< IntVar * > &nexts, int next_domain_size, const PathsMetadata &paths_metadata)
bool Accept(const Assignment *delta, const Assignment *deltadelta, int64_t objective_min, int64_t objective_max) override
void OnSynchronize(const Assignment *delta) override
int64_t Start(int i) const
DimensionChecker(const PathState *path_state, std::vector< Interval > path_capacity, std::vector< int > path_class, std::vector< std::function< Interval(int64_t, int64_t)> > demand_per_path_class, std::vector< Interval > node_capacity, int min_range_size_for_riq=kOptimalMinRangeSizeForRIQ)
absl::Span< const int64_t > CommittedTravels(int path) const
absl::Span< Interval > MutableCumuls(int path)
int NumNodes(int path) const
absl::Span< const int > Nodes(int path) const
absl::Span< Interval > MutableTransits(int path)
Interval & MutableSpan(int path)
absl::Span< int64_t > MutableTravelSums(int path)
absl::Span< int64_t > MutableTravels(int path)
std::vector< VehicleBreak > & MutableVehicleBreaks(int path)
absl::Span< const int > CommittedNodes(int path) const
absl::Span< const int64_t > TravelSums(int path) const
const RoutingDimension * dimension() const
virtual int64_t Min() const =0
virtual int64_t Max() const =0
IntVarLocalSearchFilter(const std::vector< IntVar * > &vars)
bool FindIndex(IntVar *const var, int64_t *index) const
int64_t Value(int index) const
bool IsVarSynced(int index) const
IntVar * Var() override
Creates a variable from the expression.
LightVehicleBreaksChecker(PathState *path_state, std::vector< PathData > path_data)
const RoutingDimension * dimension() const
bool SetMin(int64_t new_min) const
bool SetMax(int64_t new_max) const
double Evaluate(absl::Span< const double > values) const
MaxLinearExpressionEvaluator(const std::vector< std::vector< double > > &rows)
PathEnergyCostChecker(const PathState *path_state, std::vector< int64_t > force_start_min, std::vector< int64_t > force_end_min, std::vector< int > force_class, std::vector< const std::function< int64_t(int64_t)> * > force_per_class, std::vector< int > distance_class, std::vector< const std::function< int64_t(int64_t, int64_t)> * > distance_per_class, std::vector< EnergyCost > path_energy_cost, std::vector< bool > path_has_cost_when_empty)
void ChangeLoops(absl::Span< const int > new_loops)
const std::vector< int > & ChangedLoops() const
const std::vector< int > & ChangedPaths() const
ChainRange Chains(int path) const
static constexpr int kUnassigned
NodeRange Nodes(int path) const
PathState(int num_nodes, std::vector< int > path_start, std::vector< int > path_end)
static constexpr int kLoop
void ChangePath(int path, absl::Span< const ChainBounds > chains)
int Start(int path) const
absl::Span< int64_t > MutablePostVisits(int path)
void ChangePathSize(int path, int new_num_nodes)
absl::Span< int64_t > MutablePreVisits(int path)
const std::vector< T > & array() const
T RangeMinimum(int begin, int end) const
bool HasBreakConstraints() const
Returns true if any break interval or break distance was defined.
const std::vector< NodePrecedence > & GetNodePrecedences() const
RoutingModel * model() const
Returns the model on which the dimension was created.
int64_t global_span_cost_coefficient() const
int64_t Size() const
Returns the number of next variables in the model.
int64_t Start(int vehicle) const
RoutingDisjunctionIndex DisjunctionIndex
GlobalDimensionCumulOptimizer * GetMutableGlobalCumulLPOptimizer(const RoutingDimension &dimension) const
GlobalDimensionCumulOptimizer * GetMutableGlobalCumulMPOptimizer(const RoutingDimension &dimension) const
LocalDimensionCumulOptimizer * GetMutableLocalCumulMPOptimizer(const RoutingDimension &dimension) const
bool IsEnd(int64_t index) const
Returns true if 'index' represents the last node of a route.
int vehicles() const
Returns the number of vehicle routes in the model.
const std::vector< int > & GetDimensionResourceGroupIndices(const RoutingDimension *dimension) const
int64_t GetSoftSameVehicleCost(int index) const
Returns the cost of the soft same vehicle constraint of index 'index'.
LocalDimensionCumulOptimizer * GetMutableLocalCumulLPOptimizer(const RoutingDimension &dimension) const
const std::vector< operations_research::IntVar * > & Nexts() const
bool disable_scheduling_beware_this_may_degrade_performance() const
void MakeTreeFromNewElements()
void PushBack(int64_t height, int64_t weight)
int64_t RangeSumWithThreshold(int64_t threshold_height, int begin_index, int end_index) const
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)
bool FindCopy(const Collection &collection, const Key &key, Value *const value)
const MapUtilMappedT< Collection > & FindWithDefault(const Collection &collection, const KeyType &key, const MapUtilMappedT< Collection > &value)
For infeasible and unbounded see Not checked if options check_solutions_if_inf_or_unbounded and the If options first_solution_only is false
std::function< int64_t(const Model &)> Value(IntegerVariable v)
LocalSearchFilter * MakePickupDeliveryFilter(const RoutingModel &routing_model, const PathState *path_state, absl::Span< const PickupDeliveryPair > pairs, const std::vector< RoutingModel::PickupAndDeliveryPolicy > &vehicle_policies)
IntVarLocalSearchFilter * MakeVehicleAmortizedCostFilter(const RoutingModel &routing_model)
Returns a filter computing vehicle amortized costs.
int64_t CapAdd(int64_t x, int64_t y)
bool FillDimensionValuesFromRoutingDimension(int path, int64_t capacity, int64_t span_upper_bound, absl::Span< const DimensionValues::Interval > cumul_of_node, absl::Span< const DimensionValues::Interval > slack_of_node, absl::AnyInvocable< int64_t(int64_t, int64_t) const > evaluator, DimensionValues &dimension_values)
IntVarLocalSearchFilter * MakeRouteConstraintFilter(const RoutingModel &routing_model)
Returns a filter tracking route constraints.
void CapAddTo(int64_t x, int64_t *y)
static constexpr double kInfinity
IntVarLocalSearchFilter * MakeSameVehicleCostFilter(const RoutingModel &routing_model)
Returns a filter computing same vehicle costs.
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)
int64_t ComputeBestVehicleToResourceAssignment(absl::Span< const 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)
DimensionSchedulingStatus
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)
LinearExpr operator-(LinearExpr lhs, const LinearExpr &rhs)
void CapSubFrom(int64_t amount, int64_t *target)
IntVarLocalSearchFilter * MakeCumulBoundsPropagatorFilter(const RoutingDimension &dimension)
Returns a filter handling dimension cumul bounds.
LocalSearchFilter * MakePathStateFilter(Solver *solver, std::unique_ptr< PathState > path_state, const std::vector< IntVar * > &nexts)
ClosedInterval::Iterator end(ClosedInterval interval)
LocalSearchFilter * MakeLightVehicleBreaksFilter(Solver *solver, std::unique_ptr< LightVehicleBreaksChecker > checker, absl::string_view dimension_name)
bool PropagateTransitAndSpan(int path, DimensionValues &dimension_values)
IntVarLocalSearchFilter * MakePathCumulFilter(const RoutingDimension &dimension, bool propagate_own_objective_value, bool filter_objective_cost, bool may_use_optimizers)
Returns a filter handling dimension costs and constraints.
IntVarLocalSearchFilter * MakeCPFeasibilityFilter(RoutingModel *routing_model)
Returns a filter checking the current solution using CP propagation.
bool ComputeVehicleToResourceClassAssignmentCosts(int v, double solve_duration_ratio, 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)
LinearExpr operator+(LinearExpr lhs, const LinearExpr &rhs)
void FillPrePostVisitValues(int path, const DimensionValues &dimension_values, std::optional< absl::AnyInvocable< int64_t(int64_t, int64_t) const > > pre_travel_evaluator, std::optional< absl::AnyInvocable< int64_t(int64_t, int64_t) const > > post_travel_evaluator, PrePostVisitValues &visit_values)
IntVarLocalSearchFilter * MakeOrderedActivityGroupFilter(const RoutingModel &routing_model)
int64_t CapProd(int64_t x, int64_t y)
LocalSearchFilter * MakeVehicleVarFilter(const RoutingModel &routing_model, const PathState *path_state)
Returns a filter checking that vehicle variable domains are respected.
static const int kUnassigned
void AppendDimensionCumulFilters(const std::vector< RoutingDimension * > &dimensions, const RoutingSearchParameters ¶meters, bool filter_objective_cost, bool use_chain_cumul_filter, std::vector< LocalSearchFilterManager::FilterEvent > *filters)
LocalSearchFilter * MakeResourceAssignmentFilter(LocalDimensionCumulOptimizer *lp_optimizer, LocalDimensionCumulOptimizer *mp_optimizer, bool propagate_own_objective_value, bool filter_objective_cost)
bool PropagateLightweightVehicleBreaks(int path, DimensionValues &dimension_values, absl::Span< const std::pair< int64_t, int64_t > > interbreaks)
int MostSignificantBitPosition32(uint32_t n)
IntVarLocalSearchFilter * MakeTypeRegulationsFilter(const RoutingModel &routing_model)
Returns a filter ensuring type regulation constraints are enforced.
IntVarLocalSearchFilter * MakeGlobalLPCumulFilter(GlobalDimensionCumulOptimizer *lp_optimizer, GlobalDimensionCumulOptimizer *mp_optimizer, bool filter_objective_cost)
Returns a filter checking global linear constraints and costs.
IntVarLocalSearchFilter * MakeActiveNodeGroupFilter(const RoutingModel &routing_model)
int64_t CapOpp(int64_t v)
LocalSearchFilter * MakeDimensionFilter(Solver *solver, std::unique_ptr< DimensionChecker > checker, absl::string_view dimension_name)
trees with all degrees equal to
The vehicle routing library lets one model and solve generic vehicle routing problems ranging from th...
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.")
std::vector< VehicleBreak > vehicle_breaks
LocalSearchState::Variable start_cumul
LocalSearchState::Variable end_cumul
std::vector< InterbreakLimit > interbreak_limits
LocalSearchState::Variable total_transit
LocalSearchState::Variable span
static const int64_t kint64max
static const int64_t kint64min