26 #include "absl/memory/memory.h" 42 glop::GlopParameters GetGlopParametersForLocalLP() {
49 glop::GlopParameters GetGlopParametersForGlobalLP() {
55 bool GetCumulBoundsWithOffset(
const RoutingDimension& dimension,
56 int64_t node_index, int64_t cumul_offset,
61 const IntVar& cumul_var = *dimension.CumulVar(node_index);
67 const int64_t first_after_offset =
68 std::max(dimension.GetFirstPossibleGreaterOrEqualValueForNode(
69 node_index, cumul_offset),
83 int64_t GetFirstPossibleValueForCumulWithOffset(
84 const RoutingDimension& dimension, int64_t node_index,
85 int64_t lower_bound_without_offset, int64_t cumul_offset) {
87 dimension.GetFirstPossibleGreaterOrEqualValueForNode(
88 node_index,
CapAdd(lower_bound_without_offset, cumul_offset)),
92 int64_t GetLastPossibleValueForCumulWithOffset(
93 const RoutingDimension& dimension, int64_t node_index,
94 int64_t upper_bound_without_offset, int64_t cumul_offset) {
96 dimension.GetLastPossibleLessOrEqualValueForNode(
97 node_index,
CapAdd(upper_bound_without_offset, cumul_offset)),
106 void StoreVisitedPickupDeliveryPairsOnRoute(
107 const RoutingDimension& dimension,
int vehicle,
108 const std::function<int64_t(int64_t)>& next_accessor,
109 std::vector<int>* visited_pairs,
110 std::vector<std::pair<int64_t, int64_t>>*
111 visited_pickup_delivery_indices_for_pair) {
113 DCHECK_EQ(visited_pickup_delivery_indices_for_pair->size(),
114 dimension.model()->GetPickupAndDeliveryPairs().size());
115 DCHECK(std::all_of(visited_pickup_delivery_indices_for_pair->begin(),
116 visited_pickup_delivery_indices_for_pair->end(),
117 [](std::pair<int64_t, int64_t> p) {
118 return p.first == -1 && p.second == -1;
120 visited_pairs->clear();
121 if (!dimension.HasPickupToDeliveryLimits()) {
124 const RoutingModel&
model = *dimension.model();
126 int64_t node_index =
model.Start(vehicle);
127 while (!
model.IsEnd(node_index)) {
128 const std::vector<std::pair<int, int>>& pickup_index_pairs =
129 model.GetPickupIndexPairs(node_index);
130 const std::vector<std::pair<int, int>>& delivery_index_pairs =
131 model.GetDeliveryIndexPairs(node_index);
132 if (!pickup_index_pairs.empty()) {
135 DCHECK(delivery_index_pairs.empty());
137 (*visited_pickup_delivery_indices_for_pair)[pickup_index_pairs[0].first]
139 visited_pairs->push_back(pickup_index_pairs[0].first);
140 }
else if (!delivery_index_pairs.empty()) {
144 DCHECK_EQ(delivery_index_pairs.size(), 1);
145 const int pair_index = delivery_index_pairs[0].first;
146 std::pair<int64_t, int64_t>& pickup_delivery_index =
147 (*visited_pickup_delivery_indices_for_pair)[pair_index];
148 if (pickup_delivery_index.first < 0) {
151 node_index = next_accessor(node_index);
154 pickup_delivery_index.second = node_index;
156 node_index = next_accessor(node_index);
165 : optimizer_core_(dimension, false) {
169 solver_.resize(vehicles);
170 switch (solver_type) {
173 for (
int vehicle = 0; vehicle < vehicles; ++vehicle) {
177 absl::make_unique<RoutingGlopWrapper>(
false,
parameters);
182 for (
int vehicle = 0; vehicle < vehicles; ++vehicle) {
183 solver_[vehicle] = absl::make_unique<RoutingCPSatWrapper>();
188 LOG(DFATAL) <<
"Unrecognized solver type: " << solver_type;
193 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
194 int64_t* optimal_cost) {
196 solver_[vehicle].get(),
nullptr,
197 nullptr, optimal_cost,
nullptr);
202 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
203 int64_t* optimal_cost_without_transits) {
205 int64_t transit_cost = 0;
207 vehicle, next_accessor, solver_[vehicle].get(),
nullptr,
nullptr, &
cost,
210 optimal_cost_without_transits !=
nullptr) {
211 *optimal_cost_without_transits =
CapSub(
cost, transit_cost);
217 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
218 std::vector<int64_t>* optimal_cumuls,
219 std::vector<int64_t>* optimal_breaks) {
221 vehicle, next_accessor, solver_[vehicle].get(), optimal_cumuls,
222 optimal_breaks,
nullptr,
nullptr);
227 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
228 std::vector<int64_t>* packed_cumuls, std::vector<int64_t>* packed_breaks) {
230 vehicle, next_accessor, solver_[vehicle].get(), packed_cumuls,
234 const int CumulBoundsPropagator::kNoParent = -2;
235 const int CumulBoundsPropagator::kParentToBePropagated = -1;
238 : dimension_(*dimension), num_nodes_(2 * dimension->cumuls().size()) {
239 outgoing_arcs_.resize(num_nodes_);
240 node_in_queue_.resize(num_nodes_,
false);
241 tree_parent_node_of_.resize(num_nodes_, kNoParent);
242 propagated_bounds_.resize(num_nodes_);
243 visited_pickup_delivery_indices_for_pair_.resize(
247 void CumulBoundsPropagator::AddArcs(
int first_index,
int second_index,
250 outgoing_arcs_[PositiveNode(first_index)].push_back(
251 {PositiveNode(second_index), offset});
252 AddNodeToQueue(PositiveNode(first_index));
254 outgoing_arcs_[NegativeNode(second_index)].push_back(
255 {NegativeNode(first_index), offset});
256 AddNodeToQueue(NegativeNode(second_index));
259 bool CumulBoundsPropagator::InitializeArcsAndBounds(
260 const std::function<int64_t(int64_t)>& next_accessor,
261 int64_t cumul_offset) {
264 for (std::vector<ArcInfo>& arcs : outgoing_arcs_) {
268 RoutingModel*
const model = dimension_.
model();
269 std::vector<int64_t>&
lower_bounds = propagated_bounds_;
271 for (
int vehicle = 0; vehicle <
model->vehicles(); vehicle++) {
272 const std::function<int64_t(int64_t, int64_t)>& transit_accessor =
275 int node =
model->Start(vehicle);
277 int64_t cumul_lb, cumul_ub;
278 if (!GetCumulBoundsWithOffset(dimension_, node, cumul_offset, &cumul_lb,
287 if (
model->IsEnd(node)) {
291 const int next = next_accessor(node);
292 const int64_t transit = transit_accessor(node,
next);
293 const IntVar& slack_var = *dimension_.
SlackVar(node);
296 AddArcs(node,
next,
CapAdd(transit, slack_var.Min()));
299 AddArcs(
next, node,
CapSub(-slack_var.Max(), transit));
308 AddArcs(
model->End(vehicle),
model->Start(vehicle), -span_ub);
312 std::vector<int> visited_pairs;
313 StoreVisitedPickupDeliveryPairsOnRoute(
314 dimension_, vehicle, next_accessor, &visited_pairs,
315 &visited_pickup_delivery_indices_for_pair_);
316 for (
int pair_index : visited_pairs) {
317 const int64_t pickup_index =
318 visited_pickup_delivery_indices_for_pair_[pair_index].first;
319 const int64_t delivery_index =
320 visited_pickup_delivery_indices_for_pair_[pair_index].second;
321 visited_pickup_delivery_indices_for_pair_[pair_index] = {-1, -1};
324 if (delivery_index < 0) {
330 pair_index,
model->GetPickupIndexPairs(pickup_index)[0].second,
331 model->GetDeliveryIndexPairs(delivery_index)[0].second);
334 AddArcs(delivery_index, pickup_index, -limit);
339 for (
const RoutingDimension::NodePrecedence& precedence :
341 const int first_index = precedence.first_node;
342 const int second_index = precedence.second_node;
350 AddArcs(first_index, second_index, precedence.offset);
356 bool CumulBoundsPropagator::UpdateCurrentLowerBoundOfNode(
int node,
359 const int cumul_var_index = node / 2;
361 if (node == PositiveNode(cumul_var_index)) {
363 propagated_bounds_[node] = GetFirstPossibleValueForCumulWithOffset(
364 dimension_, cumul_var_index, new_lb, offset);
367 const int64_t new_ub =
CapSub(0, new_lb);
368 propagated_bounds_[node] =
369 CapSub(0, GetLastPossibleValueForCumulWithOffset(
370 dimension_, cumul_var_index, new_ub, offset));
374 const int64_t cumul_lower_bound =
375 propagated_bounds_[PositiveNode(cumul_var_index)];
377 const int64_t negated_cumul_upper_bound =
378 propagated_bounds_[NegativeNode(cumul_var_index)];
380 return CapAdd(negated_cumul_upper_bound, cumul_lower_bound) <= 0;
383 bool CumulBoundsPropagator::DisassembleSubtree(
int source,
int target) {
384 tmp_dfs_stack_.clear();
385 tmp_dfs_stack_.push_back(source);
386 while (!tmp_dfs_stack_.empty()) {
387 const int tail = tmp_dfs_stack_.back();
388 tmp_dfs_stack_.pop_back();
389 for (
const ArcInfo& arc : outgoing_arcs_[
tail]) {
390 const int child_node = arc.head;
391 if (tree_parent_node_of_[child_node] !=
tail)
continue;
392 if (child_node == target)
return false;
393 tree_parent_node_of_[child_node] = kParentToBePropagated;
394 tmp_dfs_stack_.push_back(child_node);
401 const std::function<int64_t(int64_t)>& next_accessor,
402 int64_t cumul_offset) {
403 tree_parent_node_of_.assign(num_nodes_, kNoParent);
404 DCHECK(std::none_of(node_in_queue_.begin(), node_in_queue_.end(),
405 [](
bool b) {
return b; }));
406 DCHECK(bf_queue_.empty());
408 if (!InitializeArcsAndBounds(next_accessor, cumul_offset)) {
409 return CleanupAndReturnFalse();
412 std::vector<int64_t>& current_lb = propagated_bounds_;
415 while (!bf_queue_.empty()) {
416 const int node = bf_queue_.front();
417 bf_queue_.pop_front();
418 node_in_queue_[node] =
false;
420 if (tree_parent_node_of_[node] == kParentToBePropagated) {
427 for (
const ArcInfo& arc : outgoing_arcs_[node]) {
430 const int64_t induced_lb =
435 const int head_node = arc.head;
436 if (induced_lb <= current_lb[head_node]) {
441 if (!UpdateCurrentLowerBoundOfNode(head_node, induced_lb, cumul_offset) ||
442 !DisassembleSubtree(head_node, node)) {
445 return CleanupAndReturnFalse();
448 tree_parent_node_of_[head_node] = node;
449 AddNodeToQueue(head_node);
457 : dimension_(dimension),
458 visited_pickup_delivery_indices_for_pair_(
459 dimension->
model()->GetPickupAndDeliveryPairs().size(), {-1, -1}) {
460 if (use_precedence_propagator) {
461 propagator_ = absl::make_unique<CumulBoundsPropagator>(dimension);
468 vehicle_to_all_break_variables_offset_.reserve(num_vehicles);
469 int num_break_vars = 0;
470 for (
int vehicle = 0; vehicle < num_vehicles; ++vehicle) {
471 vehicle_to_all_break_variables_offset_.push_back(num_break_vars);
473 num_break_vars += 2 * intervals.size();
475 all_break_variables_.resize(num_break_vars, -1);
480 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
482 std::vector<int64_t>* break_values, int64_t*
cost, int64_t* transit_cost,
484 InitOptimizer(solver);
487 DCHECK(propagator_ ==
nullptr);
490 const bool optimize_vehicle_costs =
491 (cumul_values !=
nullptr ||
cost !=
nullptr) &&
492 (!
model->IsEnd(next_accessor(
model->Start(vehicle))) ||
493 model->AreEmptyRouteCostsConsideredForVehicle(vehicle));
494 const int64_t cumul_offset =
496 int64_t cost_offset = 0;
497 if (!SetRouteCumulConstraints(vehicle, next_accessor, cumul_offset,
498 optimize_vehicle_costs, solver, transit_cost,
508 SetValuesFromLP(current_route_cumul_variables_, cumul_offset, solver,
510 SetValuesFromLP(current_route_break_variables_, cumul_offset, solver,
512 if (
cost !=
nullptr) {
523 const std::function<int64_t(int64_t)>& next_accessor,
525 std::vector<int64_t>* break_values, int64_t*
cost, int64_t* transit_cost,
527 InitOptimizer(solver);
531 const bool optimize_costs = (cumul_values !=
nullptr) || (
cost !=
nullptr);
532 bool has_vehicles_being_optimized =
false;
536 if (propagator_ !=
nullptr &&
537 !propagator_->PropagateCumulBounds(next_accessor, cumul_offset)) {
541 int64_t total_transit_cost = 0;
542 int64_t total_cost_offset = 0;
544 int num_needed_resources = 0;
545 int num_available_resources =
model->vehicles();
546 const auto& resource_groups =
model->GetResourceGroups();
547 for (
int rg_index :
model->GetDimensionResourceGroupIndices(dimension_)) {
548 num_available_resources =
549 std::min(num_available_resources, resource_groups[rg_index]->Size());
552 for (
int vehicle = 0; vehicle <
model->vehicles(); vehicle++) {
553 int64_t route_transit_cost = 0;
554 int64_t route_cost_offset = 0;
555 const bool vehicle_is_used =
556 !
model->IsEnd(next_accessor(
model->Start(vehicle))) ||
557 model->AreEmptyRouteCostsConsideredForVehicle(vehicle);
558 if (vehicle_is_used && ++num_needed_resources > num_available_resources) {
561 const bool optimize_vehicle_costs = optimize_costs && vehicle_is_used;
562 if (!SetRouteCumulConstraints(vehicle, next_accessor, cumul_offset,
563 optimize_vehicle_costs, solver,
564 &route_transit_cost, &route_cost_offset)) {
567 total_transit_cost =
CapAdd(total_transit_cost, route_transit_cost);
568 total_cost_offset =
CapAdd(total_cost_offset, route_cost_offset);
569 has_vehicles_being_optimized |= optimize_vehicle_costs;
571 if (transit_cost !=
nullptr) {
572 *transit_cost = total_transit_cost;
575 SetGlobalConstraints(next_accessor, cumul_offset,
576 has_vehicles_being_optimized, solver);
586 SetValuesFromLP(index_to_cumul_variable_, cumul_offset, solver, cumul_values);
587 SetValuesFromLP(all_break_variables_, cumul_offset, solver, break_values);
589 if (
cost !=
nullptr) {
600 const std::function<int64_t(int64_t)>& next_accessor,
602 std::vector<int64_t>* break_values) {
607 nullptr,
nullptr, &
cost,
613 std::iota(vehicles.begin(), vehicles.end(), 0);
623 SetValuesFromLP(index_to_cumul_variable_, global_offset, solver,
625 SetValuesFromLP(all_break_variables_, global_offset, solver, break_values);
632 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
634 std::vector<int64_t>* break_values) {
649 const int64_t local_offset =
651 SetValuesFromLP(current_route_cumul_variables_, local_offset, solver,
653 SetValuesFromLP(current_route_break_variables_, local_offset, solver,
677 for (
int vehicle : vehicles) {
679 index_to_cumul_variable_[
model->End(vehicle)], 1);
690 for (
int vehicle : vehicles) {
691 const int end_cumul_var = index_to_cumul_variable_[
model->End(vehicle)];
699 index_to_cumul_variable_[
model->Start(vehicle)], -1);
704 void DimensionCumulOptimizerCore::InitOptimizer(
705 RoutingLinearSolverWrapper* solver) {
707 index_to_cumul_variable_.assign(dimension_->
cumuls().size(), -1);
708 max_end_cumul_ = solver->CreateNewPositiveVariable();
709 min_start_cumul_ = solver->CreateNewPositiveVariable();
712 bool DimensionCumulOptimizerCore::ComputeRouteCumulBounds(
713 const std::vector<int64_t>& route,
714 const std::vector<int64_t>& fixed_transits, int64_t cumul_offset) {
715 const int route_size = route.size();
716 current_route_min_cumuls_.resize(route_size);
717 current_route_max_cumuls_.resize(route_size);
718 if (propagator_ !=
nullptr) {
719 for (
int pos = 0; pos < route_size; pos++) {
720 const int64_t node = route[pos];
721 current_route_min_cumuls_[pos] = propagator_->CumulMin(node);
722 DCHECK_GE(current_route_min_cumuls_[pos], 0);
723 current_route_max_cumuls_[pos] = propagator_->CumulMax(node);
724 DCHECK_GE(current_route_max_cumuls_[pos], current_route_min_cumuls_[pos]);
730 for (
int pos = 0; pos < route_size; ++pos) {
731 if (!GetCumulBoundsWithOffset(*dimension_, route[pos], cumul_offset,
732 ¤t_route_min_cumuls_[pos],
733 ¤t_route_max_cumuls_[pos])) {
740 for (
int pos = 1; pos < route_size; ++pos) {
741 const int64_t slack_min = dimension_->
SlackVar(route[pos - 1])->
Min();
742 current_route_min_cumuls_[pos] =
std::max(
743 current_route_min_cumuls_[pos],
745 CapAdd(current_route_min_cumuls_[pos - 1], fixed_transits[pos - 1]),
747 current_route_min_cumuls_[pos] = GetFirstPossibleValueForCumulWithOffset(
748 *dimension_, route[pos], current_route_min_cumuls_[pos], cumul_offset);
749 if (current_route_min_cumuls_[pos] > current_route_max_cumuls_[pos]) {
754 for (
int pos = route_size - 2; pos >= 0; --pos) {
757 if (current_route_max_cumuls_[pos + 1] <
759 const int64_t slack_min = dimension_->
SlackVar(route[pos])->
Min();
760 current_route_max_cumuls_[pos] =
std::min(
761 current_route_max_cumuls_[pos],
763 CapSub(current_route_max_cumuls_[pos + 1], fixed_transits[pos]),
765 current_route_max_cumuls_[pos] = GetLastPossibleValueForCumulWithOffset(
766 *dimension_, route[pos], current_route_max_cumuls_[pos],
768 if (current_route_max_cumuls_[pos] < current_route_min_cumuls_[pos]) {
776 bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
777 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
778 int64_t cumul_offset,
bool optimize_costs,
779 RoutingLinearSolverWrapper* solver, int64_t* route_transit_cost,
780 int64_t* route_cost_offset) {
781 RoutingModel*
const model = dimension_->
model();
783 std::vector<int64_t> path;
785 int node =
model->Start(vehicle);
786 path.push_back(node);
787 while (!
model->IsEnd(node)) {
788 node = next_accessor(node);
789 path.push_back(node);
793 const int path_size = path.size();
795 std::vector<int64_t> fixed_transit(path_size - 1);
797 const std::function<int64_t(int64_t, int64_t)>& transit_accessor =
799 for (
int pos = 1; pos < path_size; ++pos) {
800 fixed_transit[pos - 1] = transit_accessor(path[pos - 1], path[pos]);
804 if (!ComputeRouteCumulBounds(path, fixed_transit, cumul_offset)) {
810 std::vector<int>& lp_cumuls = current_route_cumul_variables_;
811 lp_cumuls.assign(path_size, -1);
812 for (
int pos = 0; pos < path_size; ++pos) {
813 const int lp_cumul = solver->CreateNewPositiveVariable();
814 index_to_cumul_variable_[path[pos]] = lp_cumul;
815 lp_cumuls[pos] = lp_cumul;
816 if (!solver->SetVariableBounds(lp_cumul, current_route_min_cumuls_[pos],
817 current_route_max_cumuls_[pos])) {
820 const SortedDisjointIntervalList& forbidden =
822 if (forbidden.NumIntervals() > 0) {
823 std::vector<int64_t> starts;
824 std::vector<int64_t> ends;
825 for (
const ClosedInterval
interval :
827 path[pos],
CapAdd(current_route_min_cumuls_[pos], cumul_offset),
828 CapAdd(current_route_max_cumuls_[pos], cumul_offset))) {
832 solver->SetVariableDisjointBounds(lp_cumul, starts, ends);
836 std::vector<int> lp_slacks(path_size - 1, -1);
837 for (
int pos = 0; pos < path_size - 1; ++pos) {
838 const IntVar* cp_slack = dimension_->
SlackVar(path[pos]);
839 lp_slacks[pos] = solver->CreateNewPositiveVariable();
840 if (!solver->SetVariableBounds(lp_slacks[pos], cp_slack->Min(),
850 for (
int pos = 0; pos < path_size - 1; ++pos) {
852 solver->CreateNewConstraint(fixed_transit[pos], fixed_transit[pos]);
853 solver->SetCoefficient(
ct, lp_cumuls[pos + 1], 1);
854 solver->SetCoefficient(
ct, lp_cumuls[pos], -1);
855 solver->SetCoefficient(
ct, lp_slacks[pos], -1);
857 if (route_cost_offset !=
nullptr) *route_cost_offset = 0;
858 if (optimize_costs) {
860 for (
int pos = 0; pos < path_size; ++pos) {
864 if (
coef == 0)
continue;
866 if (
bound < cumul_offset && route_cost_offset !=
nullptr) {
868 *route_cost_offset =
CapAdd(*route_cost_offset,
872 if (current_route_max_cumuls_[pos] <=
bound) {
876 const int soft_ub_diff = solver->CreateNewPositiveVariable();
877 solver->SetObjectiveCoefficient(soft_ub_diff,
coef);
879 const int ct = solver->CreateNewConstraint(
881 solver->SetCoefficient(
ct, lp_cumuls[pos], 1);
882 solver->SetCoefficient(
ct, soft_ub_diff, -1);
885 for (
int pos = 0; pos < path_size; ++pos) {
889 if (
coef == 0)
continue;
890 const int64_t
bound = std::max<int64_t>(
893 if (current_route_min_cumuls_[pos] >=
bound) {
897 const int soft_lb_diff = solver->CreateNewPositiveVariable();
898 solver->SetObjectiveCoefficient(soft_lb_diff,
coef);
900 const int ct = solver->CreateNewConstraint(
902 solver->SetCoefficient(
ct, lp_cumuls[pos], 1);
903 solver->SetCoefficient(
ct, soft_lb_diff, 1);
907 std::vector<int> visited_pairs;
908 StoreVisitedPickupDeliveryPairsOnRoute(
909 *dimension_, vehicle, next_accessor, &visited_pairs,
910 &visited_pickup_delivery_indices_for_pair_);
911 for (
int pair_index : visited_pairs) {
912 const int64_t pickup_index =
913 visited_pickup_delivery_indices_for_pair_[pair_index].first;
914 const int64_t delivery_index =
915 visited_pickup_delivery_indices_for_pair_[pair_index].second;
916 visited_pickup_delivery_indices_for_pair_[pair_index] = {-1, -1};
919 if (delivery_index < 0) {
925 pair_index,
model->GetPickupIndexPairs(pickup_index)[0].second,
926 model->GetDeliveryIndexPairs(delivery_index)[0].second);
929 const int ct = solver->CreateNewConstraint(
931 solver->SetCoefficient(
ct, index_to_cumul_variable_[delivery_index], 1);
932 solver->SetCoefficient(
ct, index_to_cumul_variable_[pickup_index], -1);
940 const int ct = solver->CreateNewConstraint(
942 solver->SetCoefficient(
ct, lp_cumuls.back(), 1);
943 solver->SetCoefficient(
ct, lp_cumuls.front(), -1);
946 const int64_t span_cost_coef =
948 if (optimize_costs && span_cost_coef > 0) {
949 solver->SetObjectiveCoefficient(lp_cumuls.back(), span_cost_coef);
950 solver->SetObjectiveCoefficient(lp_cumuls.front(), -span_cost_coef);
954 SimpleBoundCosts::BoundCost bound_cost =
957 bound_cost.cost > 0) {
958 const int span_violation = solver->CreateNewPositiveVariable();
960 const int violation = solver->CreateNewConstraint(
962 solver->SetCoefficient(violation, lp_cumuls.back(), 1.0);
963 solver->SetCoefficient(violation, lp_cumuls.front(), -1.0);
964 solver->SetCoefficient(violation, span_violation, -1.0);
966 solver->SetObjectiveCoefficient(span_violation, bound_cost.cost);
974 solver->SetCoefficient(
ct, min_start_cumul_, 1);
975 solver->SetCoefficient(
ct, lp_cumuls.front(), -1);
978 solver->SetCoefficient(
ct, max_end_cumul_, 1);
979 solver->SetCoefficient(
ct, lp_cumuls.back(), -1);
982 if (route_transit_cost !=
nullptr) {
983 if (optimize_costs && span_cost_coef > 0) {
984 const int64_t total_fixed_transit = std::accumulate(
985 fixed_transit.begin(), fixed_transit.end(), 0,
CapAdd);
986 *route_transit_cost =
CapProd(total_fixed_transit, span_cost_coef);
988 *route_transit_cost = 0;
996 current_route_break_variables_.clear();
998 const std::vector<IntervalVar*>& breaks =
1000 const int num_breaks = breaks.size();
1004 if (num_breaks == 0) {
1006 for (
const auto& distance_duration :
1008 maximum_route_span =
1009 std::min(maximum_route_span, distance_duration.first);
1012 const int ct = solver->CreateNewConstraint(
1014 solver->SetCoefficient(
ct, lp_cumuls.back(), 1);
1015 solver->SetCoefficient(
ct, lp_cumuls.front(), -1);
1022 std::vector<int64_t> pre_travel(path_size - 1, 0);
1023 std::vector<int64_t> post_travel(path_size - 1, 0);
1025 const int pre_travel_index =
1027 if (pre_travel_index != -1) {
1031 const int post_travel_index =
1033 if (post_travel_index != -1) {
1042 std::vector<int> lp_break_start;
1043 std::vector<int> lp_break_duration;
1044 std::vector<int> lp_break_end;
1045 if (solver->IsCPSATSolver()) {
1046 lp_break_start.resize(num_breaks, -1);
1047 lp_break_duration.resize(num_breaks, -1);
1048 lp_break_end.resize(num_breaks, -1);
1051 std::vector<int> slack_exact_lower_bound_ct(path_size - 1, -1);
1052 std::vector<int> slack_linear_lower_bound_ct(path_size - 1, -1);
1054 const int64_t vehicle_start_min = current_route_min_cumuls_.front();
1055 const int64_t vehicle_start_max = current_route_max_cumuls_.front();
1056 const int64_t vehicle_end_min = current_route_min_cumuls_.back();
1057 const int64_t vehicle_end_max = current_route_max_cumuls_.back();
1058 const int all_break_variables_offset =
1059 vehicle_to_all_break_variables_offset_[vehicle];
1060 for (
int br = 0; br < num_breaks; ++br) {
1061 const IntervalVar& break_var = *breaks[br];
1062 if (!break_var.MustBePerformed())
continue;
1063 const int64_t break_start_min =
CapSub(break_var.StartMin(), cumul_offset);
1064 const int64_t break_start_max =
CapSub(break_var.StartMax(), cumul_offset);
1065 const int64_t break_end_min =
CapSub(break_var.EndMin(), cumul_offset);
1066 const int64_t break_end_max =
CapSub(break_var.EndMax(), cumul_offset);
1067 const int64_t break_duration_min = break_var.DurationMin();
1068 const int64_t break_duration_max = break_var.DurationMax();
1071 if (solver->IsCPSATSolver()) {
1072 if (break_end_max <= vehicle_start_min ||
1073 vehicle_end_max <= break_start_min) {
1074 all_break_variables_[all_break_variables_offset + 2 * br] = -1;
1075 all_break_variables_[all_break_variables_offset + 2 * br + 1] = -1;
1076 current_route_break_variables_.push_back(-1);
1077 current_route_break_variables_.push_back(-1);
1080 lp_break_start[br] =
1081 solver->AddVariable(break_start_min, break_start_max);
1082 lp_break_end[br] = solver->AddVariable(break_end_min, break_end_max);
1083 lp_break_duration[br] =
1084 solver->AddVariable(break_duration_min, break_duration_max);
1086 solver->AddLinearConstraint(0, 0,
1087 {{lp_break_end[br], 1},
1088 {lp_break_start[br], -1},
1089 {lp_break_duration[br], -1}});
1091 all_break_variables_[all_break_variables_offset + 2 * br] =
1093 all_break_variables_[all_break_variables_offset + 2 * br + 1] =
1095 current_route_break_variables_.push_back(lp_break_start[br]);
1096 current_route_break_variables_.push_back(lp_break_end[br]);
1098 if (break_end_min <= vehicle_start_max ||
1099 vehicle_end_min <= break_start_max) {
1107 const int break_in_one_slack_ct = solver->CreateNewConstraint(1, 1);
1109 if (solver->IsCPSATSolver()) {
1111 if (break_end_min <= vehicle_start_max) {
1112 const int ct = solver->AddLinearConstraint(
1114 {{lp_cumuls.front(), 1}, {lp_break_end[br], -1}});
1115 const int break_is_before_route = solver->AddVariable(0, 1);
1116 solver->SetEnforcementLiteral(
ct, break_is_before_route);
1117 solver->SetCoefficient(break_in_one_slack_ct, break_is_before_route, 1);
1120 if (vehicle_end_min <= break_start_max) {
1121 const int ct = solver->AddLinearConstraint(
1123 {{lp_break_start[br], 1}, {lp_cumuls.back(), -1}});
1124 const int break_is_after_route = solver->AddVariable(0, 1);
1125 solver->SetEnforcementLiteral(
ct, break_is_after_route);
1126 solver->SetCoefficient(break_in_one_slack_ct, break_is_after_route, 1);
1131 for (
int pos = 0; pos < path_size - 1; ++pos) {
1134 const int64_t slack_start_min =
1135 CapAdd(current_route_min_cumuls_[pos], pre_travel[pos]);
1136 if (slack_start_min > break_start_max)
break;
1137 const int64_t slack_end_max =
1138 CapSub(current_route_max_cumuls_[pos + 1], post_travel[pos]);
1139 if (break_end_min > slack_end_max)
continue;
1140 const int64_t slack_duration_max =
1142 current_route_min_cumuls_[pos]),
1143 fixed_transit[pos]),
1145 if (slack_duration_max < break_duration_min)
continue;
1152 const int break_in_slack = solver->AddVariable(0, 1);
1153 solver->SetCoefficient(break_in_one_slack_ct, break_in_slack, 1);
1154 if (slack_linear_lower_bound_ct[pos] == -1) {
1155 slack_linear_lower_bound_ct[pos] = solver->AddLinearConstraint(
1158 solver->SetCoefficient(slack_linear_lower_bound_ct[pos], break_in_slack,
1159 break_duration_min);
1160 if (solver->IsCPSATSolver()) {
1165 const int break_duration_in_slack =
1166 solver->AddVariable(0, slack_duration_max);
1167 solver->AddProductConstraint(break_duration_in_slack,
1168 {break_in_slack, lp_break_duration[br]});
1169 if (slack_exact_lower_bound_ct[pos] == -1) {
1170 slack_exact_lower_bound_ct[pos] = solver->AddLinearConstraint(
1173 solver->SetCoefficient(slack_exact_lower_bound_ct[pos],
1174 break_duration_in_slack, 1);
1177 const int break_start_after_current_ct = solver->AddLinearConstraint(
1179 {{lp_break_start[br], 1}, {lp_cumuls[pos], -1}});
1180 solver->SetEnforcementLiteral(break_start_after_current_ct,
1183 const int break_ends_before_next_ct = solver->AddLinearConstraint(
1185 {{lp_cumuls[pos + 1], 1}, {lp_break_end[br], -1}});
1186 solver->SetEnforcementLiteral(break_ends_before_next_ct,
1192 if (!solver->IsCPSATSolver())
return true;
1198 if (!
interval->MustBePerformed())
return true;
1201 for (
int br = 1; br < num_breaks; ++br) {
1202 solver->AddLinearConstraint(
1204 {{lp_break_end[br - 1], -1}, {lp_break_start[br], 1}});
1207 for (
const auto& distance_duration :
1209 const int64_t limit = distance_duration.first;
1210 const int64_t min_break_duration = distance_duration.second;
1238 int previous_cover = solver->AddVariable(
CapAdd(vehicle_start_min, limit),
1239 CapAdd(vehicle_start_max, limit));
1240 solver->AddLinearConstraint(limit, limit,
1241 {{previous_cover, 1}, {lp_cumuls.front(), -1}});
1242 for (
int br = 0; br < num_breaks; ++br) {
1243 if (lp_break_start[br] == -1)
continue;
1244 const int64_t break_end_min =
CapSub(breaks[br]->EndMin(), cumul_offset);
1245 const int64_t break_end_max =
CapSub(breaks[br]->EndMax(), cumul_offset);
1248 const int break_is_eligible = solver->AddVariable(0, 1);
1249 const int break_is_not_eligible = solver->AddVariable(0, 1);
1251 solver->AddLinearConstraint(
1252 1, 1, {{break_is_eligible, 1}, {break_is_not_eligible, 1}});
1253 const int positive_ct = solver->AddLinearConstraint(
1255 {{lp_break_end[br], 1}, {lp_break_start[br], -1}});
1256 solver->SetEnforcementLiteral(positive_ct, break_is_eligible);
1257 const int negative_ct = solver->AddLinearConstraint(
1259 {{lp_break_end[br], 1}, {lp_break_start[br], -1}});
1260 solver->SetEnforcementLiteral(negative_ct, break_is_not_eligible);
1267 const int break_cover = solver->AddVariable(
1270 const int limit_cover_ct = solver->AddLinearConstraint(
1271 limit, limit, {{break_cover, 1}, {lp_break_end[br], -1}});
1272 solver->SetEnforcementLiteral(limit_cover_ct, break_is_eligible);
1273 const int empty_cover_ct = solver->AddLinearConstraint(
1274 CapAdd(vehicle_start_min, limit),
CapAdd(vehicle_start_min, limit),
1275 {{break_cover, 1}});
1276 solver->SetEnforcementLiteral(empty_cover_ct, break_is_not_eligible);
1279 solver->AddVariable(
CapAdd(vehicle_start_min, limit),
1281 solver->AddMaximumConstraint(cover, {previous_cover, break_cover});
1284 const int route_end_is_not_covered = solver->AddReifiedLinearConstraint(
1286 {{lp_cumuls.back(), 1}, {previous_cover, -1}});
1287 const int break_start_cover_ct = solver->AddLinearConstraint(
1289 {{previous_cover, 1}, {lp_break_start[br], -1}});
1290 solver->SetEnforcementLiteral(break_start_cover_ct,
1291 route_end_is_not_covered);
1293 previous_cover = cover;
1296 {{previous_cover, 1}, {lp_cumuls.back(), -1}});
1302 void DimensionCumulOptimizerCore::SetGlobalConstraints(
1303 const std::function<int64_t(int64_t)>& next_accessor, int64_t cumul_offset,
1304 bool optimize_costs, RoutingLinearSolverWrapper* solver) {
1308 if (optimize_costs && global_span_coeff > 0) {
1309 solver->SetObjectiveCoefficient(max_end_cumul_, global_span_coeff);
1310 solver->SetObjectiveCoefficient(min_start_cumul_, -global_span_coeff);
1314 for (
const RoutingDimension::NodePrecedence& precedence :
1316 const int first_cumul_var = index_to_cumul_variable_[precedence.first_node];
1317 const int second_cumul_var =
1318 index_to_cumul_variable_[precedence.second_node];
1319 if (first_cumul_var < 0 || second_cumul_var < 0) {
1324 DCHECK_NE(first_cumul_var, second_cumul_var)
1325 <<
"Dimension " << dimension_->
name()
1326 <<
" has a self-precedence on node " << precedence.first_node <<
".";
1329 const int ct = solver->CreateNewConstraint(
1331 solver->SetCoefficient(
ct, second_cumul_var, 1);
1332 solver->SetCoefficient(
ct, first_cumul_var, -1);
1335 const RoutingModel&
model = *dimension_->
model();
1336 if (!solver->IsCPSATSolver()) {
1342 using ResourceGroup = RoutingModel::ResourceGroup;
1343 const auto& resource_groups =
model.GetResourceGroups();
1344 for (
int rg_index :
model.GetDimensionResourceGroupIndices(dimension_)) {
1353 const std::vector<ResourceGroup::Resource>& resources =
1354 resource_groups[rg_index]->GetResources();
1356 static const int kNoConstraint = -1;
1359 std::vector<int> vehicle_constraints(
model.vehicles(), kNoConstraint);
1360 for (
int v = 0; v <
model.vehicles(); v++) {
1361 if (
model.IsEnd(next_accessor(
model.Start(v))) &&
1362 !
model.AreEmptyRouteCostsConsideredForVehicle(v)) {
1366 vehicle_constraints[v] = solver->CreateNewConstraint(1, 1);
1370 std::vector<int> resource_constraints(resources.size(), kNoConstraint);
1371 for (
int r = 0; r < resources.size(); r++) {
1372 const ResourceGroup::Attributes& attributes =
1373 resources[r].GetDimensionAttributes(dimension_);
1374 if (attributes.start_domain().Max() < cumul_offset ||
1375 attributes.end_domain().Max() < cumul_offset) {
1381 resource_constraints[r] = solver->CreateNewConstraint(0, 1);
1388 for (
int r = 0; r < resources.size(); r++) {
1389 if (resource_constraints[r] == kNoConstraint)
continue;
1390 const ResourceGroup::Attributes& attributes =
1391 resources[r].GetDimensionAttributes(dimension_);
1393 if (vehicle_constraints[v] == kNoConstraint)
continue;
1395 const int assign_r_to_v = solver->AddVariable(0, 1);
1396 solver->SetCoefficient(vehicle_constraints[v], assign_r_to_v, 1);
1397 solver->SetCoefficient(resource_constraints[r], assign_r_to_v, 1);
1399 const auto& add_domain_constraint =
1400 [&solver, cumul_offset, assign_r_to_v](
const Domain& domain,
1401 int cumul_variable) {
1405 const int64_t cumul_min =
1406 std::max<int64_t>(
CapSub(domain.Min(), cumul_offset), 0);
1407 const int64_t cumul_max =
1410 :
CapSub(domain.Max(), cumul_offset);
1412 const int cumul_constraint = solver->AddLinearConstraint(
1413 cumul_min, cumul_max, {{cumul_variable, 1}});
1414 solver->SetEnforcementLiteral(cumul_constraint, assign_r_to_v);
1416 add_domain_constraint(attributes.start_domain(),
1417 index_to_cumul_variable_[
model.Start(v)]);
1418 add_domain_constraint(attributes.end_domain(),
1419 index_to_cumul_variable_[
model.End(v)]);
1425 void DimensionCumulOptimizerCore::SetValuesFromLP(
1426 const std::vector<int>& lp_variables, int64_t offset,
1427 RoutingLinearSolverWrapper* solver, std::vector<int64_t>* lp_values) {
1428 if (lp_values ==
nullptr)
return;
1430 for (
int i = 0; i < lp_variables.size(); i++) {
1431 const int cumul_var = lp_variables[i];
1432 if (cumul_var < 0)
continue;
1433 const double lp_value_double = solver->GetValue(cumul_var);
1434 const int64_t lp_value_int64 =
1438 (*lp_values)[i] =
CapAdd(lp_value_int64, offset);
1445 : optimizer_core_(dimension,
1447 !dimension->GetNodePrecedences().empty()) {
1448 switch (solver_type) {
1450 solver_ = absl::make_unique<RoutingGlopWrapper>(
1454 GetGlopParametersForGlobalLP());
1458 solver_ = absl::make_unique<RoutingCPSatWrapper>();
1462 LOG(DFATAL) <<
"Unrecognized solver type: " << solver_type;
1468 const std::function<int64_t(int64_t)>& next_accessor,
1469 int64_t* optimal_cost_without_transits) {
1471 int64_t transit_cost = 0;
1473 next_accessor, solver_.get(),
nullptr,
nullptr, &
cost, &transit_cost);
1475 optimal_cost_without_transits !=
nullptr) {
1476 *optimal_cost_without_transits =
CapSub(
cost, transit_cost);
1482 const std::function<int64_t(int64_t)>& next_accessor,
1483 std::vector<int64_t>* optimal_cumuls,
1484 std::vector<int64_t>* optimal_breaks) {
1485 return optimizer_core_.
Optimize(next_accessor, solver_.get(), optimal_cumuls,
1486 optimal_breaks,
nullptr,
nullptr);
1490 const std::function<int64_t(int64_t)>& next_accessor,
1491 std::vector<int64_t>* packed_cumuls, std::vector<int64_t>* packed_breaks) {
1493 packed_cumuls, packed_breaks);
int64_t GetCumulVarSoftUpperBoundCoefficient(int64_t index) const
Returns the cost coefficient of the soft upper bound of a cumul variable for a given variable index.
DimensionSchedulingStatus ComputeRouteCumulCostWithoutFixedTransits(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, int64_t *optimal_cost_without_transits)
DimensionSchedulingStatus ComputePackedCumuls(const std::function< int64_t(int64_t)> &next_accessor, std::vector< int64_t > *packed_cumuls, std::vector< int64_t > *packed_breaks)
int64_t CapSub(int64_t x, int64_t y)
int64_t GetCumulVarSoftUpperBound(int64_t index) const
Returns the soft upper bound of a cumul variable for a given variable index.
const std::vector< IntVar * > & cumuls() const
Like CumulVar(), TransitVar(), SlackVar() but return the whole variable vectors instead (indexed by i...
int64_t GetLocalOptimizerOffsetForVehicle(int vehicle) const
bool HasBreakConstraints() const
Returns true if any break interval or break distance was defined.
int GetPreTravelEvaluatorOfVehicle(int vehicle) const
!defined(SWIGPYTHON)
virtual double GetValue(int index) const =0
std::vector< double > lower_bounds
int64_t GetCumulVarSoftLowerBoundCoefficient(int64_t index) const
Returns the cost coefficient of the soft lower bound of a cumul variable for a given variable index.
virtual bool SetVariableBounds(int index, int64_t lower_bound, int64_t upper_bound)=0
SortedDisjointIntervalList GetAllowedIntervalsInRange(int64_t index, int64_t min_value, int64_t max_value) const
Returns allowed intervals for a given node in a given interval.
int64_t global_span_cost_coefficient() const
RoutingModel * model() const
Returns the model on which the dimension was created.
virtual void SetObjectiveCoefficient(int index, double coefficient)=0
virtual int64_t Min() const =0
int64_t CapProd(int64_t x, int64_t y)
DimensionSchedulingStatus
Dimensions represent quantities accumulated at nodes along the routes.
#define DCHECK_GT(val1, val2)
DimensionSchedulingStatus ComputePackedRouteCumuls(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, std::vector< int64_t > *packed_cumuls, std::vector< int64_t > *packed_breaks)
int64_t GetSpanUpperBoundForVehicle(int vehicle) const
static int64_t FastInt64Round(double x)
const IndexPairs & GetPickupAndDeliveryPairs() const
Returns pickup and delivery pairs currently in the model.
static Domain AllValues()
Returns the full domain Int64.
void FillPathEvaluation(const std::vector< int64_t > &path, const RoutingModel::TransitCallback2 &evaluator, std::vector< int64_t > *values)
int64_t GetPickupToDeliveryLimitForPair(int pair_index, int pickup, int delivery) const
DimensionSchedulingStatus OptimizeAndPackSingleRoute(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, RoutingLinearSolverWrapper *solver, std::vector< int64_t > *cumul_values, std::vector< int64_t > *break_values)
const RoutingDimension & dimension() const
int64_t GetCumulVarSoftLowerBound(int64_t index) const
Returns the soft lower bound of a cumul variable for a given variable index.
DimensionSchedulingStatus ComputeCumulCostWithoutFixedTransits(const std::function< int64_t(int64_t)> &next_accessor, int64_t *optimal_cost_without_transits)
virtual int64_t GetObjectiveValue() const =0
const std::string & name() const
Returns the name of the dimension.
int64_t CapAdd(int64_t x, int64_t y)
#define DCHECK_NE(val1, val2)
int64_t GetSpanCostCoefficientForVehicle(int vehicle) const
DimensionSchedulingStatus ComputeCumuls(const std::function< int64_t(int64_t)> &next_accessor, std::vector< int64_t > *optimal_cumuls, std::vector< int64_t > *optimal_breaks)
const std::vector< SortedDisjointIntervalList > & forbidden_intervals() const
Returns forbidden intervals for each node.
virtual void AddObjectiveConstraint()=0
bool HasCumulVarSoftUpperBound(int64_t index) const
Returns true if a soft upper bound has been set for a given variable index.
DimensionSchedulingStatus OptimizeAndPack(const std::function< int64_t(int64_t)> &next_accessor, RoutingLinearSolverWrapper *solver, std::vector< int64_t > *cumul_values, std::vector< int64_t > *break_values)
#define DCHECK_GE(val1, val2)
const RoutingModel::TransitCallback2 & transit_evaluator(int vehicle) const
Returns the callback evaluating the transit value between two node indices for a given vehicle.
DimensionSchedulingStatus OptimizeSingleRoute(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, RoutingLinearSolverWrapper *solver, std::vector< int64_t > *cumul_values, std::vector< int64_t > *break_values, int64_t *cost, int64_t *transit_cost, bool clear_lp=true)
SimpleBoundCosts::BoundCost GetSoftSpanUpperBoundForVehicle(int vehicle) const
bool HasCumulVarSoftLowerBound(int64_t index) const
Returns true if a soft lower bound has been set for a given variable index.
static constexpr SchedulingSolver GLOP
#define DCHECK(condition)
const std::vector< std::pair< int64_t, int64_t > > & GetBreakDistanceDurationOfVehicle(int vehicle) const
Returns the pairs (distance, duration) specified by break distance constraints.
int GetPostTravelEvaluatorOfVehicle(int vehicle) const
DimensionSchedulingStatus ComputeRouteCumuls(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, std::vector< int64_t > *optimal_cumuls, std::vector< int64_t > *optimal_breaks)
int vehicles() const
Returns the number of vehicle routes in the model.
DimensionSchedulingStatus Optimize(const std::function< int64_t(int64_t)> &next_accessor, RoutingLinearSolverWrapper *solver, std::vector< int64_t > *cumul_values, std::vector< int64_t > *break_values, int64_t *cost, int64_t *transit_cost, bool clear_lp=true)
#define DCHECK_EQ(val1, val2)
RoutingSearchParameters_SchedulingSolver
const std::vector< NodePrecedence > & GetNodePrecedences() const
const RoutingDimension * dimension() const
DimensionCumulOptimizerCore(const RoutingDimension *dimension, bool use_precedence_propagator)
LocalDimensionCumulOptimizer(const RoutingDimension *dimension, RoutingSearchParameters::SchedulingSolver solver_type)
IntVar * SlackVar(int64_t index) const
Collection of objects used to extend the Constraint Solver library.
bool PropagateCumulBounds(const std::function< int64_t(int64_t)> &next_accessor, int64_t cumul_offset)
int64_t GetGlobalOptimizerOffset() const
const std::vector< IntervalVar * > & GetBreakIntervalsOfVehicle(int vehicle) const
Returns the break intervals set by SetBreakIntervalsOfVehicle().
virtual DimensionSchedulingStatus Solve(absl::Duration duration_limit)=0
const RoutingDimension * dimension() const
bool HasSoftSpanUpperBounds() const
static constexpr SchedulingSolver CP_SAT
const RoutingDimension * dimension() const
const std::vector< int > & GetDimensionResourceGroupIndices(const RoutingDimension *dimension) const
Returns the indices of resource groups for this dimension.
GlobalDimensionCumulOptimizer(const RoutingDimension *dimension, RoutingSearchParameters::SchedulingSolver solver_type)
virtual int64_t GetVariableLowerBound(int index) const =0
virtual bool SolutionIsInteger() const =0
virtual int64_t Max() const =0
CumulBoundsPropagator(const RoutingDimension *dimension)
#define DCHECK_LT(val1, val2)
virtual void ClearObjective()=0
DimensionSchedulingStatus ComputeRouteCumulCost(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, int64_t *optimal_cost)