26#include "absl/memory/memory.h"
27#include "absl/strings/str_join.h"
28#include "absl/time/time.h"
33#include "ortools/constraint_solver/routing_parameters.pb.h"
34#include "ortools/glop/parameters.pb.h"
45glop::GlopParameters GetGlopParametersForLocalLP() {
52glop::GlopParameters GetGlopParametersForGlobalLP() {
58bool GetCumulBoundsWithOffset(
const RoutingDimension& dimension,
59 int64_t node_index, int64_t cumul_offset,
64 const IntVar& cumul_var = *dimension.CumulVar(node_index);
70 const int64_t first_after_offset =
71 std::max(dimension.GetFirstPossibleGreaterOrEqualValueForNode(
72 node_index, cumul_offset),
86int64_t GetFirstPossibleValueForCumulWithOffset(
87 const RoutingDimension& dimension, int64_t node_index,
88 int64_t lower_bound_without_offset, int64_t cumul_offset) {
90 dimension.GetFirstPossibleGreaterOrEqualValueForNode(
91 node_index,
CapAdd(lower_bound_without_offset, cumul_offset)),
95int64_t GetLastPossibleValueForCumulWithOffset(
96 const RoutingDimension& dimension, int64_t node_index,
97 int64_t upper_bound_without_offset, int64_t cumul_offset) {
99 dimension.GetLastPossibleLessOrEqualValueForNode(
100 node_index,
CapAdd(upper_bound_without_offset, cumul_offset)),
109void StoreVisitedPickupDeliveryPairsOnRoute(
110 const RoutingDimension& dimension,
int vehicle,
111 const std::function<int64_t(int64_t)>& next_accessor,
112 std::vector<int>* visited_pairs,
113 std::vector<std::pair<int64_t, int64_t>>*
114 visited_pickup_delivery_indices_for_pair) {
116 DCHECK_EQ(visited_pickup_delivery_indices_for_pair->size(),
117 dimension.model()->GetPickupAndDeliveryPairs().size());
118 DCHECK(std::all_of(visited_pickup_delivery_indices_for_pair->begin(),
119 visited_pickup_delivery_indices_for_pair->end(),
120 [](std::pair<int64_t, int64_t> p) {
121 return p.first == -1 && p.second == -1;
123 visited_pairs->clear();
124 if (!dimension.HasPickupToDeliveryLimits()) {
127 const RoutingModel&
model = *dimension.model();
129 int64_t node_index =
model.Start(vehicle);
130 while (!
model.IsEnd(node_index)) {
131 const std::vector<std::pair<int, int>>& pickup_index_pairs =
132 model.GetPickupIndexPairs(node_index);
133 const std::vector<std::pair<int, int>>& delivery_index_pairs =
134 model.GetDeliveryIndexPairs(node_index);
135 if (!pickup_index_pairs.empty()) {
138 DCHECK(delivery_index_pairs.empty());
140 (*visited_pickup_delivery_indices_for_pair)[pickup_index_pairs[0].first]
142 visited_pairs->push_back(pickup_index_pairs[0].first);
143 }
else if (!delivery_index_pairs.empty()) {
147 DCHECK_EQ(delivery_index_pairs.size(), 1);
148 const int pair_index = delivery_index_pairs[0].first;
149 std::pair<int64_t, int64_t>& pickup_delivery_index =
150 (*visited_pickup_delivery_indices_for_pair)[pair_index];
151 if (pickup_delivery_index.first < 0) {
154 node_index = next_accessor(node_index);
157 pickup_delivery_index.second = node_index;
159 node_index = next_accessor(node_index);
169 RoutingSearchParameters::SchedulingSolver solver_type)
170 : optimizer_core_(dimension, false) {
174 solver_.resize(vehicles);
175 switch (solver_type) {
176 case RoutingSearchParameters::SCHEDULING_GLOP: {
177 const glop::GlopParameters
parameters = GetGlopParametersForLocalLP();
178 for (
int vehicle = 0; vehicle < vehicles; ++vehicle) {
182 absl::make_unique<RoutingGlopWrapper>(
false,
parameters);
186 case RoutingSearchParameters::SCHEDULING_CP_SAT: {
187 for (
int vehicle = 0; vehicle < vehicles; ++vehicle) {
188 solver_[vehicle] = absl::make_unique<RoutingCPSatWrapper>();
193 LOG(DFATAL) <<
"Unrecognized solver type: " << solver_type;
198 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
199 int64_t* optimal_cost) {
201 solver_[vehicle].get(),
nullptr,
202 nullptr, optimal_cost,
nullptr);
207 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
208 int64_t* optimal_cost_without_transits) {
210 int64_t transit_cost = 0;
212 vehicle, next_accessor, solver_[vehicle].get(),
nullptr,
nullptr, &
cost,
215 optimal_cost_without_transits !=
nullptr) {
216 *optimal_cost_without_transits =
CapSub(
cost, transit_cost);
223 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
224 const std::vector<RoutingModel::ResourceGroup::Resource>& resources,
225 const std::vector<int>& resource_indices,
bool optimize_vehicle_costs,
226 std::vector<int64_t>* optimal_costs_without_transits,
227 std::vector<std::vector<int64_t>>* optimal_cumuls,
228 std::vector<std::vector<int64_t>>* optimal_breaks) {
230 vehicle, next_accessor, resources, resource_indices,
231 optimize_vehicle_costs, solver_[vehicle].get(),
232 optimal_costs_without_transits, optimal_cumuls, optimal_breaks);
236 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
237 std::vector<int64_t>* optimal_cumuls,
238 std::vector<int64_t>* optimal_breaks) {
240 vehicle, next_accessor, solver_[vehicle].get(), optimal_cumuls,
241 optimal_breaks,
nullptr,
nullptr);
246 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
248 std::vector<int64_t>* packed_cumuls, std::vector<int64_t>* packed_breaks) {
250 vehicle, next_accessor, resource, solver_[vehicle].get(), packed_cumuls,
254const int CumulBoundsPropagator::kNoParent = -2;
255const int CumulBoundsPropagator::kParentToBePropagated = -1;
258 : dimension_(*dimension), num_nodes_(2 * dimension->cumuls().size()) {
259 outgoing_arcs_.resize(num_nodes_);
260 node_in_queue_.resize(num_nodes_,
false);
261 tree_parent_node_of_.resize(num_nodes_, kNoParent);
262 propagated_bounds_.resize(num_nodes_);
263 visited_pickup_delivery_indices_for_pair_.resize(
267void CumulBoundsPropagator::AddArcs(
int first_index,
int second_index,
270 outgoing_arcs_[PositiveNode(first_index)].push_back(
271 {PositiveNode(second_index), offset});
272 AddNodeToQueue(PositiveNode(first_index));
274 outgoing_arcs_[NegativeNode(second_index)].push_back(
275 {NegativeNode(first_index), offset});
276 AddNodeToQueue(NegativeNode(second_index));
279bool CumulBoundsPropagator::InitializeArcsAndBounds(
280 const std::function<int64_t(int64_t)>& next_accessor,
281 int64_t cumul_offset) {
284 for (std::vector<ArcInfo>& arcs : outgoing_arcs_) {
288 RoutingModel*
const model = dimension_.
model();
289 std::vector<int64_t>&
lower_bounds = propagated_bounds_;
291 for (
int vehicle = 0; vehicle <
model->vehicles(); vehicle++) {
292 const std::function<int64_t(int64_t, int64_t)>& transit_accessor =
295 int node =
model->Start(vehicle);
297 int64_t cumul_lb, cumul_ub;
298 if (!GetCumulBoundsWithOffset(dimension_, node, cumul_offset, &cumul_lb,
307 if (
model->IsEnd(node)) {
311 const int next = next_accessor(node);
312 const int64_t transit = transit_accessor(node,
next);
313 const IntVar& slack_var = *dimension_.
SlackVar(node);
316 AddArcs(node,
next,
CapAdd(transit, slack_var.Min()));
319 AddArcs(
next, node,
CapSub(-slack_var.Max(), transit));
328 AddArcs(
model->End(vehicle),
model->Start(vehicle), -span_ub);
332 std::vector<int> visited_pairs;
333 StoreVisitedPickupDeliveryPairsOnRoute(
334 dimension_, vehicle, next_accessor, &visited_pairs,
335 &visited_pickup_delivery_indices_for_pair_);
336 for (
int pair_index : visited_pairs) {
337 const int64_t pickup_index =
338 visited_pickup_delivery_indices_for_pair_[pair_index].first;
339 const int64_t delivery_index =
340 visited_pickup_delivery_indices_for_pair_[pair_index].second;
341 visited_pickup_delivery_indices_for_pair_[pair_index] = {-1, -1};
344 if (delivery_index < 0) {
350 pair_index,
model->GetPickupIndexPairs(pickup_index)[0].second,
351 model->GetDeliveryIndexPairs(delivery_index)[0].second);
354 AddArcs(delivery_index, pickup_index, -limit);
359 for (
const RoutingDimension::NodePrecedence& precedence :
361 const int first_index = precedence.first_node;
362 const int second_index = precedence.second_node;
370 AddArcs(first_index, second_index, precedence.offset);
376bool CumulBoundsPropagator::UpdateCurrentLowerBoundOfNode(
int node,
379 const int cumul_var_index = node / 2;
381 if (node == PositiveNode(cumul_var_index)) {
383 propagated_bounds_[node] = GetFirstPossibleValueForCumulWithOffset(
384 dimension_, cumul_var_index, new_lb, offset);
387 const int64_t new_ub =
CapSub(0, new_lb);
388 propagated_bounds_[node] =
389 CapSub(0, GetLastPossibleValueForCumulWithOffset(
390 dimension_, cumul_var_index, new_ub, offset));
394 const int64_t cumul_lower_bound =
395 propagated_bounds_[PositiveNode(cumul_var_index)];
397 const int64_t negated_cumul_upper_bound =
398 propagated_bounds_[NegativeNode(cumul_var_index)];
400 return CapAdd(negated_cumul_upper_bound, cumul_lower_bound) <= 0;
403bool CumulBoundsPropagator::DisassembleSubtree(
int source,
int target) {
404 tmp_dfs_stack_.clear();
405 tmp_dfs_stack_.push_back(source);
406 while (!tmp_dfs_stack_.empty()) {
407 const int tail = tmp_dfs_stack_.back();
408 tmp_dfs_stack_.pop_back();
409 for (
const ArcInfo&
arc : outgoing_arcs_[
tail]) {
410 const int child_node =
arc.head;
411 if (tree_parent_node_of_[child_node] !=
tail)
continue;
412 if (child_node == target)
return false;
413 tree_parent_node_of_[child_node] = kParentToBePropagated;
414 tmp_dfs_stack_.push_back(child_node);
421 const std::function<int64_t(int64_t)>& next_accessor,
422 int64_t cumul_offset) {
423 tree_parent_node_of_.assign(num_nodes_, kNoParent);
424 DCHECK(std::none_of(node_in_queue_.begin(), node_in_queue_.end(),
425 [](
bool b) { return b; }));
426 DCHECK(bf_queue_.empty());
428 if (!InitializeArcsAndBounds(next_accessor, cumul_offset)) {
429 return CleanupAndReturnFalse();
432 std::vector<int64_t>& current_lb = propagated_bounds_;
435 while (!bf_queue_.empty()) {
436 const int node = bf_queue_.front();
437 bf_queue_.pop_front();
438 node_in_queue_[node] =
false;
440 if (tree_parent_node_of_[node] == kParentToBePropagated) {
447 for (
const ArcInfo&
arc : outgoing_arcs_[node]) {
450 const int64_t induced_lb =
455 const int head_node =
arc.head;
456 if (induced_lb <= current_lb[head_node]) {
461 if (!UpdateCurrentLowerBoundOfNode(head_node, induced_lb, cumul_offset) ||
462 !DisassembleSubtree(head_node, node)) {
465 return CleanupAndReturnFalse();
468 tree_parent_node_of_[head_node] = node;
469 AddNodeToQueue(head_node);
477 : dimension_(dimension),
478 visited_pickup_delivery_indices_for_pair_(
479 dimension->
model()->GetPickupAndDeliveryPairs().size(), {-1, -1}) {
480 if (use_precedence_propagator) {
481 propagator_ = absl::make_unique<CumulBoundsPropagator>(dimension);
483 const RoutingModel&
model = *dimension_->
model();
488 const int num_vehicles =
model.vehicles();
489 vehicle_to_all_break_variables_offset_.reserve(num_vehicles);
490 int num_break_vars = 0;
491 for (
int vehicle = 0; vehicle < num_vehicles; ++vehicle) {
492 vehicle_to_all_break_variables_offset_.push_back(num_break_vars);
494 num_break_vars += 2 * intervals.size();
496 all_break_variables_.resize(num_break_vars, -1);
498 if (!
model.GetDimensionResourceGroupIndices(dimension_).empty()) {
499 resource_group_to_resource_to_vehicle_assignment_variables_.resize(
500 model.GetResourceGroups().size());
505 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
507 std::vector<int64_t>* break_values, int64_t*
cost, int64_t* transit_cost,
509 InitOptimizer(solver);
515 const bool optimize_vehicle_costs =
516 (cumul_values !=
nullptr ||
cost !=
nullptr) &&
517 (!
model->IsEnd(next_accessor(
model->Start(vehicle))) ||
518 model->IsVehicleUsedWhenEmpty(vehicle));
519 const int64_t cumul_offset =
521 int64_t cost_offset = 0;
522 if (!SetRouteCumulConstraints(vehicle, next_accessor, cumul_offset,
523 optimize_vehicle_costs, solver, transit_cost,
527 if (
model->CheckLimit()) {
537 SetValuesFromLP(current_route_cumul_variables_, cumul_offset, solver,
539 SetValuesFromLP(current_route_break_variables_, cumul_offset, solver,
541 if (
cost !=
nullptr) {
555bool GetDomainOffsetBounds(
const Domain& domain, int64_t offset,
558 std::max<int64_t>(
CapSub(domain.
Min(), offset), 0);
562 :
CapSub(domain.Max(), offset);
569bool GetIntervalIntersectionWithOffsetDomain(
const ClosedInterval&
interval,
570 const Domain& domain,
572 ClosedInterval* intersection) {
573 ClosedInterval domain_bounds;
574 if (!GetDomainOffsetBounds(domain, offset, &domain_bounds)) {
577 const int64_t intersection_lb =
std::max(
interval.start, domain_bounds.start);
579 if (intersection_lb > intersection_ub)
return false;
581 *intersection = ClosedInterval(intersection_lb, intersection_ub);
585ClosedInterval GetVariableBounds(
int index,
586 const RoutingLinearSolverWrapper& solver) {
587 return ClosedInterval(solver.GetVariableLowerBound(
index),
588 solver.GetVariableUpperBound(
index));
591bool TightenStartEndVariableBoundsWithResource(
592 const RoutingDimension& dimension,
const ResourceGroup::Resource& resource,
593 const ClosedInterval& start_bounds,
int start_index,
594 const ClosedInterval& end_bounds,
int end_index, int64_t offset,
595 RoutingLinearSolverWrapper* solver) {
596 const ResourceGroup::Attributes& attributes =
597 resource.GetDimensionAttributes(&dimension);
598 ClosedInterval new_start_bounds;
599 ClosedInterval new_end_bounds;
600 return GetIntervalIntersectionWithOffsetDomain(start_bounds,
601 attributes.start_domain(),
602 offset, &new_start_bounds) &&
603 solver->SetVariableBounds(start_index, new_start_bounds.start,
604 new_start_bounds.end) &&
605 GetIntervalIntersectionWithOffsetDomain(
606 end_bounds, attributes.end_domain(), offset, &new_end_bounds) &&
607 solver->SetVariableBounds(end_index, new_end_bounds.start,
613std::vector<DimensionSchedulingStatus>
615 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
616 const std::vector<RoutingModel::ResourceGroup::Resource>& resources,
617 const std::vector<int>& resource_indices,
bool optimize_vehicle_costs,
619 std::vector<int64_t>* costs_without_transits,
620 std::vector<std::vector<int64_t>>* cumul_values,
621 std::vector<std::vector<int64_t>>* break_values,
bool clear_lp) {
622 if (resource_indices.empty())
return {};
624 InitOptimizer(solver);
628 DCHECK_NE(costs_without_transits,
nullptr);
629 costs_without_transits->clear();
632 if (
model->IsEnd(next_accessor(
model->Start(vehicle))) &&
633 !
model->IsVehicleUsedWhenEmpty(vehicle)) {
638 const int64_t cumul_offset =
640 int64_t cost_offset = 0;
641 int64_t transit_cost = 0;
642 if (!SetRouteCumulConstraints(vehicle, next_accessor, cumul_offset,
643 optimize_vehicle_costs, solver, &transit_cost,
648 costs_without_transits->assign(resource_indices.size(), -1);
649 if (cumul_values !=
nullptr) {
650 cumul_values->assign(resource_indices.size(), {});
652 if (break_values !=
nullptr) {
653 break_values->assign(resource_indices.size(), {});
656 DCHECK_GE(current_route_cumul_variables_.size(), 2);
658 const int start_cumul = current_route_cumul_variables_[0];
659 const ClosedInterval start_bounds = GetVariableBounds(start_cumul, *solver);
660 const int end_cumul = current_route_cumul_variables_.back();
661 const ClosedInterval end_bounds = GetVariableBounds(end_cumul, *solver);
662 std::vector<DimensionSchedulingStatus> statuses;
663 for (
int i = 0; i < resource_indices.size(); i++) {
664 if (
model->CheckLimit()) {
666 costs_without_transits->clear();
667 if (cumul_values !=
nullptr) {
668 cumul_values->clear();
670 if (break_values !=
nullptr) {
671 break_values->clear();
675 if (!TightenStartEndVariableBoundsWithResource(
676 *dimension_, resources[resource_indices[i]], start_bounds,
677 start_cumul, end_bounds, end_cumul, cumul_offset, solver)) {
683 statuses.push_back(solver->
Solve(
model->RemainingTime()));
687 costs_without_transits->at(i) =
688 optimize_vehicle_costs
693 if (cumul_values !=
nullptr) {
694 SetValuesFromLP(current_route_cumul_variables_, cumul_offset, solver,
695 &cumul_values->at(i));
697 if (break_values !=
nullptr) {
698 SetValuesFromLP(current_route_break_variables_, cumul_offset, solver,
699 &break_values->at(i));
710 const std::function<int64_t(int64_t)>& next_accessor,
712 std::vector<int64_t>* break_values,
713 std::vector<std::vector<int>>* resource_indices_per_group, int64_t*
cost,
714 int64_t* transit_cost,
bool clear_lp) {
715 InitOptimizer(solver);
719 const bool optimize_costs = (cumul_values !=
nullptr) || (
cost !=
nullptr);
720 bool has_vehicles_being_optimized =
false;
724 if (propagator_ !=
nullptr &&
725 !propagator_->PropagateCumulBounds(next_accessor, cumul_offset)) {
729 int64_t total_transit_cost = 0;
730 int64_t total_cost_offset = 0;
732 for (
int vehicle = 0; vehicle <
model->vehicles(); vehicle++) {
733 int64_t route_transit_cost = 0;
734 int64_t route_cost_offset = 0;
735 const bool vehicle_is_used =
736 !
model->IsEnd(next_accessor(
model->Start(vehicle))) ||
737 model->IsVehicleUsedWhenEmpty(vehicle);
738 const bool optimize_vehicle_costs = optimize_costs && vehicle_is_used;
739 if (!SetRouteCumulConstraints(vehicle, next_accessor, cumul_offset,
740 optimize_vehicle_costs, solver,
741 &route_transit_cost, &route_cost_offset)) {
744 total_transit_cost =
CapAdd(total_transit_cost, route_transit_cost);
745 total_cost_offset =
CapAdd(total_cost_offset, route_cost_offset);
746 has_vehicles_being_optimized |= optimize_vehicle_costs;
748 if (transit_cost !=
nullptr) {
749 *transit_cost = total_transit_cost;
752 if (!SetGlobalConstraints(next_accessor, cumul_offset,
753 has_vehicles_being_optimized, solver)) {
766 SetValuesFromLP(index_to_cumul_variable_, cumul_offset, solver, cumul_values);
767 SetValuesFromLP(all_break_variables_, cumul_offset, solver, break_values);
768 SetResourceIndices(solver, resource_indices_per_group);
770 if (
cost !=
nullptr) {
781 const std::function<int64_t(int64_t)>& next_accessor,
783 std::vector<int64_t>* break_values,
784 std::vector<std::vector<int>>* resource_indices_per_group) {
796 std::iota(vehicles.begin(), vehicles.end(), 0);
806 SetValuesFromLP(index_to_cumul_variable_, global_offset, solver,
808 SetValuesFromLP(all_break_variables_, global_offset, solver, break_values);
809 SetResourceIndices(solver, resource_indices_per_group);
816 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
819 std::vector<int64_t>* break_values) {
820 if (resource ==
nullptr) {
832 std::vector<int64_t> costs_without_transits;
833 const std::vector<DimensionSchedulingStatus> statuses =
835 vehicle, next_accessor, {*resource}, {0},
836 true, solver, &costs_without_transits,
850 const int64_t local_offset =
852 SetValuesFromLP(current_route_cumul_variables_, local_offset, solver,
854 SetValuesFromLP(current_route_break_variables_, local_offset, solver,
878 for (
int vehicle : vehicles) {
880 index_to_cumul_variable_[
model->End(vehicle)], 1);
891 for (
int vehicle : vehicles) {
892 const int end_cumul_var = index_to_cumul_variable_[
model->End(vehicle)];
900 index_to_cumul_variable_[
model->Start(vehicle)], -1);
905void DimensionCumulOptimizerCore::InitOptimizer(
906 RoutingLinearSolverWrapper* solver) {
908 index_to_cumul_variable_.assign(dimension_->
cumuls().size(), -1);
909 max_end_cumul_ = solver->CreateNewPositiveVariable();
910 min_start_cumul_ = solver->CreateNewPositiveVariable();
913bool DimensionCumulOptimizerCore::ComputeRouteCumulBounds(
914 const std::vector<int64_t>& route,
915 const std::vector<int64_t>& fixed_transits, int64_t cumul_offset) {
916 const int route_size = route.size();
917 current_route_min_cumuls_.resize(route_size);
918 current_route_max_cumuls_.resize(route_size);
919 if (propagator_ !=
nullptr) {
920 for (
int pos = 0; pos < route_size; pos++) {
921 const int64_t node = route[pos];
922 current_route_min_cumuls_[pos] = propagator_->CumulMin(node);
923 DCHECK_GE(current_route_min_cumuls_[pos], 0);
924 current_route_max_cumuls_[pos] = propagator_->CumulMax(node);
925 DCHECK_GE(current_route_max_cumuls_[pos], current_route_min_cumuls_[pos]);
931 for (
int pos = 0; pos < route_size; ++pos) {
932 if (!GetCumulBoundsWithOffset(*dimension_, route[pos], cumul_offset,
933 ¤t_route_min_cumuls_[pos],
934 ¤t_route_max_cumuls_[pos])) {
941 for (
int pos = 1; pos < route_size; ++pos) {
942 const int64_t slack_min = dimension_->
SlackVar(route[pos - 1])->
Min();
943 current_route_min_cumuls_[pos] =
std::max(
944 current_route_min_cumuls_[pos],
946 CapAdd(current_route_min_cumuls_[pos - 1], fixed_transits[pos - 1]),
948 current_route_min_cumuls_[pos] = GetFirstPossibleValueForCumulWithOffset(
949 *dimension_, route[pos], current_route_min_cumuls_[pos], cumul_offset);
950 if (current_route_min_cumuls_[pos] > current_route_max_cumuls_[pos]) {
955 for (
int pos = route_size - 2; pos >= 0; --pos) {
958 if (current_route_max_cumuls_[pos + 1] <
960 const int64_t slack_min = dimension_->
SlackVar(route[pos])->
Min();
961 current_route_max_cumuls_[pos] =
std::min(
962 current_route_max_cumuls_[pos],
964 CapSub(current_route_max_cumuls_[pos + 1], fixed_transits[pos]),
966 current_route_max_cumuls_[pos] = GetLastPossibleValueForCumulWithOffset(
967 *dimension_, route[pos], current_route_max_cumuls_[pos],
969 if (current_route_max_cumuls_[pos] < current_route_min_cumuls_[pos]) {
977bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
978 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
979 int64_t cumul_offset,
bool optimize_costs,
980 RoutingLinearSolverWrapper* solver, int64_t* route_transit_cost,
981 int64_t* route_cost_offset) {
982 RoutingModel*
const model = dimension_->
model();
984 std::vector<int64_t> path;
986 int node =
model->Start(vehicle);
987 path.push_back(node);
988 while (!
model->IsEnd(node)) {
989 node = next_accessor(node);
990 path.push_back(node);
994 const int path_size = path.size();
996 std::vector<int64_t> fixed_transit(path_size - 1);
998 const std::function<int64_t(int64_t, int64_t)>& transit_accessor =
1000 for (
int pos = 1; pos < path_size; ++pos) {
1001 fixed_transit[pos - 1] = transit_accessor(path[pos - 1], path[pos]);
1005 if (!ComputeRouteCumulBounds(path, fixed_transit, cumul_offset)) {
1011 std::vector<int>& lp_cumuls = current_route_cumul_variables_;
1012 lp_cumuls.assign(path_size, -1);
1013 for (
int pos = 0; pos < path_size; ++pos) {
1014 const int lp_cumul = solver->CreateNewPositiveVariable();
1015 index_to_cumul_variable_[path[pos]] = lp_cumul;
1016 lp_cumuls[pos] = lp_cumul;
1017 if (!solver->SetVariableBounds(lp_cumul, current_route_min_cumuls_[pos],
1018 current_route_max_cumuls_[pos])) {
1021 const SortedDisjointIntervalList& forbidden =
1023 if (forbidden.NumIntervals() > 0) {
1024 std::vector<int64_t> starts;
1025 std::vector<int64_t> ends;
1026 for (
const ClosedInterval
interval :
1028 path[pos],
CapAdd(current_route_min_cumuls_[pos], cumul_offset),
1029 CapAdd(current_route_max_cumuls_[pos], cumul_offset))) {
1033 solver->SetVariableDisjointBounds(lp_cumul, starts, ends);
1037 std::vector<int> lp_slacks(path_size - 1, -1);
1038 for (
int pos = 0; pos < path_size - 1; ++pos) {
1039 const IntVar* cp_slack = dimension_->
SlackVar(path[pos]);
1040 lp_slacks[pos] = solver->CreateNewPositiveVariable();
1041 if (!solver->SetVariableBounds(lp_slacks[pos], cp_slack->Min(),
1051 for (
int pos = 0; pos < path_size - 1; ++pos) {
1053 solver->CreateNewConstraint(fixed_transit[pos], fixed_transit[pos]);
1054 solver->SetCoefficient(
ct, lp_cumuls[pos + 1], 1);
1055 solver->SetCoefficient(
ct, lp_cumuls[pos], -1);
1056 solver->SetCoefficient(
ct, lp_slacks[pos], -1);
1058 if (route_cost_offset !=
nullptr) *route_cost_offset = 0;
1059 if (optimize_costs) {
1061 for (
int pos = 0; pos < path_size; ++pos) {
1063 const int64_t
coef =
1065 if (
coef == 0)
continue;
1067 if (
bound < cumul_offset && route_cost_offset !=
nullptr) {
1069 *route_cost_offset =
CapAdd(*route_cost_offset,
1073 if (current_route_max_cumuls_[pos] <=
bound) {
1077 const int soft_ub_diff = solver->CreateNewPositiveVariable();
1078 solver->SetObjectiveCoefficient(soft_ub_diff,
coef);
1080 const int ct = solver->CreateNewConstraint(
1082 solver->SetCoefficient(
ct, lp_cumuls[pos], 1);
1083 solver->SetCoefficient(
ct, soft_ub_diff, -1);
1086 for (
int pos = 0; pos < path_size; ++pos) {
1088 const int64_t
coef =
1090 if (
coef == 0)
continue;
1091 const int64_t
bound = std::max<int64_t>(
1094 if (current_route_min_cumuls_[pos] >=
bound) {
1098 const int soft_lb_diff = solver->CreateNewPositiveVariable();
1099 solver->SetObjectiveCoefficient(soft_lb_diff,
coef);
1101 const int ct = solver->CreateNewConstraint(
1103 solver->SetCoefficient(
ct, lp_cumuls[pos], 1);
1104 solver->SetCoefficient(
ct, soft_lb_diff, 1);
1108 std::vector<int> visited_pairs;
1109 StoreVisitedPickupDeliveryPairsOnRoute(
1110 *dimension_, vehicle, next_accessor, &visited_pairs,
1111 &visited_pickup_delivery_indices_for_pair_);
1112 for (
int pair_index : visited_pairs) {
1113 const int64_t pickup_index =
1114 visited_pickup_delivery_indices_for_pair_[pair_index].first;
1115 const int64_t delivery_index =
1116 visited_pickup_delivery_indices_for_pair_[pair_index].second;
1117 visited_pickup_delivery_indices_for_pair_[pair_index] = {-1, -1};
1120 if (delivery_index < 0) {
1126 pair_index,
model->GetPickupIndexPairs(pickup_index)[0].second,
1127 model->GetDeliveryIndexPairs(delivery_index)[0].second);
1130 const int ct = solver->CreateNewConstraint(
1132 solver->SetCoefficient(
ct, index_to_cumul_variable_[delivery_index], 1);
1133 solver->SetCoefficient(
ct, index_to_cumul_variable_[pickup_index], -1);
1141 const int ct = solver->CreateNewConstraint(
1143 solver->SetCoefficient(
ct, lp_cumuls.back(), 1);
1144 solver->SetCoefficient(
ct, lp_cumuls.front(), -1);
1147 const int64_t span_cost_coef =
1149 if (optimize_costs && span_cost_coef > 0) {
1150 solver->SetObjectiveCoefficient(lp_cumuls.back(), span_cost_coef);
1151 solver->SetObjectiveCoefficient(lp_cumuls.front(), -span_cost_coef);
1155 SimpleBoundCosts::BoundCost bound_cost =
1158 bound_cost.cost > 0) {
1159 const int span_violation = solver->CreateNewPositiveVariable();
1161 const int violation = solver->CreateNewConstraint(
1163 solver->SetCoefficient(violation, lp_cumuls.back(), 1.0);
1164 solver->SetCoefficient(violation, lp_cumuls.front(), -1.0);
1165 solver->SetCoefficient(violation, span_violation, -1.0);
1167 solver->SetObjectiveCoefficient(span_violation, bound_cost.cost);
1175 solver->SetCoefficient(
ct, min_start_cumul_, 1);
1176 solver->SetCoefficient(
ct, lp_cumuls.front(), -1);
1179 solver->SetCoefficient(
ct, max_end_cumul_, 1);
1180 solver->SetCoefficient(
ct, lp_cumuls.back(), -1);
1183 if (route_transit_cost !=
nullptr) {
1184 if (optimize_costs && span_cost_coef > 0) {
1185 const int64_t total_fixed_transit = std::accumulate(
1186 fixed_transit.begin(), fixed_transit.end(), 0,
CapAdd);
1187 *route_transit_cost =
CapProd(total_fixed_transit, span_cost_coef);
1189 *route_transit_cost = 0;
1197 current_route_break_variables_.clear();
1199 const std::vector<IntervalVar*>& breaks =
1201 const int num_breaks = breaks.size();
1205 if (num_breaks == 0) {
1207 for (
const auto& distance_duration :
1209 maximum_route_span =
1210 std::min(maximum_route_span, distance_duration.first);
1213 const int ct = solver->CreateNewConstraint(
1215 solver->SetCoefficient(
ct, lp_cumuls.back(), 1);
1216 solver->SetCoefficient(
ct, lp_cumuls.front(), -1);
1223 std::vector<int64_t> pre_travel(path_size - 1, 0);
1224 std::vector<int64_t> post_travel(path_size - 1, 0);
1226 const int pre_travel_index =
1228 if (pre_travel_index != -1) {
1232 const int post_travel_index =
1234 if (post_travel_index != -1) {
1243 std::vector<int> lp_break_start;
1244 std::vector<int> lp_break_duration;
1245 std::vector<int> lp_break_end;
1246 if (solver->IsCPSATSolver()) {
1247 lp_break_start.resize(num_breaks, -1);
1248 lp_break_duration.resize(num_breaks, -1);
1249 lp_break_end.resize(num_breaks, -1);
1252 std::vector<int> slack_exact_lower_bound_ct(path_size - 1, -1);
1253 std::vector<int> slack_linear_lower_bound_ct(path_size - 1, -1);
1255 const int64_t vehicle_start_min = current_route_min_cumuls_.front();
1256 const int64_t vehicle_start_max = current_route_max_cumuls_.front();
1257 const int64_t vehicle_end_min = current_route_min_cumuls_.back();
1258 const int64_t vehicle_end_max = current_route_max_cumuls_.back();
1259 const int all_break_variables_offset =
1260 vehicle_to_all_break_variables_offset_[vehicle];
1261 for (
int br = 0; br < num_breaks; ++br) {
1262 const IntervalVar& break_var = *breaks[br];
1263 if (!break_var.MustBePerformed())
continue;
1264 const int64_t break_start_min =
CapSub(break_var.StartMin(), cumul_offset);
1265 const int64_t break_start_max =
CapSub(break_var.StartMax(), cumul_offset);
1266 const int64_t break_end_min =
CapSub(break_var.EndMin(), cumul_offset);
1267 const int64_t break_end_max =
CapSub(break_var.EndMax(), cumul_offset);
1268 const int64_t break_duration_min = break_var.DurationMin();
1269 const int64_t break_duration_max = break_var.DurationMax();
1272 if (solver->IsCPSATSolver()) {
1273 if (break_end_max <= vehicle_start_min ||
1274 vehicle_end_max <= break_start_min) {
1275 all_break_variables_[all_break_variables_offset + 2 * br] = -1;
1276 all_break_variables_[all_break_variables_offset + 2 * br + 1] = -1;
1277 current_route_break_variables_.push_back(-1);
1278 current_route_break_variables_.push_back(-1);
1281 lp_break_start[br] =
1282 solver->AddVariable(break_start_min, break_start_max);
1283 lp_break_end[br] = solver->AddVariable(break_end_min, break_end_max);
1284 lp_break_duration[br] =
1285 solver->AddVariable(break_duration_min, break_duration_max);
1287 solver->AddLinearConstraint(0, 0,
1288 {{lp_break_end[br], 1},
1289 {lp_break_start[br], -1},
1290 {lp_break_duration[br], -1}});
1292 all_break_variables_[all_break_variables_offset + 2 * br] =
1294 all_break_variables_[all_break_variables_offset + 2 * br + 1] =
1296 current_route_break_variables_.push_back(lp_break_start[br]);
1297 current_route_break_variables_.push_back(lp_break_end[br]);
1299 if (break_end_min <= vehicle_start_max ||
1300 vehicle_end_min <= break_start_max) {
1301 all_break_variables_[all_break_variables_offset + 2 * br] = -1;
1302 all_break_variables_[all_break_variables_offset + 2 * br + 1] = -1;
1303 current_route_break_variables_.push_back(-1);
1304 current_route_break_variables_.push_back(-1);
1312 const int break_in_one_slack_ct = solver->CreateNewConstraint(1, 1);
1314 if (solver->IsCPSATSolver()) {
1316 if (break_end_min <= vehicle_start_max) {
1317 const int ct = solver->AddLinearConstraint(
1319 {{lp_cumuls.front(), 1}, {lp_break_end[br], -1}});
1320 const int break_is_before_route = solver->AddVariable(0, 1);
1321 solver->SetEnforcementLiteral(
ct, break_is_before_route);
1322 solver->SetCoefficient(break_in_one_slack_ct, break_is_before_route, 1);
1325 if (vehicle_end_min <= break_start_max) {
1326 const int ct = solver->AddLinearConstraint(
1328 {{lp_break_start[br], 1}, {lp_cumuls.back(), -1}});
1329 const int break_is_after_route = solver->AddVariable(0, 1);
1330 solver->SetEnforcementLiteral(
ct, break_is_after_route);
1331 solver->SetCoefficient(break_in_one_slack_ct, break_is_after_route, 1);
1336 for (
int pos = 0; pos < path_size - 1; ++pos) {
1339 const int64_t slack_start_min =
1340 CapAdd(current_route_min_cumuls_[pos], pre_travel[pos]);
1341 if (slack_start_min > break_start_max)
break;
1342 const int64_t slack_end_max =
1343 CapSub(current_route_max_cumuls_[pos + 1], post_travel[pos]);
1344 if (break_end_min > slack_end_max)
continue;
1345 const int64_t slack_duration_max =
1347 current_route_min_cumuls_[pos]),
1348 fixed_transit[pos]),
1350 if (slack_duration_max < break_duration_min)
continue;
1357 const int break_in_slack = solver->AddVariable(0, 1);
1358 solver->SetCoefficient(break_in_one_slack_ct, break_in_slack, 1);
1359 if (slack_linear_lower_bound_ct[pos] == -1) {
1360 slack_linear_lower_bound_ct[pos] = solver->AddLinearConstraint(
1363 solver->SetCoefficient(slack_linear_lower_bound_ct[pos], break_in_slack,
1364 break_duration_min);
1365 if (solver->IsCPSATSolver()) {
1370 const int break_duration_in_slack =
1371 solver->AddVariable(0, slack_duration_max);
1372 solver->AddProductConstraint(break_duration_in_slack,
1373 {break_in_slack, lp_break_duration[br]});
1374 if (slack_exact_lower_bound_ct[pos] == -1) {
1375 slack_exact_lower_bound_ct[pos] = solver->AddLinearConstraint(
1378 solver->SetCoefficient(slack_exact_lower_bound_ct[pos],
1379 break_duration_in_slack, 1);
1382 const int break_start_after_current_ct = solver->AddLinearConstraint(
1384 {{lp_break_start[br], 1}, {lp_cumuls[pos], -1}});
1385 solver->SetEnforcementLiteral(break_start_after_current_ct,
1388 const int break_ends_before_next_ct = solver->AddLinearConstraint(
1390 {{lp_cumuls[pos + 1], 1}, {lp_break_end[br], -1}});
1391 solver->SetEnforcementLiteral(break_ends_before_next_ct,
1397 if (!solver->IsCPSATSolver())
return true;
1403 if (!
interval->MustBePerformed())
return true;
1406 for (
int br = 1; br < num_breaks; ++br) {
1407 solver->AddLinearConstraint(
1409 {{lp_break_end[br - 1], -1}, {lp_break_start[br], 1}});
1412 for (
const auto& distance_duration :
1414 const int64_t limit = distance_duration.first;
1415 const int64_t min_break_duration = distance_duration.second;
1443 int previous_cover = solver->AddVariable(
CapAdd(vehicle_start_min, limit),
1444 CapAdd(vehicle_start_max, limit));
1445 solver->AddLinearConstraint(limit, limit,
1446 {{previous_cover, 1}, {lp_cumuls.front(), -1}});
1447 for (
int br = 0; br < num_breaks; ++br) {
1448 if (lp_break_start[br] == -1)
continue;
1449 const int64_t break_end_min =
CapSub(breaks[br]->EndMin(), cumul_offset);
1450 const int64_t break_end_max =
CapSub(breaks[br]->EndMax(), cumul_offset);
1453 const int break_is_eligible = solver->AddVariable(0, 1);
1454 const int break_is_not_eligible = solver->AddVariable(0, 1);
1456 solver->AddLinearConstraint(
1457 1, 1, {{break_is_eligible, 1}, {break_is_not_eligible, 1}});
1458 const int positive_ct = solver->AddLinearConstraint(
1460 {{lp_break_end[br], 1}, {lp_break_start[br], -1}});
1461 solver->SetEnforcementLiteral(positive_ct, break_is_eligible);
1462 const int negative_ct = solver->AddLinearConstraint(
1464 {{lp_break_end[br], 1}, {lp_break_start[br], -1}});
1465 solver->SetEnforcementLiteral(negative_ct, break_is_not_eligible);
1472 const int break_cover = solver->AddVariable(
1475 const int limit_cover_ct = solver->AddLinearConstraint(
1476 limit, limit, {{break_cover, 1}, {lp_break_end[br], -1}});
1477 solver->SetEnforcementLiteral(limit_cover_ct, break_is_eligible);
1478 const int empty_cover_ct = solver->AddLinearConstraint(
1479 CapAdd(vehicle_start_min, limit),
CapAdd(vehicle_start_min, limit),
1480 {{break_cover, 1}});
1481 solver->SetEnforcementLiteral(empty_cover_ct, break_is_not_eligible);
1484 solver->AddVariable(
CapAdd(vehicle_start_min, limit),
1486 solver->AddMaximumConstraint(cover, {previous_cover, break_cover});
1489 const int route_end_is_not_covered = solver->AddReifiedLinearConstraint(
1491 {{lp_cumuls.back(), 1}, {previous_cover, -1}});
1492 const int break_start_cover_ct = solver->AddLinearConstraint(
1494 {{previous_cover, 1}, {lp_break_start[br], -1}});
1495 solver->SetEnforcementLiteral(break_start_cover_ct,
1496 route_end_is_not_covered);
1498 previous_cover = cover;
1501 {{previous_cover, 1}, {lp_cumuls.back(), -1}});
1507bool DimensionCumulOptimizerCore::SetGlobalConstraints(
1508 const std::function<int64_t(int64_t)>& next_accessor, int64_t cumul_offset,
1509 bool optimize_costs, RoutingLinearSolverWrapper* solver) {
1513 if (optimize_costs && global_span_coeff > 0) {
1514 solver->SetObjectiveCoefficient(max_end_cumul_, global_span_coeff);
1515 solver->SetObjectiveCoefficient(min_start_cumul_, -global_span_coeff);
1519 for (
const RoutingDimension::NodePrecedence& precedence :
1521 const int first_cumul_var = index_to_cumul_variable_[precedence.first_node];
1522 const int second_cumul_var =
1523 index_to_cumul_variable_[precedence.second_node];
1524 if (first_cumul_var < 0 || second_cumul_var < 0) {
1529 DCHECK_NE(first_cumul_var, second_cumul_var)
1530 <<
"Dimension " << dimension_->
name()
1531 <<
" has a self-precedence on node " << precedence.first_node <<
".";
1534 const int ct = solver->CreateNewConstraint(
1536 solver->SetCoefficient(
ct, second_cumul_var, 1);
1537 solver->SetCoefficient(
ct, first_cumul_var, -1);
1540 const RoutingModel&
model = *dimension_->
model();
1541 if (!solver->IsCPSATSolver()) {
1547 const int num_vehicles =
model.vehicles();
1548 const auto& resource_groups =
model.GetResourceGroups();
1549 for (
int rg_index :
model.GetDimensionResourceGroupIndices(dimension_)) {
1559 const ResourceGroup& resource_group = *resource_groups[rg_index];
1560 DCHECK(!resource_group.GetVehiclesRequiringAResource().empty());
1562 const std::vector<ResourceGroup::Resource>& resources =
1563 resource_group.GetResources();
1564 int num_required_resources = 0;
1565 static const int kNoConstraint = -1;
1568 std::vector<int> vehicle_constraints(
model.vehicles(), kNoConstraint);
1569 for (
int v : resource_group.GetVehiclesRequiringAResource()) {
1570 if (
model.IsEnd(next_accessor(
model.Start(v))) &&
1571 !
model.IsVehicleUsedWhenEmpty(v)) {
1575 num_required_resources++;
1576 vehicle_constraints[v] = solver->CreateNewConstraint(1, 1);
1580 const int num_resources = resources.size();
1581 std::vector<int> resource_constraints(num_resources, kNoConstraint);
1582 int num_available_resources = 0;
1583 for (
int r = 0; r < num_resources; r++) {
1584 const ResourceGroup::Attributes& attributes =
1585 resources[r].GetDimensionAttributes(dimension_);
1586 if (attributes.start_domain().Max() < cumul_offset ||
1587 attributes.end_domain().Max() < cumul_offset) {
1593 num_available_resources++;
1594 resource_constraints[r] = solver->CreateNewConstraint(0, 1);
1597 if (num_required_resources > num_available_resources) {
1602 std::vector<int>& resource_to_vehicle_assignment_variables =
1603 resource_group_to_resource_to_vehicle_assignment_variables_[rg_index];
1604 resource_to_vehicle_assignment_variables.assign(
1605 num_resources * num_vehicles, -1);
1611 for (
int r = 0; r < num_resources; r++) {
1612 if (resource_constraints[r] == kNoConstraint)
continue;
1613 const ResourceGroup::Attributes& attributes =
1614 resources[r].GetDimensionAttributes(dimension_);
1615 for (
int v : resource_group.GetVehiclesRequiringAResource()) {
1616 if (vehicle_constraints[v] == kNoConstraint)
continue;
1618 const int assign_r_to_v = solver->AddVariable(0, 1);
1619 resource_to_vehicle_assignment_variables[r * num_vehicles + v] =
1621 solver->SetCoefficient(vehicle_constraints[v], assign_r_to_v, 1);
1622 solver->SetCoefficient(resource_constraints[r], assign_r_to_v, 1);
1624 const auto& add_domain_constraint =
1625 [&solver, cumul_offset, assign_r_to_v](
const Domain& domain,
1626 int cumul_variable) {
1630 ClosedInterval cumul_bounds;
1631 if (!GetDomainOffsetBounds(domain, cumul_offset, &cumul_bounds)) {
1633 solver->SetVariableBounds(assign_r_to_v, 0, 0);
1636 const int cumul_constraint = solver->AddLinearConstraint(
1637 cumul_bounds.start, cumul_bounds.end, {{cumul_variable, 1}});
1638 solver->SetEnforcementLiteral(cumul_constraint, assign_r_to_v);
1640 add_domain_constraint(attributes.start_domain(),
1641 index_to_cumul_variable_[
model.Start(v)]);
1642 add_domain_constraint(attributes.end_domain(),
1643 index_to_cumul_variable_[
model.End(v)]);
1650void DimensionCumulOptimizerCore::SetValuesFromLP(
1651 const std::vector<int>& lp_variables, int64_t offset,
1652 RoutingLinearSolverWrapper* solver, std::vector<int64_t>* lp_values)
const {
1653 if (lp_values ==
nullptr)
return;
1655 for (
int i = 0; i < lp_variables.size(); i++) {
1656 const int lp_var = lp_variables[i];
1657 if (lp_var < 0)
continue;
1658 const double lp_value_double = solver->GetValue(lp_var);
1659 const int64_t lp_value_int64 =
1662 : MathUtil::FastInt64Round(lp_value_double);
1663 (*lp_values)[i] =
CapAdd(lp_value_int64, offset);
1667void DimensionCumulOptimizerCore::SetResourceIndices(
1668 RoutingLinearSolverWrapper* solver,
1669 std::vector<std::vector<int>>* resource_indices_per_group)
const {
1670 if (resource_indices_per_group ==
nullptr ||
1671 resource_group_to_resource_to_vehicle_assignment_variables_.empty()) {
1674 const RoutingModel&
model = *dimension_->model();
1675 const int num_vehicles =
model.vehicles();
1676 DCHECK(!
model.GetDimensionResourceGroupIndices(dimension_).empty());
1677 const auto& resource_groups =
model.GetResourceGroups();
1678 resource_indices_per_group->resize(resource_groups.size());
1679 for (
int rg_index :
model.GetDimensionResourceGroupIndices(dimension_)) {
1680 const ResourceGroup& resource_group = *resource_groups[rg_index];
1681 DCHECK(!resource_group.GetVehiclesRequiringAResource().empty());
1683 const int num_resources = resource_group.Size();
1684 std::vector<int>& resource_indices =
1685 resource_indices_per_group->at(rg_index);
1686 resource_indices.assign(num_vehicles, -1);
1688 const std::vector<int>& resource_to_vehicle_assignment_variables =
1689 resource_group_to_resource_to_vehicle_assignment_variables_[rg_index];
1690 DCHECK_EQ(resource_to_vehicle_assignment_variables.size(),
1691 num_resources * num_vehicles);
1692 for (
int v : resource_group.GetVehiclesRequiringAResource()) {
1693 for (
int r = 0; r < num_resources; r++) {
1694 const int assignment_var =
1695 resource_to_vehicle_assignment_variables[r * num_vehicles + v];
1696 if (assignment_var >= 0 && solver->GetValue(assignment_var) == 1) {
1698 resource_indices[v] = r;
1708GlobalDimensionCumulOptimizer::GlobalDimensionCumulOptimizer(
1710 RoutingSearchParameters::SchedulingSolver solver_type)
1711 : optimizer_core_(dimension,
1713 !dimension->GetNodePrecedences().empty()) {
1714 switch (solver_type) {
1715 case RoutingSearchParameters::SCHEDULING_GLOP: {
1716 solver_ = absl::make_unique<RoutingGlopWrapper>(
1720 GetGlopParametersForGlobalLP());
1723 case RoutingSearchParameters::SCHEDULING_CP_SAT: {
1724 solver_ = absl::make_unique<RoutingCPSatWrapper>();
1728 LOG(DFATAL) <<
"Unrecognized solver type: " << solver_type;
1734 const std::function<int64_t(int64_t)>& next_accessor,
1735 int64_t* optimal_cost_without_transits) {
1737 int64_t transit_cost = 0;
1739 optimizer_core_.
Optimize(next_accessor, solver_.get(),
nullptr,
nullptr,
1740 nullptr, &
cost, &transit_cost);
1742 optimal_cost_without_transits !=
nullptr) {
1743 *optimal_cost_without_transits =
CapSub(
cost, transit_cost);
1749 const std::function<int64_t(int64_t)>& next_accessor,
1750 std::vector<int64_t>* optimal_cumuls, std::vector<int64_t>* optimal_breaks,
1751 std::vector<std::vector<int>>* optimal_resource_indices) {
1752 return optimizer_core_.
Optimize(next_accessor, solver_.get(), optimal_cumuls,
1753 optimal_breaks, optimal_resource_indices,
1758 const std::function<int64_t(int64_t)>& next_accessor,
1759 std::vector<int64_t>* packed_cumuls, std::vector<int64_t>* packed_breaks,
1760 std::vector<std::vector<int>>* resource_indices) {
1762 packed_cumuls, packed_breaks,
1772 : optimizer_(*optimizer),
1773 mp_optimizer_(*mp_optimizer),
1774 model_(*optimizer->dimension()->
model()),
1775 resource_group_(*resource_group) {}
1778 int v,
const std::function<int64_t(int64_t)>& next_accessor,
1779 bool optimize_vehicle_costs, std::vector<int64_t>* assignment_costs,
1780 std::vector<std::vector<int64_t>>* cumul_values,
1781 std::vector<std::vector<int64_t>>* break_values) {
1784 (next_accessor(model_.
Start(v)) == model_.
End(v) &&
1786 assignment_costs->clear();
1795 const std::vector<ResourceGroup::Resource>& resources =
1797 const int num_resources = resources.size();
1798 std::vector<int> all_resource_indices(num_resources);
1799 std::iota(all_resource_indices.begin(), all_resource_indices.end(), 0);
1800 const bool use_mp_optimizer =
1804 use_mp_optimizer ? mp_optimizer_ : optimizer_;
1805 std::vector<DimensionSchedulingStatus> statuses =
1807 v, next_accessor, resources, all_resource_indices,
1808 optimize_vehicle_costs, assignment_costs, cumul_values, break_values);
1810 if (assignment_costs->empty()) {
1814 DCHECK_EQ(assignment_costs->size(), num_resources);
1815 DCHECK_EQ(statuses.size(), num_resources);
1816 DCHECK(cumul_values ==
nullptr || cumul_values->size() == num_resources);
1817 DCHECK(break_values ==
nullptr || break_values->size() == num_resources);
1819 if (use_mp_optimizer) {
1823 return absl::c_any_of(*assignment_costs,
1824 [](int64_t
cost) {
return cost >= 0; });
1827 std::vector<int> mp_optimizer_resource_indices;
1828 for (
int r = 0; r < num_resources; r++) {
1830 mp_optimizer_resource_indices.push_back(r);
1834 std::vector<int64_t> mp_assignment_costs;
1835 std::vector<std::vector<int64_t>> mp_cumul_values;
1836 std::vector<std::vector<int64_t>> mp_break_values;
1838 v, next_accessor, resources, mp_optimizer_resource_indices,
1839 optimize_vehicle_costs, &mp_assignment_costs,
1840 cumul_values ==
nullptr ?
nullptr : &mp_cumul_values,
1841 break_values ==
nullptr ?
nullptr : &mp_break_values);
1842 if (!mp_optimizer_resource_indices.empty() && mp_assignment_costs.empty()) {
1846 DCHECK_EQ(mp_assignment_costs.size(), mp_optimizer_resource_indices.size());
1847 DCHECK(cumul_values ==
nullptr ||
1848 mp_cumul_values.size() == mp_optimizer_resource_indices.size());
1849 DCHECK(break_values ==
nullptr ||
1850 mp_break_values.size() == mp_optimizer_resource_indices.size());
1851 for (
int i = 0; i < mp_optimizer_resource_indices.size(); i++) {
1852 assignment_costs->at(mp_optimizer_resource_indices[i]) =
1853 mp_assignment_costs[i];
1854 if (cumul_values !=
nullptr) {
1855 cumul_values->at(mp_optimizer_resource_indices[i])
1856 .swap(mp_cumul_values[i]);
1858 if (break_values !=
nullptr) {
1859 break_values->at(mp_optimizer_resource_indices[i])
1860 .swap(mp_break_values[i]);
1863 return absl::c_any_of(*assignment_costs,
1864 [](int64_t
cost) {
return cost >= 0; });
1868 const std::vector<std::vector<int64_t>>&
1869 primary_vehicle_to_resource_assignment_costs,
1870 const std::vector<std::vector<int64_t>>&
1871 secondary_vehicle_to_resource_assignment_costs,
1872 const std::function<
bool(
int)>& use_primary_for_vehicle,
1873 std::vector<int>* resource_indices)
const {
1874 const int num_vehicles = model_.
vehicles();
1875 DCHECK_EQ(primary_vehicle_to_resource_assignment_costs.size(), num_vehicles);
1876 DCHECK_EQ(secondary_vehicle_to_resource_assignment_costs.size(),
1878 const int num_resources = resource_group_.
Size();
1881 2 + num_vehicles + num_resources,
1882 num_vehicles + num_vehicles * num_resources +
1884 const int source_index = num_vehicles + num_resources;
1885 const int sink_index = source_index + 1;
1886 const auto resource_index = [num_vehicles](
int r) {
1887 return num_vehicles + r;
1890 std::vector<std::vector<ArcIndex>> vehicle_to_resource_arc_index;
1891 if (resource_indices !=
nullptr) {
1892 vehicle_to_resource_arc_index.resize(
1893 num_vehicles, std::vector<ArcIndex>(num_resources, -1));
1895 int num_used_vehicles = 0;
1897 DCHECK(use_primary_for_vehicle(v) ||
1898 primary_vehicle_to_resource_assignment_costs[v].empty());
1899 const std::vector<int64_t>& assignment_costs =
1900 use_primary_for_vehicle(v)
1901 ? primary_vehicle_to_resource_assignment_costs[v]
1902 : secondary_vehicle_to_resource_assignment_costs[v];
1903 if (assignment_costs.empty()) {
1907 DCHECK_EQ(assignment_costs.size(), num_resources);
1908 num_used_vehicles++;
1909 DCHECK_LE(num_used_vehicles, num_resources)
1910 << num_used_vehicles <<
" used vehicles and only " << num_resources
1911 <<
" resources available!";
1917 bool has_feasible_resource =
false;
1918 for (
int r = 0; r < num_resources; r++) {
1919 const int64_t assignment_cost = assignment_costs[r];
1920 if (assignment_cost < 0)
continue;
1922 v, resource_index(r), 1, assignment_cost);
1923 if (!vehicle_to_resource_arc_index.empty()) {
1924 vehicle_to_resource_arc_index[v][r] = arc_index;
1926 has_feasible_resource =
true;
1928 DCHECK(has_feasible_resource)
1929 <<
"No feasible resource for vehicle " << v
1930 <<
", should've been caught by ComputeAssignmentCostsForVehicle()";
1934 for (
int r = 0; r < num_resources; r++) {
1944 if (resource_indices !=
nullptr) resource_indices->clear();
1949 if (resource_indices ==
nullptr) {
1954 resource_indices->assign(num_vehicles, -1);
1956 for (
int r = 0; r < num_resources; r++) {
1957 if (vehicle_to_resource_arc_index[v][r] >= 0 &&
1958 flow.
Flow(vehicle_to_resource_arc_index[v][r]) > 0) {
1959 resource_indices->at(v) = r;
#define DCHECK_LE(val1, val2)
#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)
std::vector< DimensionSchedulingStatus > OptimizeSingleRouteWithResources(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, const std::vector< RoutingModel::ResourceGroup::Resource > &resources, const std::vector< int > &resource_indices, bool optimize_vehicle_costs, RoutingLinearSolverWrapper *solver, std::vector< int64_t > *costs_without_transits, std::vector< std::vector< int64_t > > *cumul_values, std::vector< std::vector< int64_t > > *break_values, bool clear_lp=true)
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, std::vector< std::vector< int > > *resource_indices_per_group, int64_t *cost, int64_t *transit_cost, bool clear_lp=true)
DimensionCumulOptimizerCore(const RoutingDimension *dimension, bool use_precedence_propagator)
const RoutingDimension * dimension() const
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, std::vector< std::vector< int > > *resource_indices_per_group)
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)
DimensionSchedulingStatus OptimizeAndPackSingleRoute(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, const RoutingModel::ResourceGroup::Resource *resource, RoutingLinearSolverWrapper *solver, std::vector< int64_t > *cumul_values, std::vector< int64_t > *break_values)
We call domain any subset of Int64 = [kint64min, kint64max].
static Domain AllValues()
Returns the full domain Int64.
int64_t Min() const
Returns the min value of the domain.
int64_t Max() const
Returns the max value of the domain.
DimensionSchedulingStatus ComputeCumuls(const std::function< int64_t(int64_t)> &next_accessor, std::vector< int64_t > *optimal_cumuls, std::vector< int64_t > *optimal_breaks, std::vector< std::vector< int > > *optimal_resource_indices_per_group)
DimensionSchedulingStatus ComputeCumulCostWithoutFixedTransits(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, std::vector< std::vector< int > > *resource_indices_per_group)
const RoutingDimension * dimension() const
virtual int64_t Min() const =0
virtual int64_t Max() const =0
DimensionSchedulingStatus ComputePackedRouteCumuls(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, const RoutingModel::ResourceGroup::Resource *resource, std::vector< int64_t > *packed_cumuls, std::vector< int64_t > *packed_breaks)
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
std::vector< DimensionSchedulingStatus > ComputeRouteCumulCostsForResourcesWithoutFixedTransits(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, const std::vector< RoutingModel::ResourceGroup::Resource > &resources, const std::vector< int > &resource_indices, bool optimize_vehicle_costs, std::vector< int64_t > *optimal_costs_without_transits, std::vector< std::vector< int64_t > > *optimal_cumuls, std::vector< std::vector< int64_t > > *optimal_breaks)
static int64_t FastInt64Round(double x)
ResourceAssignmentOptimizer(const RoutingModel::ResourceGroup *resource_group, LocalDimensionCumulOptimizer *optimizer, LocalDimensionCumulOptimizer *mp_optimizer)
const RoutingDimension *const dimension() const
bool ComputeAssignmentCostsForVehicle(int v, const std::function< int64_t(int64_t)> &next_accessor, bool optimize_vehicle_costs, std::vector< int64_t > *assignment_costs, std::vector< std::vector< int64_t > > *cumul_values, std::vector< std::vector< int64_t > > *break_values)
int64_t ComputeBestAssignmentCost(const std::vector< std::vector< int64_t > > &primary_vehicle_to_resource_assignment_costs, const std::vector< std::vector< int64_t > > &secondary_vehicle_to_resource_assignment_costs, const std::function< bool(int)> &use_primary_for_vehicle, std::vector< int > *resource_indices) const
Dimensions represent quantities accumulated at nodes along the routes.
SimpleBoundCosts::BoundCost GetSoftSpanUpperBoundForVehicle(int vehicle) const
const std::string & name() const
Returns the name of the dimension.
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.
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.
IntVar * SlackVar(int64_t index) const
int64_t GetSpanCostCoefficientForVehicle(int vehicle) const
int64_t global_span_cost_coefficient() 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 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.
RoutingModel * model() const
Returns the model on which the dimension was created.
const std::vector< IntervalVar * > & GetBreakIntervalsOfVehicle(int vehicle) const
Returns the break intervals set by SetBreakIntervalsOfVehicle().
const std::vector< NodePrecedence > & GetNodePrecedences() const
const std::vector< SortedDisjointIntervalList > & forbidden_intervals() const
Returns forbidden intervals for each node.
int64_t GetLocalOptimizerOffsetForVehicle(int vehicle) 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.
virtual int64_t GetObjectiveValue() const =0
virtual void AddObjectiveConstraint()=0
virtual void ClearObjective()=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
A Resource sets attributes (costs/constraints) for a set of dimensions.
A ResourceGroup defines a set of available Resources with attributes on one or multiple dimensions.
const std::vector< int > & GetVehiclesRequiringAResource() const
bool VehicleRequiresAResource(int vehicle) const
const std::vector< Resource > & GetResources() const
const std::vector< int > & GetDimensionResourceGroupIndices(const RoutingDimension *dimension) const
Returns the indices of resource groups for this dimension.
const IndexPairs & GetPickupAndDeliveryPairs() const
Returns pickup and delivery pairs currently in the model.
bool IsVehicleUsedWhenEmpty(int vehicle) const
bool CheckLimit()
Returns true if the search limit has been crossed.
int64_t Start(int vehicle) const
Model inspection.
int vehicles() const
Returns the number of vehicle routes in the model.
int64_t End(int vehicle) const
Returns the variable index of the ending node of a vehicle route.
ArcIndex AddArcWithCapacityAndUnitCost(NodeIndex tail, NodeIndex head, FlowQuantity capacity, CostValue unit_cost)
FlowQuantity Flow(ArcIndex arc) const
void SetNodeSupply(NodeIndex node, FlowQuantity supply)
CostValue OptimalCost() const
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
Represents a closed interval [start, end].