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);
164 RoutingSearchParameters::SchedulingSolver solver_type)
165 : optimizer_core_(dimension, false) {
169 solver_.resize(vehicles);
170 switch (solver_type) {
171 case RoutingSearchParameters::GLOP: {
172 const glop::GlopParameters
parameters = GetGlopParametersForLocalLP();
173 for (
int vehicle = 0; vehicle < vehicles; ++vehicle) {
174 solver_[vehicle] = absl::make_unique<RoutingGlopWrapper>(
parameters);
178 case RoutingSearchParameters::CP_SAT: {
179 for (
int vehicle = 0; vehicle < vehicles; ++vehicle) {
180 solver_[vehicle] = absl::make_unique<RoutingCPSatWrapper>();
185 LOG(DFATAL) <<
"Unrecognized solver type: " << solver_type;
190 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
191 int64_t* optimal_cost) {
193 solver_[vehicle].get(),
nullptr,
194 nullptr, optimal_cost,
nullptr);
199 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
200 int64_t* optimal_cost_without_transits) {
202 int64_t transit_cost = 0;
204 vehicle, next_accessor, solver_[vehicle].get(),
nullptr,
nullptr, &
cost,
207 optimal_cost_without_transits !=
nullptr) {
208 *optimal_cost_without_transits =
CapSub(
cost, transit_cost);
214 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
215 std::vector<int64_t>* optimal_cumuls,
216 std::vector<int64_t>* optimal_breaks) {
218 vehicle, next_accessor, solver_[vehicle].get(), optimal_cumuls,
219 optimal_breaks,
nullptr,
nullptr);
224 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
225 std::vector<int64_t>* packed_cumuls, std::vector<int64_t>* packed_breaks) {
227 vehicle, next_accessor, solver_[vehicle].get(), packed_cumuls,
231 const int CumulBoundsPropagator::kNoParent = -2;
232 const int CumulBoundsPropagator::kParentToBePropagated = -1;
235 : dimension_(*dimension), num_nodes_(2 * dimension->cumuls().size()) {
236 outgoing_arcs_.resize(num_nodes_);
237 node_in_queue_.resize(num_nodes_,
false);
238 tree_parent_node_of_.resize(num_nodes_, kNoParent);
239 propagated_bounds_.resize(num_nodes_);
240 visited_pickup_delivery_indices_for_pair_.resize(
244 void CumulBoundsPropagator::AddArcs(
int first_index,
int second_index,
247 outgoing_arcs_[PositiveNode(first_index)].push_back(
248 {PositiveNode(second_index), offset});
249 AddNodeToQueue(PositiveNode(first_index));
251 outgoing_arcs_[NegativeNode(second_index)].push_back(
252 {NegativeNode(first_index), offset});
253 AddNodeToQueue(NegativeNode(second_index));
256 bool CumulBoundsPropagator::InitializeArcsAndBounds(
257 const std::function<int64_t(int64_t)>& next_accessor,
258 int64_t cumul_offset) {
261 for (std::vector<ArcInfo>& arcs : outgoing_arcs_) {
265 RoutingModel*
const model = dimension_.
model();
266 std::vector<int64_t>&
lower_bounds = propagated_bounds_;
268 for (
int vehicle = 0; vehicle <
model->vehicles(); vehicle++) {
269 const std::function<int64_t(int64_t, int64_t)>& transit_accessor =
272 int node =
model->Start(vehicle);
274 int64_t cumul_lb, cumul_ub;
275 if (!GetCumulBoundsWithOffset(dimension_, node, cumul_offset, &cumul_lb,
284 if (
model->IsEnd(node)) {
288 const int next = next_accessor(node);
289 const int64_t transit = transit_accessor(node,
next);
290 const IntVar& slack_var = *dimension_.
SlackVar(node);
293 AddArcs(node,
next,
CapAdd(transit, slack_var.Min()));
296 AddArcs(
next, node,
CapSub(-slack_var.Max(), transit));
305 AddArcs(
model->End(vehicle),
model->Start(vehicle), -span_ub);
309 std::vector<int> visited_pairs;
310 StoreVisitedPickupDeliveryPairsOnRoute(
311 dimension_, vehicle, next_accessor, &visited_pairs,
312 &visited_pickup_delivery_indices_for_pair_);
313 for (
int pair_index : visited_pairs) {
314 const int64_t pickup_index =
315 visited_pickup_delivery_indices_for_pair_[pair_index].first;
316 const int64_t delivery_index =
317 visited_pickup_delivery_indices_for_pair_[pair_index].second;
318 visited_pickup_delivery_indices_for_pair_[pair_index] = {-1, -1};
321 if (delivery_index < 0) {
327 pair_index,
model->GetPickupIndexPairs(pickup_index)[0].second,
328 model->GetDeliveryIndexPairs(delivery_index)[0].second);
331 AddArcs(delivery_index, pickup_index, -limit);
336 for (
const RoutingDimension::NodePrecedence& precedence :
338 const int first_index = precedence.first_node;
339 const int second_index = precedence.second_node;
347 AddArcs(first_index, second_index, precedence.offset);
353 bool CumulBoundsPropagator::UpdateCurrentLowerBoundOfNode(
int node,
356 const int cumul_var_index = node / 2;
358 if (node == PositiveNode(cumul_var_index)) {
360 propagated_bounds_[node] = GetFirstPossibleValueForCumulWithOffset(
361 dimension_, cumul_var_index, new_lb, offset);
364 const int64_t new_ub =
CapSub(0, new_lb);
365 propagated_bounds_[node] =
366 CapSub(0, GetLastPossibleValueForCumulWithOffset(
367 dimension_, cumul_var_index, new_ub, offset));
371 const int64_t cumul_lower_bound =
372 propagated_bounds_[PositiveNode(cumul_var_index)];
374 const int64_t negated_cumul_upper_bound =
375 propagated_bounds_[NegativeNode(cumul_var_index)];
377 return CapAdd(negated_cumul_upper_bound, cumul_lower_bound) <= 0;
380 bool CumulBoundsPropagator::DisassembleSubtree(
int source,
int target) {
381 tmp_dfs_stack_.clear();
382 tmp_dfs_stack_.push_back(source);
383 while (!tmp_dfs_stack_.empty()) {
384 const int tail = tmp_dfs_stack_.back();
385 tmp_dfs_stack_.pop_back();
386 for (
const ArcInfo& arc : outgoing_arcs_[
tail]) {
387 const int child_node = arc.head;
388 if (tree_parent_node_of_[child_node] !=
tail)
continue;
389 if (child_node == target)
return false;
390 tree_parent_node_of_[child_node] = kParentToBePropagated;
391 tmp_dfs_stack_.push_back(child_node);
398 const std::function<int64_t(int64_t)>& next_accessor,
399 int64_t cumul_offset) {
400 tree_parent_node_of_.assign(num_nodes_, kNoParent);
401 DCHECK(std::none_of(node_in_queue_.begin(), node_in_queue_.end(),
402 [](
bool b) { return b; }));
403 DCHECK(bf_queue_.empty());
405 if (!InitializeArcsAndBounds(next_accessor, cumul_offset)) {
406 return CleanupAndReturnFalse();
409 std::vector<int64_t>& current_lb = propagated_bounds_;
412 while (!bf_queue_.empty()) {
413 const int node = bf_queue_.front();
414 bf_queue_.pop_front();
415 node_in_queue_[node] =
false;
417 if (tree_parent_node_of_[node] == kParentToBePropagated) {
424 for (
const ArcInfo& arc : outgoing_arcs_[node]) {
427 const int64_t induced_lb =
432 const int head_node = arc.head;
433 if (induced_lb <= current_lb[head_node]) {
438 if (!UpdateCurrentLowerBoundOfNode(head_node, induced_lb, cumul_offset) ||
439 !DisassembleSubtree(head_node, node)) {
442 return CleanupAndReturnFalse();
445 tree_parent_node_of_[head_node] = node;
446 AddNodeToQueue(head_node);
454 : dimension_(dimension),
455 visited_pickup_delivery_indices_for_pair_(
456 dimension->
model()->GetPickupAndDeliveryPairs().size(), {-1, -1}) {
457 if (use_precedence_propagator) {
458 propagator_ = absl::make_unique<CumulBoundsPropagator>(dimension);
465 vehicle_to_all_break_variables_offset_.reserve(num_vehicles);
466 int num_break_vars = 0;
467 for (
int vehicle = 0; vehicle < num_vehicles; ++vehicle) {
468 vehicle_to_all_break_variables_offset_.push_back(num_break_vars);
470 num_break_vars += 2 * intervals.size();
472 all_break_variables_.resize(num_break_vars, -1);
477 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
479 std::vector<int64_t>* break_values, int64_t*
cost, int64_t* transit_cost,
481 InitOptimizer(solver);
484 DCHECK(propagator_ ==
nullptr);
487 const bool optimize_vehicle_costs =
488 (cumul_values !=
nullptr ||
cost !=
nullptr) &&
489 (!
model->IsEnd(next_accessor(
model->Start(vehicle))) ||
490 model->AreEmptyRouteCostsConsideredForVehicle(vehicle));
491 const int64_t cumul_offset =
493 int64_t cost_offset = 0;
494 if (!SetRouteCumulConstraints(vehicle, next_accessor, cumul_offset,
495 optimize_vehicle_costs, solver, transit_cost,
505 SetValuesFromLP(current_route_cumul_variables_, cumul_offset, solver,
507 SetValuesFromLP(current_route_break_variables_, cumul_offset, solver,
509 if (
cost !=
nullptr) {
520 const std::function<int64_t(int64_t)>& next_accessor,
522 std::vector<int64_t>* break_values, int64_t*
cost, int64_t* transit_cost,
524 InitOptimizer(solver);
528 const bool optimize_costs = (cumul_values !=
nullptr) || (
cost !=
nullptr);
529 bool has_vehicles_being_optimized =
false;
533 if (propagator_ !=
nullptr &&
534 !propagator_->PropagateCumulBounds(next_accessor, cumul_offset)) {
538 int64_t total_transit_cost = 0;
539 int64_t total_cost_offset = 0;
541 for (
int vehicle = 0; vehicle <
model->vehicles(); vehicle++) {
542 int64_t route_transit_cost = 0;
543 int64_t route_cost_offset = 0;
544 const bool optimize_vehicle_costs =
546 (!
model->IsEnd(next_accessor(
model->Start(vehicle))) ||
547 model->AreEmptyRouteCostsConsideredForVehicle(vehicle));
548 if (!SetRouteCumulConstraints(vehicle, next_accessor, cumul_offset,
549 optimize_vehicle_costs, solver,
550 &route_transit_cost, &route_cost_offset)) {
553 total_transit_cost =
CapAdd(total_transit_cost, route_transit_cost);
554 total_cost_offset =
CapAdd(total_cost_offset, route_cost_offset);
555 has_vehicles_being_optimized |= optimize_vehicle_costs;
557 if (transit_cost !=
nullptr) {
558 *transit_cost = total_transit_cost;
561 SetGlobalConstraints(has_vehicles_being_optimized, solver);
568 SetValuesFromLP(index_to_cumul_variable_, cumul_offset, solver, cumul_values);
569 SetValuesFromLP(all_break_variables_, cumul_offset, solver, break_values);
571 if (
cost !=
nullptr) {
582 const std::function<int64_t(int64_t)>& next_accessor,
584 std::vector<int64_t>* break_values) {
588 if (!
Optimize(next_accessor, solver,
589 nullptr,
nullptr, &
cost,
595 std::iota(vehicles.begin(), vehicles.end(), 0);
596 if (PackRoutes(std::move(vehicles), solver) ==
601 SetValuesFromLP(index_to_cumul_variable_, global_offset, solver,
603 SetValuesFromLP(all_break_variables_, global_offset, solver, break_values);
610 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
612 std::vector<int64_t>* break_values) {
627 const int64_t local_offset =
629 SetValuesFromLP(current_route_cumul_variables_, local_offset, solver,
631 SetValuesFromLP(current_route_break_variables_, local_offset, solver,
653 const int objective_ct =
656 for (
int variable = 0; variable < solver->
NumVariables(); variable++) {
663 for (
int vehicle : vehicles) {
665 index_to_cumul_variable_[
model->End(vehicle)], 1);
676 for (
int vehicle : vehicles) {
677 const int end_cumul_var = index_to_cumul_variable_[
model->End(vehicle)];
685 index_to_cumul_variable_[
model->Start(vehicle)], -1);
690 void DimensionCumulOptimizerCore::InitOptimizer(
691 RoutingLinearSolverWrapper* solver) {
693 index_to_cumul_variable_.assign(dimension_->
cumuls().size(), -1);
694 max_end_cumul_ = solver->CreateNewPositiveVariable();
695 min_start_cumul_ = solver->CreateNewPositiveVariable();
698 bool DimensionCumulOptimizerCore::ComputeRouteCumulBounds(
699 const std::vector<int64_t>& route,
700 const std::vector<int64_t>& fixed_transits, int64_t cumul_offset) {
701 const int route_size = route.size();
702 current_route_min_cumuls_.resize(route_size);
703 current_route_max_cumuls_.resize(route_size);
704 if (propagator_ !=
nullptr) {
705 for (
int pos = 0; pos < route_size; pos++) {
706 const int64_t node = route[pos];
707 current_route_min_cumuls_[pos] = propagator_->CumulMin(node);
708 DCHECK_GE(current_route_min_cumuls_[pos], 0);
709 current_route_max_cumuls_[pos] = propagator_->CumulMax(node);
710 DCHECK_GE(current_route_max_cumuls_[pos], current_route_min_cumuls_[pos]);
716 for (
int pos = 0; pos < route_size; ++pos) {
717 if (!GetCumulBoundsWithOffset(*dimension_, route[pos], cumul_offset,
718 ¤t_route_min_cumuls_[pos],
719 ¤t_route_max_cumuls_[pos])) {
726 for (
int pos = 1; pos < route_size; ++pos) {
727 const int64_t slack_min = dimension_->
SlackVar(route[pos - 1])->
Min();
728 current_route_min_cumuls_[pos] =
std::max(
729 current_route_min_cumuls_[pos],
731 CapAdd(current_route_min_cumuls_[pos - 1], fixed_transits[pos - 1]),
733 current_route_min_cumuls_[pos] = GetFirstPossibleValueForCumulWithOffset(
734 *dimension_, route[pos], current_route_min_cumuls_[pos], cumul_offset);
735 if (current_route_min_cumuls_[pos] > current_route_max_cumuls_[pos]) {
740 for (
int pos = route_size - 2; pos >= 0; --pos) {
743 if (current_route_max_cumuls_[pos + 1] <
745 const int64_t slack_min = dimension_->
SlackVar(route[pos])->
Min();
746 current_route_max_cumuls_[pos] =
std::min(
747 current_route_max_cumuls_[pos],
749 CapSub(current_route_max_cumuls_[pos + 1], fixed_transits[pos]),
751 current_route_max_cumuls_[pos] = GetLastPossibleValueForCumulWithOffset(
752 *dimension_, route[pos], current_route_max_cumuls_[pos],
754 if (current_route_max_cumuls_[pos] < current_route_min_cumuls_[pos]) {
762 bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
763 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
764 int64_t cumul_offset,
bool optimize_costs,
765 RoutingLinearSolverWrapper* solver, int64_t* route_transit_cost,
766 int64_t* route_cost_offset) {
767 RoutingModel*
const model = dimension_->
model();
769 std::vector<int64_t> path;
771 int node =
model->Start(vehicle);
772 path.push_back(node);
773 while (!
model->IsEnd(node)) {
774 node = next_accessor(node);
775 path.push_back(node);
779 const int path_size = path.size();
781 std::vector<int64_t> fixed_transit(path_size - 1);
783 const std::function<int64_t(int64_t, int64_t)>& transit_accessor =
785 for (
int pos = 1; pos < path_size; ++pos) {
786 fixed_transit[pos - 1] = transit_accessor(path[pos - 1], path[pos]);
790 if (!ComputeRouteCumulBounds(path, fixed_transit, cumul_offset)) {
796 std::vector<int>& lp_cumuls = current_route_cumul_variables_;
797 lp_cumuls.assign(path_size, -1);
798 for (
int pos = 0; pos < path_size; ++pos) {
799 const int lp_cumul = solver->CreateNewPositiveVariable();
800 index_to_cumul_variable_[path[pos]] = lp_cumul;
801 lp_cumuls[pos] = lp_cumul;
802 if (!solver->SetVariableBounds(lp_cumul, current_route_min_cumuls_[pos],
803 current_route_max_cumuls_[pos])) {
806 const SortedDisjointIntervalList& forbidden =
808 if (forbidden.NumIntervals() > 0) {
809 std::vector<int64_t> starts;
810 std::vector<int64_t> ends;
811 for (
const ClosedInterval
interval :
813 path[pos],
CapAdd(current_route_min_cumuls_[pos], cumul_offset),
814 CapAdd(current_route_max_cumuls_[pos], cumul_offset))) {
818 solver->SetVariableDisjointBounds(lp_cumul, starts, ends);
822 std::vector<int> lp_slacks(path_size - 1, -1);
823 for (
int pos = 0; pos < path_size - 1; ++pos) {
824 const IntVar* cp_slack = dimension_->
SlackVar(path[pos]);
825 lp_slacks[pos] = solver->CreateNewPositiveVariable();
826 if (!solver->SetVariableBounds(lp_slacks[pos], cp_slack->Min(),
836 for (
int pos = 0; pos < path_size - 1; ++pos) {
838 solver->CreateNewConstraint(fixed_transit[pos], fixed_transit[pos]);
839 solver->SetCoefficient(
ct, lp_cumuls[pos + 1], 1);
840 solver->SetCoefficient(
ct, lp_cumuls[pos], -1);
841 solver->SetCoefficient(
ct, lp_slacks[pos], -1);
843 if (route_cost_offset !=
nullptr) *route_cost_offset = 0;
844 if (optimize_costs) {
846 for (
int pos = 0; pos < path_size; ++pos) {
850 if (
coef == 0)
continue;
852 if (
bound < cumul_offset && route_cost_offset !=
nullptr) {
854 *route_cost_offset =
CapAdd(*route_cost_offset,
858 if (current_route_max_cumuls_[pos] <=
bound) {
862 const int soft_ub_diff = solver->CreateNewPositiveVariable();
863 solver->SetObjectiveCoefficient(soft_ub_diff,
coef);
865 const int ct = solver->CreateNewConstraint(
867 solver->SetCoefficient(
ct, lp_cumuls[pos], 1);
868 solver->SetCoefficient(
ct, soft_ub_diff, -1);
871 for (
int pos = 0; pos < path_size; ++pos) {
875 if (
coef == 0)
continue;
876 const int64_t
bound = std::max<int64_t>(
879 if (current_route_min_cumuls_[pos] >=
bound) {
883 const int soft_lb_diff = solver->CreateNewPositiveVariable();
884 solver->SetObjectiveCoefficient(soft_lb_diff,
coef);
886 const int ct = solver->CreateNewConstraint(
888 solver->SetCoefficient(
ct, lp_cumuls[pos], 1);
889 solver->SetCoefficient(
ct, soft_lb_diff, 1);
893 std::vector<int> visited_pairs;
894 StoreVisitedPickupDeliveryPairsOnRoute(
895 *dimension_, vehicle, next_accessor, &visited_pairs,
896 &visited_pickup_delivery_indices_for_pair_);
897 for (
int pair_index : visited_pairs) {
898 const int64_t pickup_index =
899 visited_pickup_delivery_indices_for_pair_[pair_index].first;
900 const int64_t delivery_index =
901 visited_pickup_delivery_indices_for_pair_[pair_index].second;
902 visited_pickup_delivery_indices_for_pair_[pair_index] = {-1, -1};
905 if (delivery_index < 0) {
911 pair_index,
model->GetPickupIndexPairs(pickup_index)[0].second,
912 model->GetDeliveryIndexPairs(delivery_index)[0].second);
915 const int ct = solver->CreateNewConstraint(
917 solver->SetCoefficient(
ct, index_to_cumul_variable_[delivery_index], 1);
918 solver->SetCoefficient(
ct, index_to_cumul_variable_[pickup_index], -1);
926 const int ct = solver->CreateNewConstraint(
928 solver->SetCoefficient(
ct, lp_cumuls.back(), 1);
929 solver->SetCoefficient(
ct, lp_cumuls.front(), -1);
932 const int64_t span_cost_coef =
934 if (optimize_costs && span_cost_coef > 0) {
935 solver->SetObjectiveCoefficient(lp_cumuls.back(), span_cost_coef);
936 solver->SetObjectiveCoefficient(lp_cumuls.front(), -span_cost_coef);
940 SimpleBoundCosts::BoundCost bound_cost =
943 bound_cost.cost > 0) {
944 const int span_violation = solver->CreateNewPositiveVariable();
946 const int violation = solver->CreateNewConstraint(
948 solver->SetCoefficient(violation, lp_cumuls.back(), 1.0);
949 solver->SetCoefficient(violation, lp_cumuls.front(), -1.0);
950 solver->SetCoefficient(violation, span_violation, -1.0);
952 solver->SetObjectiveCoefficient(span_violation, bound_cost.cost);
960 solver->SetCoefficient(
ct, min_start_cumul_, 1);
961 solver->SetCoefficient(
ct, lp_cumuls.front(), -1);
964 solver->SetCoefficient(
ct, max_end_cumul_, 1);
965 solver->SetCoefficient(
ct, lp_cumuls.back(), -1);
968 if (route_transit_cost !=
nullptr) {
969 if (optimize_costs && span_cost_coef > 0) {
970 const int64_t total_fixed_transit = std::accumulate(
971 fixed_transit.begin(), fixed_transit.end(), 0,
CapAdd);
972 *route_transit_cost =
CapProd(total_fixed_transit, span_cost_coef);
974 *route_transit_cost = 0;
982 current_route_break_variables_.clear();
984 const std::vector<IntervalVar*>& breaks =
986 const int num_breaks = breaks.size();
990 if (num_breaks == 0) {
992 for (
const auto& distance_duration :
995 std::min(maximum_route_span, distance_duration.first);
998 const int ct = solver->CreateNewConstraint(
1000 solver->SetCoefficient(
ct, lp_cumuls.back(), 1);
1001 solver->SetCoefficient(
ct, lp_cumuls.front(), -1);
1008 std::vector<int64_t> pre_travel(path_size - 1, 0);
1009 std::vector<int64_t> post_travel(path_size - 1, 0);
1011 const int pre_travel_index =
1013 if (pre_travel_index != -1) {
1017 const int post_travel_index =
1019 if (post_travel_index != -1) {
1028 std::vector<int> lp_break_start;
1029 std::vector<int> lp_break_duration;
1030 std::vector<int> lp_break_end;
1031 if (solver->IsCPSATSolver()) {
1032 lp_break_start.resize(num_breaks, -1);
1033 lp_break_duration.resize(num_breaks, -1);
1034 lp_break_end.resize(num_breaks, -1);
1037 std::vector<int> slack_exact_lower_bound_ct(path_size - 1, -1);
1038 std::vector<int> slack_linear_lower_bound_ct(path_size - 1, -1);
1040 const int64_t vehicle_start_min = current_route_min_cumuls_.front();
1041 const int64_t vehicle_start_max = current_route_max_cumuls_.front();
1042 const int64_t vehicle_end_min = current_route_min_cumuls_.back();
1043 const int64_t vehicle_end_max = current_route_max_cumuls_.back();
1044 const int all_break_variables_offset =
1045 vehicle_to_all_break_variables_offset_[vehicle];
1046 for (
int br = 0; br < num_breaks; ++br) {
1047 const IntervalVar& break_var = *breaks[br];
1048 if (!break_var.MustBePerformed())
continue;
1049 const int64_t break_start_min =
CapSub(break_var.StartMin(), cumul_offset);
1050 const int64_t break_start_max =
CapSub(break_var.StartMax(), cumul_offset);
1051 const int64_t break_end_min =
CapSub(break_var.EndMin(), cumul_offset);
1052 const int64_t break_end_max =
CapSub(break_var.EndMax(), cumul_offset);
1053 const int64_t break_duration_min = break_var.DurationMin();
1054 const int64_t break_duration_max = break_var.DurationMax();
1057 if (solver->IsCPSATSolver()) {
1058 if (break_end_max <= vehicle_start_min ||
1059 vehicle_end_max <= break_start_min) {
1060 all_break_variables_[all_break_variables_offset + 2 * br] = -1;
1061 all_break_variables_[all_break_variables_offset + 2 * br + 1] = -1;
1062 current_route_break_variables_.push_back(-1);
1063 current_route_break_variables_.push_back(-1);
1066 lp_break_start[br] =
1067 solver->AddVariable(break_start_min, break_start_max);
1068 lp_break_end[br] = solver->AddVariable(break_end_min, break_end_max);
1069 lp_break_duration[br] =
1070 solver->AddVariable(break_duration_min, break_duration_max);
1072 solver->AddLinearConstraint(0, 0,
1073 {{lp_break_end[br], 1},
1074 {lp_break_start[br], -1},
1075 {lp_break_duration[br], -1}});
1077 all_break_variables_[all_break_variables_offset + 2 * br] =
1079 all_break_variables_[all_break_variables_offset + 2 * br + 1] =
1081 current_route_break_variables_.push_back(lp_break_start[br]);
1082 current_route_break_variables_.push_back(lp_break_end[br]);
1084 if (break_end_min <= vehicle_start_max ||
1085 vehicle_end_min <= break_start_max) {
1093 const int break_in_one_slack_ct = solver->CreateNewConstraint(1, 1);
1095 if (solver->IsCPSATSolver()) {
1097 if (break_end_min <= vehicle_start_max) {
1098 const int ct = solver->AddLinearConstraint(
1100 {{lp_cumuls.front(), 1}, {lp_break_end[br], -1}});
1101 const int break_is_before_route = solver->AddVariable(0, 1);
1102 solver->SetEnforcementLiteral(
ct, break_is_before_route);
1103 solver->SetCoefficient(break_in_one_slack_ct, break_is_before_route, 1);
1106 if (vehicle_end_min <= break_start_max) {
1107 const int ct = solver->AddLinearConstraint(
1109 {{lp_break_start[br], 1}, {lp_cumuls.back(), -1}});
1110 const int break_is_after_route = solver->AddVariable(0, 1);
1111 solver->SetEnforcementLiteral(
ct, break_is_after_route);
1112 solver->SetCoefficient(break_in_one_slack_ct, break_is_after_route, 1);
1117 for (
int pos = 0; pos < path_size - 1; ++pos) {
1120 const int64_t slack_start_min =
1121 CapAdd(current_route_min_cumuls_[pos], pre_travel[pos]);
1122 if (slack_start_min > break_start_max)
break;
1123 const int64_t slack_end_max =
1124 CapSub(current_route_max_cumuls_[pos + 1], post_travel[pos]);
1125 if (break_end_min > slack_end_max)
continue;
1126 const int64_t slack_duration_max =
1128 current_route_min_cumuls_[pos]),
1129 fixed_transit[pos]),
1131 if (slack_duration_max < break_duration_min)
continue;
1138 const int break_in_slack = solver->AddVariable(0, 1);
1139 solver->SetCoefficient(break_in_one_slack_ct, break_in_slack, 1);
1140 if (slack_linear_lower_bound_ct[pos] == -1) {
1141 slack_linear_lower_bound_ct[pos] = solver->AddLinearConstraint(
1144 solver->SetCoefficient(slack_linear_lower_bound_ct[pos], break_in_slack,
1145 break_duration_min);
1146 if (solver->IsCPSATSolver()) {
1151 const int break_duration_in_slack =
1152 solver->AddVariable(0, slack_duration_max);
1153 solver->AddProductConstraint(break_duration_in_slack,
1154 {break_in_slack, lp_break_duration[br]});
1155 if (slack_exact_lower_bound_ct[pos] == -1) {
1156 slack_exact_lower_bound_ct[pos] = solver->AddLinearConstraint(
1159 solver->SetCoefficient(slack_exact_lower_bound_ct[pos],
1160 break_duration_in_slack, 1);
1163 const int break_start_after_current_ct = solver->AddLinearConstraint(
1165 {{lp_break_start[br], 1}, {lp_cumuls[pos], -1}});
1166 solver->SetEnforcementLiteral(break_start_after_current_ct,
1169 const int break_ends_before_next_ct = solver->AddLinearConstraint(
1171 {{lp_cumuls[pos + 1], 1}, {lp_break_end[br], -1}});
1172 solver->SetEnforcementLiteral(break_ends_before_next_ct,
1178 if (!solver->IsCPSATSolver())
return true;
1184 if (!
interval->MustBePerformed())
return true;
1187 for (
int br = 1; br < num_breaks; ++br) {
1188 solver->AddLinearConstraint(
1190 {{lp_break_end[br - 1], -1}, {lp_break_start[br], 1}});
1193 for (
const auto& distance_duration :
1195 const int64_t limit = distance_duration.first;
1196 const int64_t min_break_duration = distance_duration.second;
1224 int previous_cover = solver->AddVariable(
CapAdd(vehicle_start_min, limit),
1225 CapAdd(vehicle_start_max, limit));
1226 solver->AddLinearConstraint(limit, limit,
1227 {{previous_cover, 1}, {lp_cumuls.front(), -1}});
1228 for (
int br = 0; br < num_breaks; ++br) {
1229 if (lp_break_start[br] == -1)
continue;
1230 const int64_t break_end_min =
CapSub(breaks[br]->EndMin(), cumul_offset);
1231 const int64_t break_end_max =
CapSub(breaks[br]->EndMax(), cumul_offset);
1234 const int break_is_eligible = solver->AddVariable(0, 1);
1235 const int break_is_not_eligible = solver->AddVariable(0, 1);
1237 solver->AddLinearConstraint(
1238 1, 1, {{break_is_eligible, 1}, {break_is_not_eligible, 1}});
1239 const int positive_ct = solver->AddLinearConstraint(
1241 {{lp_break_end[br], 1}, {lp_break_start[br], -1}});
1242 solver->SetEnforcementLiteral(positive_ct, break_is_eligible);
1243 const int negative_ct = solver->AddLinearConstraint(
1245 {{lp_break_end[br], 1}, {lp_break_start[br], -1}});
1246 solver->SetEnforcementLiteral(negative_ct, break_is_not_eligible);
1253 const int break_cover = solver->AddVariable(
1256 const int limit_cover_ct = solver->AddLinearConstraint(
1257 limit, limit, {{break_cover, 1}, {lp_break_end[br], -1}});
1258 solver->SetEnforcementLiteral(limit_cover_ct, break_is_eligible);
1259 const int empty_cover_ct = solver->AddLinearConstraint(
1260 CapAdd(vehicle_start_min, limit),
CapAdd(vehicle_start_min, limit),
1261 {{break_cover, 1}});
1262 solver->SetEnforcementLiteral(empty_cover_ct, break_is_not_eligible);
1265 solver->AddVariable(
CapAdd(vehicle_start_min, limit),
1267 solver->AddMaximumConstraint(cover, {previous_cover, break_cover});
1270 const int route_end_is_not_covered = solver->AddReifiedLinearConstraint(
1272 {{lp_cumuls.back(), 1}, {previous_cover, -1}});
1273 const int break_start_cover_ct = solver->AddLinearConstraint(
1275 {{previous_cover, 1}, {lp_break_start[br], -1}});
1276 solver->SetEnforcementLiteral(break_start_cover_ct,
1277 route_end_is_not_covered);
1279 previous_cover = cover;
1282 {{previous_cover, 1}, {lp_cumuls.back(), -1}});
1288 void DimensionCumulOptimizerCore::SetGlobalConstraints(
1289 bool optimize_costs, RoutingLinearSolverWrapper* solver) {
1293 if (optimize_costs && global_span_coeff > 0) {
1294 solver->SetObjectiveCoefficient(max_end_cumul_, global_span_coeff);
1295 solver->SetObjectiveCoefficient(min_start_cumul_, -global_span_coeff);
1299 for (
const RoutingDimension::NodePrecedence& precedence :
1301 const int first_cumul_var = index_to_cumul_variable_[precedence.first_node];
1302 const int second_cumul_var =
1303 index_to_cumul_variable_[precedence.second_node];
1304 if (first_cumul_var < 0 || second_cumul_var < 0) {
1309 DCHECK_NE(first_cumul_var, second_cumul_var)
1310 <<
"Dimension " << dimension_->
name()
1311 <<
" has a self-precedence on node " << precedence.first_node <<
".";
1314 const int ct = solver->CreateNewConstraint(
1316 solver->SetCoefficient(
ct, second_cumul_var, 1);
1317 solver->SetCoefficient(
ct, first_cumul_var, -1);
1321 void DimensionCumulOptimizerCore::SetValuesFromLP(
1322 const std::vector<int>& lp_variables, int64_t offset,
1323 RoutingLinearSolverWrapper* solver, std::vector<int64_t>* lp_values) {
1324 if (lp_values ==
nullptr)
return;
1326 for (
int i = 0; i < lp_variables.size(); i++) {
1327 const int cumul_var = lp_variables[i];
1328 if (cumul_var < 0)
continue;
1329 const double lp_value_double = solver->GetValue(cumul_var);
1330 const int64_t lp_value_int64 =
1334 (*lp_values)[i] =
CapAdd(lp_value_int64, offset);
1340 : optimizer_core_(dimension,
1342 !dimension->GetNodePrecedences().empty()) {
1344 absl::make_unique<RoutingGlopWrapper>(GetGlopParametersForGlobalLP());
1348 const std::function<int64_t(int64_t)>& next_accessor,
1349 int64_t* optimal_cost_without_transits) {
1351 int64_t transit_cost = 0;
1352 if (optimizer_core_.
Optimize(next_accessor, solver_.get(),
nullptr,
nullptr,
1353 &
cost, &transit_cost)) {
1354 if (optimal_cost_without_transits !=
nullptr) {
1355 *optimal_cost_without_transits =
CapSub(
cost, transit_cost);
1363 const std::function<int64_t(int64_t)>& next_accessor,
1364 std::vector<int64_t>* optimal_cumuls,
1365 std::vector<int64_t>* optimal_breaks) {
1366 return optimizer_core_.
Optimize(next_accessor, solver_.get(), optimal_cumuls,
1367 optimal_breaks,
nullptr,
nullptr);
1371 const std::function<int64_t(int64_t)>& next_accessor) {
1372 return optimizer_core_.
Optimize(next_accessor, solver_.get(),
nullptr,
1373 nullptr,
nullptr,
nullptr);
1377 const std::function<int64_t(int64_t)>& next_accessor,
1378 std::vector<int64_t>* packed_cumuls, std::vector<int64_t>* packed_breaks) {
1380 packed_cumuls, packed_breaks);
#define DCHECK_NE(val1, val2)
#define DCHECK_GE(val1, val2)
#define DCHECK_LT(val1, val2)
#define DCHECK(condition)
#define DCHECK_EQ(val1, val2)
const RoutingDimension & dimension() const
bool PropagateCumulBounds(const std::function< int64_t(int64_t)> &next_accessor, int64_t cumul_offset)
CumulBoundsPropagator(const RoutingDimension *dimension)
DimensionCumulOptimizerCore(const RoutingDimension *dimension, bool use_precedence_propagator)
const RoutingDimension * dimension() const
bool OptimizeAndPack(const std::function< int64_t(int64_t)> &next_accessor, RoutingLinearSolverWrapper *solver, std::vector< int64_t > *cumul_values, std::vector< int64_t > *break_values)
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)
bool 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)
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)
bool ComputeCumulCostWithoutFixedTransits(const std::function< int64_t(int64_t)> &next_accessor, int64_t *optimal_cost_without_transits)
GlobalDimensionCumulOptimizer(const RoutingDimension *dimension)
bool IsFeasible(const std::function< int64_t(int64_t)> &next_accessor)
bool ComputeCumuls(const std::function< int64_t(int64_t)> &next_accessor, std::vector< int64_t > *optimal_cumuls, std::vector< int64_t > *optimal_breaks)
bool ComputePackedCumuls(const std::function< int64_t(int64_t)> &next_accessor, std::vector< int64_t > *packed_cumuls, std::vector< int64_t > *packed_breaks)
virtual int64_t Min() const =0
virtual int64_t Max() const =0
DimensionSchedulingStatus ComputeRouteCumulCost(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, int64_t *optimal_cost)
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)
DimensionSchedulingStatus ComputeRouteCumulCostWithoutFixedTransits(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, int64_t *optimal_cost_without_transits)
LocalDimensionCumulOptimizer(const RoutingDimension *dimension, RoutingSearchParameters::SchedulingSolver solver_type)
const RoutingDimension * dimension() const
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)
static int64_t FastInt64Round(double x)
Dimensions represent quantities accumulated at nodes along the routes.
SimpleBoundCosts::BoundCost GetSoftSpanUpperBoundForVehicle(int vehicle) const
const std::vector< IntVar * > & cumuls() const
Like CumulVar(), TransitVar(), SlackVar() but return the whole variable vectors instead (indexed by i...
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.
RoutingModel * model() const
Returns the model on which the dimension was created.
int64_t GetGlobalOptimizerOffset() const
int64_t GetPickupToDeliveryLimitForPair(int pair_index, int pickup, int delivery) const
bool HasCumulVarSoftLowerBound(int64_t index) const
Returns true if a soft lower bound has been set for a given variable index.
int64_t GetSpanCostCoefficientForVehicle(int vehicle) const
int64_t global_span_cost_coefficient() const
int64_t GetSpanUpperBoundForVehicle(int vehicle) const
bool HasBreakConstraints() const
Returns true if any break interval or break distance was defined.
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.
int GetPreTravelEvaluatorOfVehicle(int vehicle) const
!defined(SWIGPYTHON)
bool HasCumulVarSoftUpperBound(int64_t index) const
Returns true if a soft upper bound has been set for a given variable index.
const std::vector< IntervalVar * > & GetBreakIntervalsOfVehicle(int vehicle) const
Returns the break intervals set by SetBreakIntervalsOfVehicle().
IntVar * SlackVar(int64_t index) const
const RoutingModel::TransitCallback2 & transit_evaluator(int vehicle) const
Returns the callback evaluating the transit value between two node indices for a given vehicle.
int64_t GetLocalOptimizerOffsetForVehicle(int vehicle) const
const std::string & name() const
Returns the name of the dimension.
const std::vector< NodePrecedence > & GetNodePrecedences() const
int64_t GetCumulVarSoftUpperBound(int64_t index) const
Returns the soft upper bound of a cumul variable for a given variable index.
const std::vector< std::pair< int64_t, int64_t > > & GetBreakDistanceDurationOfVehicle(int vehicle) const
Returns the pairs (distance, duration) specified by break distance constraints.
bool HasSoftSpanUpperBounds() const
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.
int GetPostTravelEvaluatorOfVehicle(int vehicle) const
int64_t GetCumulVarSoftLowerBound(int64_t index) const
Returns the soft lower bound of a cumul variable for a given variable index.
const std::vector< SortedDisjointIntervalList > & forbidden_intervals() const
Returns forbidden intervals for each node.
virtual void SetCoefficient(int ct, int index, double coefficient)=0
virtual int NumVariables() const =0
virtual double GetObjectiveCoefficient(int index) const =0
virtual int64_t GetObjectiveValue() const =0
virtual void ClearObjective()=0
virtual int CreateNewConstraint(int64_t lower_bound, int64_t upper_bound)=0
virtual double GetValue(int index) const =0
virtual DimensionSchedulingStatus Solve(absl::Duration duration_limit)=0
virtual bool SetVariableBounds(int index, int64_t lower_bound, int64_t upper_bound)=0
virtual void SetObjectiveCoefficient(int index, double coefficient)=0
virtual bool SolutionIsInteger() const =0
virtual int64_t GetVariableLowerBound(int index) const =0
const IndexPairs & GetPickupAndDeliveryPairs() const
Returns pickup and delivery pairs currently in the model.
int vehicles() const
Returns the number of vehicle routes in the model.
Collection of objects used to extend the Constraint Solver library.
int64_t CapAdd(int64_t x, int64_t y)
int64_t CapSub(int64_t x, int64_t y)
DimensionSchedulingStatus
int64_t CapProd(int64_t x, int64_t y)
void FillPathEvaluation(const std::vector< int64_t > &path, const RoutingModel::TransitCallback2 &evaluator, std::vector< int64_t > *values)
std::vector< double > lower_bounds