30#include "absl/algorithm/container.h"
31#include "absl/container/flat_hash_map.h"
32#include "absl/container/flat_hash_set.h"
33#include "absl/log/check.h"
34#include "absl/strings/str_format.h"
35#include "absl/strings/string_view.h"
36#include "absl/time/time.h"
37#include "absl/types/span.h"
45#include "ortools/constraint_solver/routing_parameters.pb.h"
46#include "ortools/glop/parameters.pb.h"
49#include "ortools/sat/cp_model.pb.h"
61glop::GlopParameters GetGlopParametersForLocalLP() {
68glop::GlopParameters GetGlopParametersForGlobalLP() {
74bool GetCumulBoundsWithOffset(
const RoutingDimension& dimension,
75 int64_t node_index, int64_t cumul_offset,
80 const IntVar& cumul_var = *dimension.CumulVar(node_index);
86 const int64_t first_after_offset =
87 std::max(dimension.GetFirstPossibleGreaterOrEqualValueForNode(
88 node_index, cumul_offset),
90 DCHECK_LT(first_after_offset, std::numeric_limits<int64_t>::max());
94 if (*
upper_bound == std::numeric_limits<int64_t>::max()) {
102int64_t GetFirstPossibleValueForCumulWithOffset(
103 const RoutingDimension& dimension, int64_t node_index,
104 int64_t lower_bound_without_offset, int64_t cumul_offset) {
106 dimension.GetFirstPossibleGreaterOrEqualValueForNode(
107 node_index,
CapAdd(lower_bound_without_offset, cumul_offset)),
111int64_t GetLastPossibleValueForCumulWithOffset(
112 const RoutingDimension& dimension, int64_t node_index,
113 int64_t upper_bound_without_offset, int64_t cumul_offset) {
115 dimension.GetLastPossibleLessOrEqualValueForNode(
116 node_index,
CapAdd(upper_bound_without_offset, cumul_offset)),
125void StoreVisitedPickupDeliveryPairsOnRoute(
126 const RoutingDimension& dimension,
int vehicle,
127 const std::function<int64_t(int64_t)>& next_accessor,
128 std::vector<int>* visited_pairs,
129 std::vector<std::pair<int64_t, int64_t>>*
130 visited_pickup_delivery_indices_for_pair) {
132 DCHECK_EQ(visited_pickup_delivery_indices_for_pair->size(),
133 dimension.model()->GetPickupAndDeliveryPairs().size());
134 DCHECK(std::all_of(visited_pickup_delivery_indices_for_pair->begin(),
135 visited_pickup_delivery_indices_for_pair->end(),
136 [](std::pair<int64_t, int64_t> p) {
137 return p.first == -1 && p.second == -1;
139 visited_pairs->clear();
140 if (!dimension.HasPickupToDeliveryLimits()) {
143 const RoutingModel&
model = *dimension.model();
145 int64_t node_index =
model.Start(vehicle);
146 while (!
model.IsEnd(node_index)) {
147 const std::vector<RoutingModel::PickupDeliveryPosition>& pickup_positions =
148 model.GetPickupPositions(node_index);
149 const std::vector<RoutingModel::PickupDeliveryPosition>&
150 delivery_positions =
model.GetDeliveryPositions(node_index);
151 if (!pickup_positions.empty()) {
154 DCHECK(delivery_positions.empty());
155 DCHECK_EQ(pickup_positions.size(), 1);
156 const int pair_index = pickup_positions[0].pd_pair_index;
157 (*visited_pickup_delivery_indices_for_pair)[pair_index].first =
159 visited_pairs->push_back(pair_index);
160 }
else if (!delivery_positions.empty()) {
164 DCHECK_EQ(delivery_positions.size(), 1);
165 const int pair_index = delivery_positions[0].pd_pair_index;
166 std::pair<int64_t, int64_t>& pickup_delivery_index =
167 (*visited_pickup_delivery_indices_for_pair)[pair_index];
168 if (pickup_delivery_index.first < 0) {
171 node_index = next_accessor(node_index);
174 pickup_delivery_index.second = node_index;
176 node_index = next_accessor(node_index);
185 const RoutingDimension* dimension,
186 RoutingSearchParameters::SchedulingSolver solver_type)
187 : optimizer_core_(dimension, false) {
190 const int vehicles =
dimension->model()->vehicles();
191 solver_.resize(vehicles);
192 switch (solver_type) {
193 case RoutingSearchParameters::SCHEDULING_GLOP: {
194 const glop::GlopParameters
parameters = GetGlopParametersForLocalLP();
195 for (
int vehicle = 0; vehicle < vehicles; ++vehicle) {
199 std::make_unique<RoutingGlopWrapper>(
false,
parameters);
203 case RoutingSearchParameters::SCHEDULING_CP_SAT: {
204 for (
int vehicle = 0; vehicle < vehicles; ++vehicle) {
205 solver_[vehicle] = std::make_unique<RoutingCPSatWrapper>();
210 LOG(DFATAL) <<
"Unrecognized solver type: " << solver_type;
215 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
216 int64_t* optimal_cost) {
217 int64_t transit_cost = 0;
220 vehicle, next_accessor,
223 optimal_cost !=
nullptr,
224 solver_[vehicle].get(),
nullptr,
225 nullptr, optimal_cost, &transit_cost);
227 optimal_cost !=
nullptr) {
228 DCHECK_GE(*optimal_cost, 0);
229 *optimal_cost =
CapAdd(*optimal_cost, transit_cost);
236 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
237 int64_t* optimal_cost_without_transits) {
239 vehicle, next_accessor,
242 optimal_cost_without_transits !=
nullptr,
243 solver_[vehicle].get(),
nullptr,
244 nullptr, optimal_cost_without_transits,
nullptr);
249 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
250 const std::function<int64_t(int64_t, int64_t)>& transit_accessor,
251 const std::vector<RoutingModel::ResourceGroup::Resource>& resources,
252 const std::vector<int>& resource_indices,
bool optimize_vehicle_costs,
253 std::vector<int64_t>* optimal_costs_without_transits,
254 std::vector<std::vector<int64_t>>* optimal_cumuls,
255 std::vector<std::vector<int64_t>>* optimal_breaks) {
257 vehicle, next_accessor, transit_accessor, {}, resources, resource_indices,
258 optimize_vehicle_costs, solver_[vehicle].get(), optimal_cumuls,
259 optimal_breaks, optimal_costs_without_transits,
nullptr);
263 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
264 const RoutingModel::RouteDimensionTravelInfo& dimension_travel_info,
265 const RoutingModel::ResourceGroup::Resource* resource,
266 std::vector<int64_t>* optimal_cumuls,
267 std::vector<int64_t>* optimal_breaks) {
269 vehicle, next_accessor, dimension_travel_info, resource,
270 true, solver_[vehicle].get(), optimal_cumuls,
271 optimal_breaks,
nullptr,
nullptr);
276 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
277 const RoutingModel::RouteDimensionTravelInfo& dimension_travel_info,
278 std::vector<int64_t>* optimal_cumuls, std::vector<int64_t>* optimal_breaks,
279 int64_t* optimal_cost_without_transits) {
281 vehicle, next_accessor, dimension_travel_info,
nullptr,
282 true, solver_[vehicle].get(), optimal_cumuls,
283 optimal_breaks, optimal_cost_without_transits,
nullptr);
288 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
289 const RoutingModel::RouteDimensionTravelInfo& dimension_travel_info,
290 const std::vector<int64_t>& solution_cumul_values,
291 const std::vector<int64_t>& solution_break_values, int64_t* solution_cost,
292 int64_t* cost_offset,
bool reuse_previous_model_if_possible,
bool clear_lp,
293 absl::Duration* solve_duration) {
296 vehicle, next_accessor, dimension_travel_info, solver,
297 solution_cumul_values, solution_break_values, solution_cost, cost_offset,
298 reuse_previous_model_if_possible, clear_lp,
299 true, solve_duration);
304 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
305 const RoutingModel::RouteDimensionTravelInfo& dimension_travel_info,
306 const RoutingModel::ResourceGroup::Resource* resource,
307 std::vector<int64_t>* packed_cumuls, std::vector<int64_t>* packed_breaks) {
309 vehicle, next_accessor, dimension_travel_info, resource,
310 solver_[vehicle].get(), packed_cumuls, packed_breaks);
313const int CumulBoundsPropagator::kNoParent = -2;
314const int CumulBoundsPropagator::kParentToBePropagated = -1;
317 : dimension_(*dimension), num_nodes_(2 * dimension->cumuls().
size()) {
318 outgoing_arcs_.resize(num_nodes_);
319 node_in_queue_.resize(num_nodes_,
false);
320 tree_parent_node_of_.resize(num_nodes_, kNoParent);
321 propagated_bounds_.resize(num_nodes_);
322 visited_pickup_delivery_indices_for_pair_.resize(
323 dimension->model()->GetPickupAndDeliveryPairs().size(), {-1, -1});
326void CumulBoundsPropagator::AddArcs(
int first_index,
int second_index,
329 outgoing_arcs_[PositiveNode(first_index)].push_back(
330 {PositiveNode(second_index), offset});
331 AddNodeToQueue(PositiveNode(first_index));
333 outgoing_arcs_[NegativeNode(second_index)].push_back(
334 {NegativeNode(first_index), offset});
335 AddNodeToQueue(NegativeNode(second_index));
338bool CumulBoundsPropagator::InitializeArcsAndBounds(
339 const std::function<int64_t(int64_t)>& next_accessor, int64_t cumul_offset,
340 const std::vector<RoutingModel::RouteDimensionTravelInfo>*
341 dimension_travel_info_per_route) {
342 propagated_bounds_.assign(num_nodes_, std::numeric_limits<int64_t>::min());
344 for (std::vector<ArcInfo>& arcs : outgoing_arcs_) {
348 RoutingModel*
const model = dimension_.model();
349 std::vector<int64_t>&
lower_bounds = propagated_bounds_;
351 for (
int vehicle = 0; vehicle <
model->vehicles(); vehicle++) {
352 const std::function<int64_t(int64_t, int64_t)>& transit_accessor =
353 dimension_.transit_evaluator(vehicle);
355 int node =
model->Start(vehicle);
356 int index_on_route = 0;
358 int64_t cumul_lb, cumul_ub;
359 if (!GetCumulBoundsWithOffset(dimension_, node, cumul_offset, &cumul_lb,
364 if (cumul_ub < std::numeric_limits<int64_t>::max()) {
368 if (
model->IsEnd(node)) {
372 const int next = next_accessor(node);
373 int64_t transit = transit_accessor(node,
next);
374 if (dimension_travel_info_per_route !=
nullptr &&
375 !dimension_travel_info_per_route->empty()) {
376 const RoutingModel::RouteDimensionTravelInfo::TransitionInfo&
377 transition_info = (*dimension_travel_info_per_route)[vehicle]
378 .transition_info[index_on_route];
379 transit = transition_info.compressed_travel_value_lower_bound +
380 transition_info.pre_travel_transit_value +
381 transition_info.post_travel_transit_value;
384 const IntVar& slack_var = *dimension_.SlackVar(node);
387 AddArcs(node,
next,
CapAdd(transit, slack_var.Min()));
388 if (slack_var.Max() < std::numeric_limits<int64_t>::max()) {
390 AddArcs(
next, node,
CapSub(-slack_var.Max(), transit));
397 const int64_t span_ub = dimension_.GetSpanUpperBoundForVehicle(vehicle);
398 if (span_ub < std::numeric_limits<int64_t>::max()) {
399 AddArcs(
model->End(vehicle),
model->Start(vehicle), -span_ub);
403 std::vector<int> visited_pairs;
404 StoreVisitedPickupDeliveryPairsOnRoute(
405 dimension_, vehicle, next_accessor, &visited_pairs,
406 &visited_pickup_delivery_indices_for_pair_);
407 for (
int pair_index : visited_pairs) {
408 const int64_t pickup_index =
409 visited_pickup_delivery_indices_for_pair_[pair_index].first;
410 const int64_t delivery_index =
411 visited_pickup_delivery_indices_for_pair_[pair_index].second;
412 visited_pickup_delivery_indices_for_pair_[pair_index] = {-1, -1};
414 DCHECK_GE(pickup_index, 0);
415 if (delivery_index < 0) {
420 const int64_t limit = dimension_.GetPickupToDeliveryLimitForPair(
422 model->GetPickupPositions(pickup_index)[0].alternative_index,
423 model->GetDeliveryPositions(delivery_index)[0].alternative_index);
424 if (limit < std::numeric_limits<int64_t>::max()) {
426 AddArcs(delivery_index, pickup_index, -limit);
431 for (
const RoutingDimension::NodePrecedence& precedence :
432 dimension_.GetNodePrecedences()) {
433 const int first_index = precedence.first_node;
434 const int second_index = precedence.second_node;
436 std::numeric_limits<int64_t>::min() ||
438 std::numeric_limits<int64_t>::min()) {
442 AddArcs(first_index, second_index, precedence.offset);
448bool CumulBoundsPropagator::UpdateCurrentLowerBoundOfNode(
int node,
451 const int cumul_var_index = node / 2;
453 if (node == PositiveNode(cumul_var_index)) {
455 propagated_bounds_[node] = GetFirstPossibleValueForCumulWithOffset(
456 dimension_, cumul_var_index, new_lb, offset);
459 const int64_t new_ub =
CapSub(0, new_lb);
460 propagated_bounds_[node] =
461 CapSub(0, GetLastPossibleValueForCumulWithOffset(
462 dimension_, cumul_var_index, new_ub, offset));
466 const int64_t cumul_lower_bound =
467 propagated_bounds_[PositiveNode(cumul_var_index)];
469 const int64_t negated_cumul_upper_bound =
470 propagated_bounds_[NegativeNode(cumul_var_index)];
472 return CapAdd(negated_cumul_upper_bound, cumul_lower_bound) <= 0;
475bool CumulBoundsPropagator::DisassembleSubtree(
int source,
int target) {
476 tmp_dfs_stack_.clear();
477 tmp_dfs_stack_.push_back(source);
478 while (!tmp_dfs_stack_.empty()) {
479 const int tail = tmp_dfs_stack_.back();
480 tmp_dfs_stack_.pop_back();
481 for (
const ArcInfo&
arc : outgoing_arcs_[
tail]) {
482 const int child_node =
arc.head;
483 if (tree_parent_node_of_[child_node] !=
tail)
continue;
484 if (child_node == target)
return false;
485 tree_parent_node_of_[child_node] = kParentToBePropagated;
486 tmp_dfs_stack_.push_back(child_node);
493 const std::function<int64_t(int64_t)>& next_accessor, int64_t cumul_offset,
494 const std::vector<RoutingModel::RouteDimensionTravelInfo>*
495 dimension_travel_info_per_route) {
496 tree_parent_node_of_.assign(num_nodes_, kNoParent);
497 DCHECK(std::none_of(node_in_queue_.begin(), node_in_queue_.end(),
498 [](
bool b) { return b; }));
499 DCHECK(bf_queue_.empty());
501 if (!InitializeArcsAndBounds(next_accessor, cumul_offset,
502 dimension_travel_info_per_route)) {
503 return CleanupAndReturnFalse();
506 std::vector<int64_t>& current_lb = propagated_bounds_;
509 while (!bf_queue_.empty()) {
510 const int node = bf_queue_.front();
511 bf_queue_.pop_front();
512 node_in_queue_[node] =
false;
514 if (tree_parent_node_of_[node] == kParentToBePropagated) {
521 for (
const ArcInfo&
arc : outgoing_arcs_[node]) {
524 const int64_t induced_lb =
525 (
lower_bound == std::numeric_limits<int64_t>::min())
526 ? std::numeric_limits<int64_t>::min()
529 const int head_node =
arc.head;
530 if (induced_lb <= current_lb[head_node]) {
535 if (!UpdateCurrentLowerBoundOfNode(head_node, induced_lb, cumul_offset) ||
536 !DisassembleSubtree(head_node, node)) {
539 return CleanupAndReturnFalse();
542 tree_parent_node_of_[head_node] = node;
543 AddNodeToQueue(head_node);
550 const RoutingDimension* dimension,
bool use_precedence_propagator)
551 : dimension_(dimension),
552 visited_pickup_delivery_indices_for_pair_(
553 dimension->
model()->GetPickupAndDeliveryPairs().
size(), {-1, -1}) {
554 if (use_precedence_propagator) {
555 propagator_ = std::make_unique<CumulBoundsPropagator>(dimension);
557 const RoutingModel&
model = *dimension_->model();
558 if (dimension_->HasBreakConstraints()) {
562 const int num_vehicles =
model.vehicles();
563 vehicle_to_all_break_variables_offset_.reserve(num_vehicles);
564 int num_break_vars = 0;
565 for (
int vehicle = 0; vehicle < num_vehicles; ++vehicle) {
566 vehicle_to_all_break_variables_offset_.push_back(num_break_vars);
567 const auto& intervals = dimension_->GetBreakIntervalsOfVehicle(vehicle);
568 num_break_vars += 2 * intervals.size();
570 all_break_variables_.resize(num_break_vars, -1);
572 if (!
model.GetDimensionResourceGroupIndices(dimension_).empty()) {
573 resource_group_to_resource_to_vehicle_assignment_variables_.resize(
574 model.GetResourceGroups().size());
580 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
581 const RouteDimensionTravelInfo& dimension_travel_info,
583 absl::Span<const int64_t> solution_cumul_values,
584 absl::Span<const int64_t> solution_break_values,
585 int64_t* cost_without_transits, int64_t* cost_offset,
586 bool reuse_previous_model_if_possible,
bool clear_lp,
587 bool clear_solution_constraints, absl::Duration*
const solve_duration) {
588 absl::Duration solve_duration_value;
589 int64_t cost_offset_value;
590 if (!reuse_previous_model_if_possible || solver->
ModelIsEmpty()) {
591 InitOptimizer(solver);
594 DCHECK_EQ(propagator_.get(),
nullptr);
596 RoutingModel*
const model = dimension_->model();
597 const bool optimize_vehicle_costs =
598 !
model->IsEnd(next_accessor(
model->Start(vehicle))) ||
599 model->IsVehicleUsedWhenEmpty(vehicle);
600 if (!SetRouteCumulConstraints(
601 vehicle, next_accessor, dimension_->transit_evaluator(vehicle),
602 dimension_travel_info,
603 dimension_->GetLocalOptimizerOffsetForVehicle(vehicle),
604 optimize_vehicle_costs, solver,
nullptr, &cost_offset_value)) {
607 if (
model->CheckLimit()) {
610 solve_duration_value =
model->RemainingTime();
611 if (solve_duration !=
nullptr) *solve_duration = solve_duration_value;
612 if (cost_offset !=
nullptr) *cost_offset = cost_offset_value;
614 CHECK(cost_offset !=
nullptr)
615 <<
"Cannot reuse model without the cost_offset";
616 cost_offset_value = *cost_offset;
617 CHECK(solve_duration !=
nullptr)
618 <<
"Cannot reuse model without the solve_duration";
619 solve_duration_value = *solve_duration;
623 DCHECK_EQ(solution_cumul_values.size(),
624 current_route_cumul_variables_.size());
625 for (
int i = 0; i < current_route_cumul_variables_.size(); ++i) {
626 if (solution_cumul_values[i] < current_route_min_cumuls_[i] ||
627 solution_cumul_values[i] > current_route_max_cumuls_[i]) {
631 solution_cumul_values[i],
632 solution_cumul_values[i]);
636 DCHECK_EQ(solution_break_values.size(),
637 current_route_break_variables_.size());
638 std::vector<int64_t> current_route_min_breaks(
639 current_route_break_variables_.size());
640 std::vector<int64_t> current_route_max_breaks(
641 current_route_break_variables_.size());
642 for (
int i = 0; i < current_route_break_variables_.size(); ++i) {
643 current_route_min_breaks[i] =
645 current_route_max_breaks[i] =
648 solution_break_values[i],
649 solution_break_values[i]);
658 if (cost_without_transits !=
nullptr) {
659 *cost_without_transits =
665 }
else if (clear_solution_constraints) {
666 for (
int i = 0; i < current_route_cumul_variables_.size(); ++i) {
668 current_route_min_cumuls_[i],
669 current_route_max_cumuls_[i]);
671 for (
int i = 0; i < current_route_break_variables_.size(); ++i) {
673 current_route_min_breaks[i],
674 current_route_max_breaks[i]);
682void ClearIfNonNull(std::vector<T>* v) {
691 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
692 const RouteDimensionTravelInfo& dimension_travel_info,
693 const RoutingModel::ResourceGroup::Resource* resource,
696 int64_t* cost_without_transits, int64_t* transit_cost,
bool clear_lp) {
697 if (cost_without_transits !=
nullptr) *cost_without_transits = -1;
701 const std::vector<Resource> resources =
702 resource ==
nullptr ? std::vector<Resource>()
703 : std::vector<Resource>({*resource});
704 const std::vector<int> resource_indices =
705 resource ==
nullptr ? std::vector<int>() : std::vector<int>({0});
706 std::vector<int64_t> costs_without_transits;
707 std::vector<std::vector<int64_t>> cumul_values_vec;
708 std::vector<std::vector<int64_t>> break_values_vec;
709 const std::vector<DimensionSchedulingStatus> statuses =
711 vehicle, next_accessor, dimension_->transit_evaluator(vehicle),
712 dimension_travel_info, resources, resource_indices,
713 optimize_vehicle_costs, solver,
716 cost_without_transits !=
nullptr ? &costs_without_transits :
nullptr,
717 transit_cost, clear_lp);
722 DCHECK_EQ(statuses.size(), 1);
727 if (cost_without_transits !=
nullptr) {
728 *cost_without_transits = costs_without_transits[0];
738using ResourceGroup = RoutingModel::ResourceGroup;
740bool GetDomainOffsetBounds(
const Domain& domain, int64_t offset,
743 std::max<int64_t>(
CapSub(domain.
Min(), offset), 0);
745 domain.
Max() == std::numeric_limits<int64_t>::max()
746 ? std::numeric_limits<int64_t>::max()
747 :
CapSub(domain.Max(), offset);
754bool GetIntervalIntersectionWithOffsetDomain(
const ClosedInterval&
interval,
755 const Domain& domain,
757 ClosedInterval* intersection) {
758 ClosedInterval domain_bounds;
759 if (!GetDomainOffsetBounds(domain, offset, &domain_bounds)) {
762 const int64_t intersection_lb = std::max(
interval.start, domain_bounds.start);
763 const int64_t intersection_ub = std::min(
interval.end, domain_bounds.end);
764 if (intersection_lb > intersection_ub)
return false;
766 *intersection = ClosedInterval(intersection_lb, intersection_ub);
770ClosedInterval GetVariableBounds(
int index,
771 const RoutingLinearSolverWrapper& solver) {
772 return ClosedInterval(solver.GetVariableLowerBound(
index),
773 solver.GetVariableUpperBound(
index));
776bool TightenStartEndVariableBoundsWithResource(
777 const RoutingDimension& dimension,
const ResourceGroup::Resource& resource,
778 const ClosedInterval& start_bounds,
int start_index,
779 const ClosedInterval& end_bounds,
int end_index, int64_t offset,
780 RoutingLinearSolverWrapper* solver) {
781 const ResourceGroup::Attributes& attributes =
782 resource.GetDimensionAttributes(&dimension);
783 ClosedInterval new_start_bounds;
784 ClosedInterval new_end_bounds;
785 return GetIntervalIntersectionWithOffsetDomain(start_bounds,
786 attributes.start_domain(),
787 offset, &new_start_bounds) &&
788 solver->SetVariableBounds(start_index, new_start_bounds.start,
789 new_start_bounds.end) &&
790 GetIntervalIntersectionWithOffsetDomain(
791 end_bounds, attributes.end_domain(), offset, &new_end_bounds) &&
792 solver->SetVariableBounds(end_index, new_end_bounds.start,
796bool TightenStartEndVariableBoundsWithAssignedResources(
797 const RoutingDimension& dimension,
int v,
int start_index,
int end_index,
798 int64_t offset, RoutingLinearSolverWrapper* solver) {
799 const RoutingModel*
model = dimension.model();
800 for (
int rg_index :
model->GetDimensionResourceGroupIndices(&dimension)) {
801 const IntVar* res_var =
model->ResourceVars(rg_index)[v];
802 DCHECK(res_var->Bound());
803 const int res_index = res_var->Value();
806 DCHECK(!
model->GetResourceGroup(rg_index)->VehicleRequiresAResource(v));
809 const ResourceGroup::Resource& resource =
810 model->GetResourceGroup(rg_index)->GetResource(res_index);
811 if (!TightenStartEndVariableBoundsWithResource(
812 dimension, resource, GetVariableBounds(start_index, *solver),
813 start_index, GetVariableBounds(end_index, *solver), end_index,
823std::vector<DimensionSchedulingStatus>
825 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
826 const std::function<int64_t(int64_t, int64_t)>& transit_accessor,
827 const RouteDimensionTravelInfo& dimension_travel_info,
828 const std::vector<RoutingModel::ResourceGroup::Resource>& resources,
829 const std::vector<int>& resource_indices,
bool optimize_vehicle_costs,
833 std::vector<int64_t>* costs_without_transits, int64_t* transit_cost,
835 ClearIfNonNull(costs_without_transits);
836 const bool optimize_with_resources = !resource_indices.empty();
837 if (!optimize_with_resources && !resources.empty())
return {};
839 InitOptimizer(solver);
842 DCHECK_EQ(propagator_.get(),
nullptr);
845 if (
model->IsEnd(next_accessor(
model->Start(vehicle))) &&
846 !
model->IsVehicleUsedWhenEmpty(vehicle)) {
848 DCHECK(!optimize_with_resources);
849 optimize_vehicle_costs =
false;
852 const int64_t cumul_offset =
853 dimension_->GetLocalOptimizerOffsetForVehicle(vehicle);
854 int64_t cost_offset = 0;
855 if (!SetRouteCumulConstraints(vehicle, next_accessor, transit_accessor,
856 dimension_travel_info, cumul_offset,
857 optimize_vehicle_costs, solver, transit_cost,
859 if (costs_without_transits !=
nullptr) {
860 costs_without_transits->assign(1, -1);
864 DCHECK_GE(current_route_cumul_variables_.size(), 2);
868 const int num_solves =
869 std::max(
static_cast<decltype(resource_indices.
size())
>(1UL),
870 resource_indices.size());
871 if (costs_without_transits !=
nullptr) {
872 costs_without_transits->assign(num_solves, -1);
877 const int start_cumul = current_route_cumul_variables_[0];
878 const ClosedInterval start_bounds = GetVariableBounds(start_cumul, *solver);
879 const int end_cumul = current_route_cumul_variables_.back();
880 const ClosedInterval end_bounds = GetVariableBounds(end_cumul, *solver);
881 std::vector<DimensionSchedulingStatus> statuses;
882 statuses.reserve(num_solves);
883 for (
int i = 0; i < num_solves; i++) {
884 if (
model->CheckLimit()) {
886 ClearIfNonNull(costs_without_transits);
892 if (optimize_with_resources &&
893 !TightenStartEndVariableBoundsWithResource(
894 *dimension_, resources[resource_indices[i]], start_bounds,
895 start_cumul, end_bounds, end_cumul, cumul_offset, solver)) {
901 statuses.push_back(solver->
Solve(
model->RemainingTime()));
905 if (costs_without_transits !=
nullptr) {
906 costs_without_transits->at(i) =
907 optimize_vehicle_costs
913 SetValuesFromLP(current_route_cumul_variables_, cumul_offset, solver,
917 SetValuesFromLP(current_route_break_variables_, cumul_offset, solver,
929 const std::function<int64_t(int64_t)>& next_accessor,
930 const std::vector<RouteDimensionTravelInfo>&
931 dimension_travel_info_per_route,
934 std::vector<std::vector<int>>* resource_indices_per_group,
935 int64_t* cost_without_transits, int64_t* transit_cost,
bool clear_lp,
936 bool optimize_resource_assignment) {
937 InitOptimizer(solver);
938 if (!optimize_resource_assignment) {
939 DCHECK_EQ(resource_indices_per_group,
nullptr);
944 const bool optimize_costs =
945 (
cumul_values !=
nullptr) || (cost_without_transits !=
nullptr);
946 bool has_vehicles_being_optimized =
false;
948 const int64_t cumul_offset = dimension_->GetGlobalOptimizerOffset();
950 if (propagator_ !=
nullptr &&
951 !propagator_->PropagateCumulBounds(next_accessor, cumul_offset,
952 &dimension_travel_info_per_route)) {
956 int64_t total_transit_cost = 0;
957 int64_t total_cost_offset = 0;
958 const RoutingModel*
model = dimension_->model();
959 for (
int vehicle = 0; vehicle <
model->vehicles(); vehicle++) {
960 int64_t route_transit_cost = 0;
961 int64_t route_cost_offset = 0;
962 const bool vehicle_is_used =
963 !
model->IsEnd(next_accessor(
model->Start(vehicle))) ||
964 model->IsVehicleUsedWhenEmpty(vehicle);
965 const bool optimize_vehicle_costs = optimize_costs && vehicle_is_used;
966 const RouteDimensionTravelInfo& dimension_travel_info =
967 dimension_travel_info_per_route.empty()
968 ? RouteDimensionTravelInfo()
969 : dimension_travel_info_per_route[vehicle];
970 if (!SetRouteCumulConstraints(
971 vehicle, next_accessor, dimension_->transit_evaluator(vehicle),
972 dimension_travel_info, cumul_offset, optimize_vehicle_costs, solver,
973 &route_transit_cost, &route_cost_offset)) {
976 DCHECK_GE(current_route_cumul_variables_.size(), 2);
977 if (vehicle_is_used && !optimize_resource_assignment) {
979 if (!TightenStartEndVariableBoundsWithAssignedResources(
980 *dimension_, vehicle, current_route_cumul_variables_[0],
981 current_route_cumul_variables_.back(), cumul_offset, solver)) {
985 total_transit_cost =
CapAdd(total_transit_cost, route_transit_cost);
986 total_cost_offset =
CapAdd(total_cost_offset, route_cost_offset);
987 has_vehicles_being_optimized |= optimize_vehicle_costs;
989 if (transit_cost !=
nullptr) {
990 *transit_cost = total_transit_cost;
993 if (!SetGlobalConstraints(next_accessor, cumul_offset,
994 has_vehicles_being_optimized,
995 optimize_resource_assignment, solver)) {
1008 SetValuesFromLP(index_to_cumul_variable_, cumul_offset, solver,
cumul_values);
1009 SetValuesFromLP(all_break_variables_, cumul_offset, solver,
break_values);
1010 SetResourceIndices(solver, resource_indices_per_group);
1012 if (cost_without_transits !=
nullptr) {
1013 *cost_without_transits =
1024 const std::function<int64_t(int64_t)>& next_accessor,
1025 const std::vector<RouteDimensionTravelInfo>&
1026 dimension_travel_info_per_route,
1032 const glop::GlopParameters original_params = GetGlopParametersForGlobalLP();
1033 glop::GlopParameters packing_parameters;
1035 packing_parameters = original_params;
1036 packing_parameters.set_use_dual_simplex(
false);
1037 packing_parameters.set_use_preprocessing(
true);
1038 solver->
SetParameters(packing_parameters.SerializeAsString());
1041 if (
Optimize(next_accessor, dimension_travel_info_per_route, solver,
1051 std::iota(vehicles.begin(), vehicles.end(), 0);
1054 status = PackRoutes(vehicles, solver, packing_parameters);
1064 const int64_t global_offset = dimension_->GetGlobalOptimizerOffset();
1065 SetValuesFromLP(index_to_cumul_variable_, global_offset, solver,
1067 SetValuesFromLP(all_break_variables_, global_offset, solver,
break_values);
1074 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
1075 const RouteDimensionTravelInfo& dimension_travel_info,
1076 const RoutingModel::ResourceGroup::Resource* resource,
1079 const glop::GlopParameters original_params = GetGlopParametersForLocalLP();
1080 glop::GlopParameters packing_parameters;
1082 packing_parameters = original_params;
1083 packing_parameters.set_use_dual_simplex(
false);
1084 packing_parameters.set_use_preprocessing(
true);
1085 solver->
SetParameters(packing_parameters.SerializeAsString());
1088 vehicle, next_accessor, dimension_travel_info, resource,
1091 nullptr,
nullptr,
false);
1094 status = PackRoutes({vehicle}, solver, packing_parameters);
1103 const int64_t local_offset =
1104 dimension_->GetLocalOptimizerOffsetForVehicle(vehicle);
1105 SetValuesFromLP(current_route_cumul_variables_, local_offset, solver,
1107 SetValuesFromLP(current_route_break_variables_, local_offset, solver,
1115 const glop::GlopParameters& packing_parameters) {
1116 const RoutingModel*
model = dimension_->model();
1132 for (
int vehicle : vehicles) {
1134 index_to_cumul_variable_[
model->End(vehicle)], 1);
1137 glop::GlopParameters current_params;
1138 const auto retry_solving = [¤t_params,
model, solver]() {
1142 current_params.set_use_dual_simplex(!current_params.use_dual_simplex());
1144 return solver->
Solve(
model->RemainingTime());
1152 current_params = packing_parameters;
1161 for (
int vehicle : vehicles) {
1162 const int end_cumul_var = index_to_cumul_variable_[
model->End(vehicle)];
1170 index_to_cumul_variable_[
model->Start(vehicle)], -1);
1176 status = retry_solving();
1181#define SET_DEBUG_VARIABLE_NAME(solver, var, name) \
1184 solver->SetVariableName(var, name); \
1188void DimensionCumulOptimizerCore::InitOptimizer(
1189 RoutingLinearSolverWrapper* solver) {
1191 index_to_cumul_variable_.assign(dimension_->cumuls().size(), -1);
1192 max_end_cumul_ = solver->CreateNewPositiveVariable();
1194 min_start_cumul_ = solver->CreateNewPositiveVariable();
1198bool DimensionCumulOptimizerCore::ExtractRouteCumulBounds(
1199 absl::Span<const int64_t> route, int64_t cumul_offset) {
1200 const int route_size = route.size();
1201 current_route_min_cumuls_.resize(route_size);
1202 current_route_max_cumuls_.resize(route_size);
1205 for (
int pos = 0; pos < route_size; ++pos) {
1206 if (!GetCumulBoundsWithOffset(*dimension_, route[pos], cumul_offset,
1207 ¤t_route_min_cumuls_[pos],
1208 ¤t_route_max_cumuls_[pos])) {
1215bool DimensionCumulOptimizerCore::TightenRouteCumulBounds(
1216 const std::vector<int64_t>& route,
const std::vector<int64_t>& min_transits,
1217 int64_t cumul_offset) {
1218 const int route_size = route.size();
1219 if (propagator_ !=
nullptr) {
1220 for (
int pos = 0; pos < route_size; pos++) {
1221 const int64_t node = route[pos];
1222 current_route_min_cumuls_[pos] = propagator_->CumulMin(node);
1223 DCHECK_GE(current_route_min_cumuls_[pos], 0);
1224 current_route_max_cumuls_[pos] = propagator_->CumulMax(node);
1225 DCHECK_GE(current_route_max_cumuls_[pos], current_route_min_cumuls_[pos]);
1232 for (
int pos = 1; pos < route_size; ++pos) {
1233 const int64_t slack_min = dimension_->SlackVar(route[pos - 1])->Min();
1234 current_route_min_cumuls_[pos] = std::max(
1235 current_route_min_cumuls_[pos],
1237 CapAdd(current_route_min_cumuls_[pos - 1], min_transits[pos - 1]),
1239 current_route_min_cumuls_[pos] = GetFirstPossibleValueForCumulWithOffset(
1240 *dimension_, route[pos], current_route_min_cumuls_[pos], cumul_offset);
1241 if (current_route_min_cumuls_[pos] > current_route_max_cumuls_[pos]) {
1246 for (
int pos = route_size - 2; pos >= 0; --pos) {
1249 if (current_route_max_cumuls_[pos + 1] <
1250 std::numeric_limits<int64_t>::max()) {
1251 const int64_t slack_min = dimension_->SlackVar(route[pos])->Min();
1252 current_route_max_cumuls_[pos] = std::min(
1253 current_route_max_cumuls_[pos],
1254 CapSub(
CapSub(current_route_max_cumuls_[pos + 1], min_transits[pos]),
1256 current_route_max_cumuls_[pos] = GetLastPossibleValueForCumulWithOffset(
1257 *dimension_, route[pos], current_route_max_cumuls_[pos],
1259 if (current_route_max_cumuls_[pos] < current_route_min_cumuls_[pos]) {
1268 const std::vector<SlopeAndYIntercept>& slope_and_y_intercept) {
1269 CHECK(!slope_and_y_intercept.empty());
1270 std::vector<bool> convex(slope_and_y_intercept.size(),
false);
1271 double previous_slope = std::numeric_limits<double>::max();
1272 for (
int i = 0; i < slope_and_y_intercept.size(); ++i) {
1273 const auto& pair = slope_and_y_intercept[i];
1274 if (pair.slope < previous_slope) {
1277 previous_slope = pair.slope;
1283 const RoutingModel::RouteDimensionTravelInfo::TransitionInfo::
1284 PiecewiseLinearFormulation& pwl_function,
1285 int index_start,
int index_end) {
1286 if (index_end < 0) index_end = pwl_function.x_anchors.size() - 1;
1287 const int num_segments = index_end - index_start;
1288 DCHECK_GE(num_segments, 1);
1289 std::vector<SlopeAndYIntercept> slope_and_y_intercept(num_segments);
1290 for (
int seg = index_start; seg < index_end; ++seg) {
1291 auto& [slope, y_intercept] = slope_and_y_intercept[seg - index_start];
1292 slope = (pwl_function.y_anchors[seg + 1] - pwl_function.y_anchors[seg]) /
1293 static_cast<double>(pwl_function.x_anchors[seg + 1] -
1294 pwl_function.x_anchors[seg]);
1296 pwl_function.y_anchors[seg] - slope * pwl_function.x_anchors[seg];
1298 return slope_and_y_intercept;
1305double FindBestScaling(
const std::vector<double>&
coefficients,
1308 int64_t max_absolute_activity,
1309 double wanted_absolute_activity_precision) {
1310 double unused_relative_coeff_error = 0;
1311 double unused_scaled_sum_error = 0;
1314 wanted_absolute_activity_precision, &unused_relative_coeff_error,
1315 &unused_scaled_sum_error);
1320int64_t PieceWiseLinearFormulationValueKnownSegment(
1321 const RoutingModel::RouteDimensionTravelInfo::TransitionInfo::
1322 PiecewiseLinearFormulation& pwl,
1323 int64_t
x,
int upper_segment_index,
double delta = 0) {
1324 DCHECK_GE(upper_segment_index, 1);
1325 DCHECK_LE(upper_segment_index, pwl.x_anchors.size() - 1);
1326 const double alpha =
1327 static_cast<double>(pwl.y_anchors[upper_segment_index] -
1328 pwl.y_anchors[upper_segment_index - 1]) /
1329 (pwl.x_anchors[upper_segment_index] -
1330 pwl.x_anchors[upper_segment_index - 1]);
1331 const double beta = pwl.y_anchors[upper_segment_index] -
1332 pwl.x_anchors[upper_segment_index] * alpha;
1333 return std::ceil(alpha *
x + beta +
delta);
1339 const RoutingModel::RouteDimensionTravelInfo::TransitionInfo::
1340 PiecewiseLinearFormulation& pwl,
1343 const auto upper_segment =
1344 std::upper_bound(pwl.x_anchors.begin(), pwl.x_anchors.end(),
x);
1345 const int upper_segment_index =
1346 std::distance(pwl.x_anchors.begin(), upper_segment);
1349 if (upper_segment_index == 0) {
1351 }
else if (upper_segment == pwl.x_anchors.end()) {
1352 if (
x == pwl.x_anchors.back()) {
1353 *
value = std::ceil(pwl.y_anchors.back() +
delta);
1359 *
value = PieceWiseLinearFormulationValueKnownSegment(
1360 pwl,
x, upper_segment_index,
delta);
1365 const RoutingModel::RouteDimensionTravelInfo::TransitionInfo::
1366 PiecewiseLinearFormulation& pwl,
1367 int64_t
x,
double delta) {
1374 LOG(FATAL) <<
"Unspecified PiecewiseEvaluationStatus.";
1382 return PieceWiseLinearFormulationValueKnownSegment(pwl,
x, 1,
delta);
1386 return PieceWiseLinearFormulationValueKnownSegment(
1387 pwl,
x, pwl.x_anchors.size() - 1,
delta);
1391bool DimensionCumulOptimizerCore::SetRouteTravelConstraints(
1392 const RouteDimensionTravelInfo& dimension_travel_info,
1393 const std::vector<int>& lp_slacks,
1394 const std::vector<int64_t>& fixed_transit,
1395 RoutingLinearSolverWrapper* solver) {
1396 const std::vector<int>& lp_cumuls = current_route_cumul_variables_;
1397 const int path_size = lp_cumuls.size();
1399 if (dimension_travel_info.transition_info.empty()) {
1404 for (
int pos = 0; pos < path_size - 1; ++pos) {
1406 solver->CreateNewConstraint(fixed_transit[pos], fixed_transit[pos]);
1407 solver->SetCoefficient(
ct, lp_cumuls[pos + 1], 1);
1408 solver->SetCoefficient(
ct, lp_cumuls[pos], -1);
1409 solver->SetCoefficient(
ct, lp_slacks[pos], -1);
1416 for (
int pos = 0; pos < path_size - 1; ++pos) {
1420 const int compression_cost = solver->CreateNewPositiveVariable();
1422 absl::StrFormat(
"compression_cost(%ld)", pos));
1429 const int relative_compression_cost = solver->CreateNewPositiveVariable();
1431 solver, relative_compression_cost,
1432 absl::StrFormat(
"relative_compression_cost(%ld)", pos));
1434 const RoutingModel::RouteDimensionTravelInfo::TransitionInfo&
1435 transition_info = dimension_travel_info.transition_info[pos];
1436 const RouteDimensionTravelInfo::TransitionInfo::PiecewiseLinearFormulation&
1437 travel_function = transition_info.travel_start_dependent_travel;
1438 const int num_pwl_anchors = travel_function.x_anchors.size();
1439 DCHECK_GE(num_pwl_anchors, 2)
1440 <<
"Travel value PWL must have at least 2 points";
1441 DCHECK_EQ(num_pwl_anchors, travel_function.y_anchors.size())
1442 <<
"Travel value PWL must have as many x anchors than y.";
1446 const int64_t pre_travel_transit = transition_info.pre_travel_transit_value;
1447 const int64_t post_travel_transit =
1448 transition_info.post_travel_transit_value;
1449 const int64_t compressed_travel_value_lower_bound =
1450 transition_info.compressed_travel_value_lower_bound;
1451 const int64_t travel_value_upper_bound =
1452 dimension_travel_info.transition_info[pos].travel_value_upper_bound;
1458 const int travel_value = solver->AddVariable(
1459 compressed_travel_value_lower_bound, travel_value_upper_bound);
1461 absl::StrFormat(
"travel_value(%ld)", pos));
1462 const int travel_start = solver->AddVariable(
1463 current_route_min_cumuls_[pos] + pre_travel_transit,
1464 current_route_max_cumuls_[pos + 1] - post_travel_transit -
1465 compressed_travel_value_lower_bound);
1467 absl::StrFormat(
"travel_start(%ld)", pos));
1470 solver->AddLinearConstraint(pre_travel_transit, pre_travel_transit,
1471 {{travel_start, 1}, {lp_cumuls[pos], -1}});
1476 int index_anchor_start = 0;
1477 while (index_anchor_start < num_pwl_anchors - 1 &&
1478 travel_function.x_anchors[index_anchor_start + 1] <=
1479 current_route_min_cumuls_[pos] + pre_travel_transit) {
1480 ++index_anchor_start;
1482 int index_anchor_end = num_pwl_anchors - 1;
1483 while (index_anchor_end > 0 &&
1484 travel_function.x_anchors[index_anchor_end - 1] >=
1485 current_route_max_cumuls_[pos] + pre_travel_transit) {
1489 if (index_anchor_start >= index_anchor_end)
return false;
1493 const std::vector<SlopeAndYIntercept> slope_and_y_intercept =
1495 travel_function, index_anchor_start, index_anchor_end);
1498 const std::vector<bool> convexities =
1501 int nb_bin_variables = 0;
1502 for (
const bool convexity : convexities) {
1505 if (nb_bin_variables >= 2)
break;
1508 const bool need_bins = (nb_bin_variables > 1);
1510 const int travel_start_in_one_segment_ct =
1511 need_bins ? solver->CreateNewConstraint(1, 1)
1514 int belongs_to_this_segment_var;
1515 for (
int seg = 0; seg < convexities.size(); ++seg) {
1516 if (need_bins && convexities[seg]) {
1517 belongs_to_this_segment_var = solver->AddVariable(0, 1);
1519 solver, belongs_to_this_segment_var,
1520 absl::StrFormat(
"travel_start(%ld)belongs_to_seg(%ld)", pos, seg));
1521 solver->SetCoefficient(travel_start_in_one_segment_ct,
1522 belongs_to_this_segment_var, 1);
1527 const int64_t lower_bound_interval =
1528 seg > 0 ? travel_function.x_anchors[index_anchor_start + seg]
1529 : current_route_min_cumuls_[pos] + pre_travel_transit;
1530 int64_t end_of_seg = seg + 1;
1531 while (end_of_seg < num_pwl_anchors - 1 && !convexities[end_of_seg]) {
1534 const int64_t higher_bound_interval =
1535 end_of_seg < num_pwl_anchors - 1
1536 ? travel_function.x_anchors[index_anchor_start + end_of_seg]
1537 : current_route_max_cumuls_[pos] + pre_travel_transit;
1538 const int travel_start_in_segment_ct = solver->AddLinearConstraint(
1539 lower_bound_interval, higher_bound_interval, {{travel_start, 1}});
1540 solver->SetEnforcementLiteral(travel_start_in_segment_ct,
1541 belongs_to_this_segment_var);
1546 const auto [slope, y_intercept] = slope_and_y_intercept[seg];
1548 DCHECK_GE(slope, -1.0) <<
"Travel value PWL should have a slope >= -1";
1558 const double upper_bound = current_route_max_cumuls_[pos];
1559 const double factor = FindBestScaling(
1560 {1.0, -slope, y_intercept - 0.5},
1561 {
static_cast<double>(compressed_travel_value_lower_bound), 0, 1},
1563 {
static_cast<double>(travel_value_upper_bound),
upper_bound, 1},
1569 if (factor <= 0)
return false;
1571 const int linearization_ct = solver->AddLinearConstraint(
1573 std::numeric_limits<int64_t>::max(),
1577 solver->SetEnforcementLiteral(linearization_ct,
1578 belongs_to_this_segment_var);
1604 const int compressed_travel_value = solver->AddVariable(
1605 compressed_travel_value_lower_bound, travel_value_upper_bound);
1607 solver, compressed_travel_value,
1608 absl::StrFormat(
"compressed_travel_value(%ld)", pos));
1609 solver->AddLinearConstraint(post_travel_transit + pre_travel_transit,
1610 post_travel_transit + pre_travel_transit,
1611 {{compressed_travel_value, -1},
1612 {lp_cumuls[pos + 1], 1},
1613 {lp_cumuls[pos], -1},
1614 {lp_slacks[pos], -1}});
1621 const int travel_compression_absolute = solver->AddVariable(
1622 0, travel_value_upper_bound - compressed_travel_value_lower_bound);
1624 solver, travel_compression_absolute,
1625 absl::StrFormat(
"travel_compression_absolute(%ld)", pos));
1627 solver->AddLinearConstraint(0, 0,
1628 {{travel_compression_absolute, 1},
1630 {compressed_travel_value, 1}});
1636 solver->SetObjectiveCoefficient(
1637 travel_value, dimension_travel_info.travel_cost_coefficient);
1641 const RouteDimensionTravelInfo::TransitionInfo::PiecewiseLinearFormulation&
1643 dimension_travel_info.transition_info[pos].travel_compression_cost;
1644 const std::vector<SlopeAndYIntercept> cost_slope_and_y_intercept =
1648 travel_value_upper_bound - compressed_travel_value_lower_bound);
1649 double previous_slope = 0;
1650 for (
int seg = 0; seg < cost_function.x_anchors.size() - 1; ++seg) {
1651 const auto [slope, y_intercept] = cost_slope_and_y_intercept[seg];
1653 DCHECK_GE(slope, previous_slope)
1654 <<
"Compression error is not convex. Segment " << (1 + seg)
1655 <<
" out of " << (cost_function.x_anchors.size() - 1);
1656 previous_slope = slope;
1657 const double factor = FindBestScaling(
1658 {1.0, -slope, y_intercept},
1659 {0,
static_cast<double>(compressed_travel_value_lower_bound), 1},
1661 {cost_max,
static_cast<double>(travel_value_upper_bound), 1},
1662 (
static_cast<int64_t
>(1) << 62),
1667 if (factor <= 0)
return false;
1669 solver->AddLinearConstraint(
1671 std::numeric_limits<int64_t>::max(),
1672 {{compression_cost, std::round(factor)},
1673 {travel_compression_absolute,
1693 solver->AddLinearConstraint(
1694 0, std::numeric_limits<int64_t>::max(),
1695 {{relative_compression_cost, 1}, {compression_cost, -1}});
1697 solver->SetObjectiveCoefficient(relative_compression_cost, 1.0);
1703bool RouteIsValid(
const RoutingModel&
model,
int vehicle,
1704 const std::function<int64_t(int64_t)>& next_accessor) {
1705 absl::flat_hash_set<int64_t> visited;
1706 int node =
model.Start(vehicle);
1707 visited.insert(node);
1708 while (!
model.IsEnd(node)) {
1709 node = next_accessor(node);
1710 if (visited.contains(node))
return false;
1711 visited.insert(node);
1713 return visited.size() >= 2;
1717bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
1718 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
1719 const std::function<int64_t(int64_t, int64_t)>& transit_accessor,
1720 const RouteDimensionTravelInfo& dimension_travel_info, int64_t cumul_offset,
1721 bool optimize_costs, RoutingLinearSolverWrapper* solver,
1722 int64_t* route_transit_cost, int64_t* route_cost_offset) {
1723 RoutingModel*
const model = dimension_->model();
1725 std::vector<int64_t> path;
1727 DCHECK(RouteIsValid(*
model, vehicle, next_accessor));
1728 int node =
model->Start(vehicle);
1729 path.push_back(node);
1730 while (!
model->IsEnd(node)) {
1731 node = next_accessor(node);
1732 path.push_back(node);
1734 DCHECK_GE(path.size(), 2);
1736 const int path_size = path.size();
1738 std::vector<int64_t> fixed_transit(path_size - 1);
1739 int64_t total_fixed_transit = 0;
1741 for (
int pos = 1; pos < path_size; ++pos) {
1742 int64_t& transit = fixed_transit[pos - 1];
1743 transit = transit_accessor(path[pos - 1], path[pos]);
1744 total_fixed_transit =
CapAdd(total_fixed_transit, transit);
1747 if (!ExtractRouteCumulBounds(path, cumul_offset)) {
1750 if (dimension_travel_info.transition_info.empty()) {
1751 if (!TightenRouteCumulBounds(path, fixed_transit, cumul_offset)) {
1756 std::vector<int64_t> min_transit(path_size - 1);
1757 for (
int pos = 0; pos < path_size - 1; ++pos) {
1758 const RouteDimensionTravelInfo::TransitionInfo& transition =
1759 dimension_travel_info.transition_info[pos];
1760 min_transit[pos] = transition.pre_travel_transit_value +
1761 transition.compressed_travel_value_lower_bound +
1762 transition.post_travel_transit_value;
1764 if (!TightenRouteCumulBounds(path, min_transit, cumul_offset)) {
1771 std::vector<int>& lp_cumuls = current_route_cumul_variables_;
1772 lp_cumuls.assign(path_size, -1);
1773 for (
int pos = 0; pos < path_size; ++pos) {
1774 const int lp_cumul = solver->CreateNewPositiveVariable();
1776 absl::StrFormat(
"lp_cumul(%ld)", pos));
1777 index_to_cumul_variable_[path[pos]] = lp_cumul;
1778 lp_cumuls[pos] = lp_cumul;
1779 if (!solver->SetVariableBounds(lp_cumul, current_route_min_cumuls_[pos],
1780 current_route_max_cumuls_[pos])) {
1783 const SortedDisjointIntervalList& forbidden =
1784 dimension_->forbidden_intervals()[path[pos]];
1785 if (forbidden.NumIntervals() > 0) {
1786 std::vector<int64_t> starts;
1787 std::vector<int64_t> ends;
1788 for (
const ClosedInterval
interval :
1789 dimension_->GetAllowedIntervalsInRange(
1790 path[pos],
CapAdd(current_route_min_cumuls_[pos], cumul_offset),
1791 CapAdd(current_route_max_cumuls_[pos], cumul_offset))) {
1795 solver->SetVariableDisjointBounds(lp_cumul, starts, ends);
1799 std::vector<int> lp_slacks(path_size - 1, -1);
1800 for (
int pos = 0; pos < path_size - 1; ++pos) {
1801 const IntVar* cp_slack = dimension_->SlackVar(path[pos]);
1802 lp_slacks[pos] = solver->CreateNewPositiveVariable();
1804 absl::StrFormat(
"lp_slacks(%ld)", pos));
1805 if (!solver->SetVariableBounds(lp_slacks[pos], cp_slack->Min(),
1811 if (!SetRouteTravelConstraints(dimension_travel_info, lp_slacks,
1812 fixed_transit, solver)) {
1816 if (route_cost_offset !=
nullptr) *route_cost_offset = 0;
1817 if (optimize_costs) {
1819 for (
int pos = 0; pos < path_size; ++pos) {
1820 if (!dimension_->HasCumulVarSoftUpperBound(path[pos]))
continue;
1821 const int64_t
coef =
1822 dimension_->GetCumulVarSoftUpperBoundCoefficient(path[pos]);
1823 if (
coef == 0)
continue;
1824 int64_t
bound = dimension_->GetCumulVarSoftUpperBound(path[pos]);
1825 if (
bound < cumul_offset && route_cost_offset !=
nullptr) {
1827 *route_cost_offset =
CapAdd(*route_cost_offset,
1831 if (current_route_max_cumuls_[pos] <=
bound) {
1835 const int soft_ub_diff = solver->CreateNewPositiveVariable();
1837 absl::StrFormat(
"soft_ub_diff(%ld)", pos));
1838 solver->SetObjectiveCoefficient(soft_ub_diff,
coef);
1840 const int ct = solver->CreateNewConstraint(
1841 std::numeric_limits<int64_t>::min(),
bound);
1842 solver->SetCoefficient(
ct, lp_cumuls[pos], 1);
1843 solver->SetCoefficient(
ct, soft_ub_diff, -1);
1846 for (
int pos = 0; pos < path_size; ++pos) {
1847 if (!dimension_->HasCumulVarSoftLowerBound(path[pos]))
continue;
1848 const int64_t
coef =
1849 dimension_->GetCumulVarSoftLowerBoundCoefficient(path[pos]);
1850 if (
coef == 0)
continue;
1851 const int64_t
bound = std::max<int64_t>(
1852 0,
CapSub(dimension_->GetCumulVarSoftLowerBound(path[pos]),
1854 if (current_route_min_cumuls_[pos] >=
bound) {
1858 const int soft_lb_diff = solver->CreateNewPositiveVariable();
1860 absl::StrFormat(
"soft_lb_diff(%ld)", pos));
1861 solver->SetObjectiveCoefficient(soft_lb_diff,
coef);
1863 const int ct = solver->CreateNewConstraint(
1864 bound, std::numeric_limits<int64_t>::max());
1865 solver->SetCoefficient(
ct, lp_cumuls[pos], 1);
1866 solver->SetCoefficient(
ct, soft_lb_diff, 1);
1870 std::vector<int> visited_pairs;
1871 StoreVisitedPickupDeliveryPairsOnRoute(
1872 *dimension_, vehicle, next_accessor, &visited_pairs,
1873 &visited_pickup_delivery_indices_for_pair_);
1874 for (
int pair_index : visited_pairs) {
1875 const int64_t pickup_index =
1876 visited_pickup_delivery_indices_for_pair_[pair_index].first;
1877 const int64_t delivery_index =
1878 visited_pickup_delivery_indices_for_pair_[pair_index].second;
1879 visited_pickup_delivery_indices_for_pair_[pair_index] = {-1, -1};
1881 DCHECK_GE(pickup_index, 0);
1882 if (delivery_index < 0) {
1887 const int64_t limit = dimension_->GetPickupToDeliveryLimitForPair(
1889 model->GetPickupPositions(pickup_index)[0].alternative_index,
1890 model->GetDeliveryPositions(delivery_index)[0].alternative_index);
1891 if (limit < std::numeric_limits<int64_t>::max()) {
1893 const int ct = solver->CreateNewConstraint(
1894 std::numeric_limits<int64_t>::min(), limit);
1895 solver->SetCoefficient(
ct, index_to_cumul_variable_[delivery_index], 1);
1896 solver->SetCoefficient(
ct, index_to_cumul_variable_[pickup_index], -1);
1901 const int64_t span_bound = dimension_->GetSpanUpperBoundForVehicle(vehicle);
1902 if (span_bound < std::numeric_limits<int64_t>::max()) {
1904 const int ct = solver->CreateNewConstraint(
1905 std::numeric_limits<int64_t>::min(), span_bound);
1906 solver->SetCoefficient(
ct, lp_cumuls.back(), 1);
1907 solver->SetCoefficient(
ct, lp_cumuls.front(), -1);
1912 const int64_t span_cost_coef =
1913 dimension_->GetSpanCostCoefficientForVehicle(vehicle);
1914 const int64_t slack_cost_coef =
CapAdd(
1915 span_cost_coef, dimension_->GetSlackCostCoefficientForVehicle(vehicle));
1916 if (optimize_costs && slack_cost_coef > 0) {
1919 const int span_without_fixed_transit_var =
1920 solver->CreateNewPositiveVariable();
1922 "span_without_fixed_transit_var");
1923 solver->AddLinearConstraint(total_fixed_transit, total_fixed_transit,
1924 {{lp_cumuls.back(), 1},
1925 {lp_cumuls.front(), -1},
1926 {span_without_fixed_transit_var, -1}});
1927 solver->SetObjectiveCoefficient(span_without_fixed_transit_var,
1931 if (optimize_costs && dimension_->HasSoftSpanUpperBounds()) {
1932 const BoundCost bound_cost =
1933 dimension_->GetSoftSpanUpperBoundForVehicle(vehicle);
1934 if (bound_cost.bound < std::numeric_limits<int64_t>::max() &&
1935 bound_cost.cost > 0) {
1936 const int span_violation = solver->CreateNewPositiveVariable();
1939 const int violation = solver->CreateNewConstraint(
1940 std::numeric_limits<int64_t>::min(), bound_cost.bound);
1941 solver->SetCoefficient(violation, lp_cumuls.back(), 1.0);
1942 solver->SetCoefficient(violation, lp_cumuls.front(), -1.0);
1943 solver->SetCoefficient(violation, span_violation, -1.0);
1945 solver->SetObjectiveCoefficient(span_violation, bound_cost.cost);
1949 if (optimize_costs && dimension_->global_span_cost_coefficient() > 0) {
1952 solver->CreateNewConstraint(std::numeric_limits<int64_t>::min(), 0);
1953 solver->SetCoefficient(
ct, min_start_cumul_, 1);
1954 solver->SetCoefficient(
ct, lp_cumuls.front(), -1);
1956 ct = solver->CreateNewConstraint(0, std::numeric_limits<int64_t>::max());
1957 solver->SetCoefficient(
ct, max_end_cumul_, 1);
1958 solver->SetCoefficient(
ct, lp_cumuls.back(), -1);
1961 if (route_transit_cost !=
nullptr) {
1962 if (optimize_costs && span_cost_coef > 0) {
1963 *route_transit_cost =
CapProd(total_fixed_transit, span_cost_coef);
1965 *route_transit_cost = 0;
1973 current_route_break_variables_.clear();
1974 if (!dimension_->HasBreakConstraints())
return true;
1975 const std::vector<IntervalVar*>& breaks =
1976 dimension_->GetBreakIntervalsOfVehicle(vehicle);
1977 const int num_breaks = breaks.size();
1981 if (num_breaks == 0) {
1982 int64_t maximum_route_span = std::numeric_limits<int64_t>::max();
1983 for (
const auto& distance_duration :
1984 dimension_->GetBreakDistanceDurationOfVehicle(vehicle)) {
1985 maximum_route_span =
1986 std::min(maximum_route_span, distance_duration.first);
1988 if (maximum_route_span < std::numeric_limits<int64_t>::max()) {
1989 const int ct = solver->CreateNewConstraint(
1990 std::numeric_limits<int64_t>::min(), maximum_route_span);
1991 solver->SetCoefficient(
ct, lp_cumuls.back(), 1);
1992 solver->SetCoefficient(
ct, lp_cumuls.front(), -1);
1999 std::vector<int64_t> pre_travel(path_size - 1, 0);
2000 std::vector<int64_t> post_travel(path_size - 1, 0);
2002 const int pre_travel_index =
2003 dimension_->GetPreTravelEvaluatorOfVehicle(vehicle);
2004 if (pre_travel_index != -1) {
2008 const int post_travel_index =
2009 dimension_->GetPostTravelEvaluatorOfVehicle(vehicle);
2010 if (post_travel_index != -1) {
2019 std::vector<int> lp_break_start;
2020 std::vector<int> lp_break_duration;
2021 std::vector<int> lp_break_end;
2022 if (solver->IsCPSATSolver()) {
2023 lp_break_start.resize(num_breaks, -1);
2024 lp_break_duration.resize(num_breaks, -1);
2025 lp_break_end.resize(num_breaks, -1);
2028 std::vector<int> slack_exact_lower_bound_ct(path_size - 1, -1);
2029 std::vector<int> slack_linear_lower_bound_ct(path_size - 1, -1);
2031 const int64_t vehicle_start_min = current_route_min_cumuls_.front();
2032 const int64_t vehicle_start_max = current_route_max_cumuls_.front();
2033 const int64_t vehicle_end_min = current_route_min_cumuls_.back();
2034 const int64_t vehicle_end_max = current_route_max_cumuls_.back();
2035 const int all_break_variables_offset =
2036 vehicle_to_all_break_variables_offset_[vehicle];
2037 for (
int br = 0; br < num_breaks; ++br) {
2038 const IntervalVar& break_var = *breaks[br];
2039 if (!break_var.MustBePerformed())
continue;
2040 const int64_t break_start_min =
CapSub(break_var.StartMin(), cumul_offset);
2041 const int64_t break_start_max =
CapSub(break_var.StartMax(), cumul_offset);
2042 const int64_t break_end_min =
CapSub(break_var.EndMin(), cumul_offset);
2043 const int64_t break_end_max =
CapSub(break_var.EndMax(), cumul_offset);
2044 const int64_t break_duration_min = break_var.DurationMin();
2045 const int64_t break_duration_max = break_var.DurationMax();
2048 if (solver->IsCPSATSolver()) {
2049 if (break_end_max <= vehicle_start_min ||
2050 vehicle_end_max <= break_start_min) {
2051 all_break_variables_[all_break_variables_offset + 2 * br] = -1;
2052 all_break_variables_[all_break_variables_offset + 2 * br + 1] = -1;
2053 current_route_break_variables_.push_back(-1);
2054 current_route_break_variables_.push_back(-1);
2057 lp_break_start[br] =
2058 solver->AddVariable(break_start_min, break_start_max);
2060 absl::StrFormat(
"lp_break_start(%ld)", br));
2061 lp_break_end[br] = solver->AddVariable(break_end_min, break_end_max);
2063 absl::StrFormat(
"lp_break_end(%ld)", br));
2064 lp_break_duration[br] =
2065 solver->AddVariable(break_duration_min, break_duration_max);
2067 absl::StrFormat(
"lp_break_duration(%ld)", br));
2069 solver->AddLinearConstraint(0, 0,
2070 {{lp_break_end[br], 1},
2071 {lp_break_start[br], -1},
2072 {lp_break_duration[br], -1}});
2074 all_break_variables_[all_break_variables_offset + 2 * br] =
2076 all_break_variables_[all_break_variables_offset + 2 * br + 1] =
2078 current_route_break_variables_.push_back(lp_break_start[br]);
2079 current_route_break_variables_.push_back(lp_break_end[br]);
2081 if (break_end_min <= vehicle_start_max ||
2082 vehicle_end_min <= break_start_max) {
2083 all_break_variables_[all_break_variables_offset + 2 * br] = -1;
2084 all_break_variables_[all_break_variables_offset + 2 * br + 1] = -1;
2085 current_route_break_variables_.push_back(-1);
2086 current_route_break_variables_.push_back(-1);
2094 const int break_in_one_slack_ct = solver->CreateNewConstraint(1, 1);
2096 if (solver->IsCPSATSolver()) {
2098 if (break_end_min <= vehicle_start_max) {
2099 const int ct = solver->AddLinearConstraint(
2100 0, std::numeric_limits<int64_t>::max(),
2101 {{lp_cumuls.front(), 1}, {lp_break_end[br], -1}});
2102 const int break_is_before_route = solver->AddVariable(0, 1);
2104 solver, break_is_before_route,
2105 absl::StrFormat(
"break_is_before_route(%ld)", br));
2106 solver->SetEnforcementLiteral(
ct, break_is_before_route);
2107 solver->SetCoefficient(break_in_one_slack_ct, break_is_before_route, 1);
2110 if (vehicle_end_min <= break_start_max) {
2111 const int ct = solver->AddLinearConstraint(
2112 0, std::numeric_limits<int64_t>::max(),
2113 {{lp_break_start[br], 1}, {lp_cumuls.back(), -1}});
2114 const int break_is_after_route = solver->AddVariable(0, 1);
2116 solver, break_is_after_route,
2117 absl::StrFormat(
"break_is_after_route(%ld)", br));
2118 solver->SetEnforcementLiteral(
ct, break_is_after_route);
2119 solver->SetCoefficient(break_in_one_slack_ct, break_is_after_route, 1);
2124 for (
int pos = 0; pos < path_size - 1; ++pos) {
2127 const int64_t slack_start_min =
2128 CapAdd(current_route_min_cumuls_[pos], pre_travel[pos]);
2129 if (slack_start_min > break_start_max)
break;
2130 const int64_t slack_end_max =
2131 CapSub(current_route_max_cumuls_[pos + 1], post_travel[pos]);
2132 if (break_end_min > slack_end_max)
continue;
2133 const int64_t slack_duration_max =
2134 std::min(
CapSub(
CapSub(current_route_max_cumuls_[pos + 1],
2135 current_route_min_cumuls_[pos]),
2136 fixed_transit[pos]),
2137 dimension_->SlackVar(path[pos])->Max());
2138 if (slack_duration_max < break_duration_min)
continue;
2145 const int break_in_slack = solver->AddVariable(0, 1);
2147 solver, break_in_slack,
2148 absl::StrFormat(
"break_in_slack(%ld, %ld)", br, pos));
2149 if (slack_linear_lower_bound_ct[pos] == -1) {
2150 slack_linear_lower_bound_ct[pos] = solver->AddLinearConstraint(
2151 std::numeric_limits<int64_t>::min(), 0, {{lp_slacks[pos], -1}});
2156 if (break_in_one_slack_ct < slack_linear_lower_bound_ct[pos]) {
2157 solver->SetCoefficient(break_in_one_slack_ct, break_in_slack, 1);
2158 solver->SetCoefficient(slack_linear_lower_bound_ct[pos], break_in_slack,
2159 break_duration_min);
2161 solver->SetCoefficient(slack_linear_lower_bound_ct[pos], break_in_slack,
2162 break_duration_min);
2163 solver->SetCoefficient(break_in_one_slack_ct, break_in_slack, 1);
2166 if (solver->IsCPSATSolver()) {
2171 const int break_duration_in_slack =
2172 solver->AddVariable(0, slack_duration_max);
2174 solver, break_duration_in_slack,
2175 absl::StrFormat(
"break_duration_in_slack(%ld, %ld)", br, pos));
2176 solver->AddProductConstraint(break_duration_in_slack,
2177 {break_in_slack, lp_break_duration[br]});
2178 if (slack_exact_lower_bound_ct[pos] == -1) {
2179 slack_exact_lower_bound_ct[pos] = solver->AddLinearConstraint(
2180 std::numeric_limits<int64_t>::min(), 0, {{lp_slacks[pos], -1}});
2182 solver->SetCoefficient(slack_exact_lower_bound_ct[pos],
2183 break_duration_in_slack, 1);
2186 const int break_start_after_current_ct = solver->AddLinearConstraint(
2187 pre_travel[pos], std::numeric_limits<int64_t>::max(),
2188 {{lp_break_start[br], 1}, {lp_cumuls[pos], -1}});
2189 solver->SetEnforcementLiteral(break_start_after_current_ct,
2192 const int break_ends_before_next_ct = solver->AddLinearConstraint(
2193 post_travel[pos], std::numeric_limits<int64_t>::max(),
2194 {{lp_cumuls[pos + 1], 1}, {lp_break_end[br], -1}});
2195 solver->SetEnforcementLiteral(break_ends_before_next_ct,
2201 if (!solver->IsCPSATSolver())
return true;
2202 if (!dimension_->GetBreakDistanceDurationOfVehicle(vehicle).empty()) {
2206 dimension_->GetBreakIntervalsOfVehicle(vehicle)) {
2207 if (!
interval->MustBePerformed())
return true;
2210 for (
int br = 1; br < num_breaks; ++br) {
2211 if (lp_break_start[br] == -1 || lp_break_start[br - 1] == -1)
continue;
2212 solver->AddLinearConstraint(
2213 0, std::numeric_limits<int64_t>::max(),
2214 {{lp_break_end[br - 1], -1}, {lp_break_start[br], 1}});
2217 for (
const auto& distance_duration :
2218 dimension_->GetBreakDistanceDurationOfVehicle(vehicle)) {
2219 const int64_t limit = distance_duration.first;
2220 const int64_t min_break_duration = distance_duration.second;
2248 int previous_cover = solver->AddVariable(
CapAdd(vehicle_start_min, limit),
2249 CapAdd(vehicle_start_max, limit));
2251 solver->AddLinearConstraint(limit, limit,
2252 {{previous_cover, 1}, {lp_cumuls.front(), -1}});
2253 for (
int br = 0; br < num_breaks; ++br) {
2254 if (lp_break_start[br] == -1)
continue;
2255 const int64_t break_end_min =
CapSub(breaks[br]->EndMin(), cumul_offset);
2256 const int64_t break_end_max =
CapSub(breaks[br]->EndMax(), cumul_offset);
2259 const int break_is_eligible = solver->AddVariable(0, 1);
2261 absl::StrFormat(
"break_is_eligible(%ld)", br));
2262 const int break_is_not_eligible = solver->AddVariable(0, 1);
2264 solver, break_is_not_eligible,
2265 absl::StrFormat(
"break_is_not_eligible(%ld)", br));
2267 solver->AddLinearConstraint(
2268 1, 1, {{break_is_eligible, 1}, {break_is_not_eligible, 1}});
2269 const int positive_ct = solver->AddLinearConstraint(
2270 min_break_duration, std::numeric_limits<int64_t>::max(),
2271 {{lp_break_end[br], 1}, {lp_break_start[br], -1}});
2272 solver->SetEnforcementLiteral(positive_ct, break_is_eligible);
2273 const int negative_ct = solver->AddLinearConstraint(
2274 std::numeric_limits<int64_t>::min(), min_break_duration - 1,
2275 {{lp_break_end[br], 1}, {lp_break_start[br], -1}});
2276 solver->SetEnforcementLiteral(negative_ct, break_is_not_eligible);
2283 const int break_cover = solver->AddVariable(
2284 CapAdd(std::min(vehicle_start_min, break_end_min), limit),
2285 CapAdd(std::max(vehicle_start_min, break_end_max), limit));
2287 absl::StrFormat(
"break_cover(%ld)", br));
2288 const int limit_cover_ct = solver->AddLinearConstraint(
2289 limit, limit, {{break_cover, 1}, {lp_break_end[br], -1}});
2290 solver->SetEnforcementLiteral(limit_cover_ct, break_is_eligible);
2291 const int empty_cover_ct = solver->AddLinearConstraint(
2292 CapAdd(vehicle_start_min, limit),
CapAdd(vehicle_start_min, limit),
2293 {{break_cover, 1}});
2294 solver->SetEnforcementLiteral(empty_cover_ct, break_is_not_eligible);
2297 solver->AddVariable(
CapAdd(vehicle_start_min, limit),
2298 std::numeric_limits<int64_t>::max());
2300 solver->AddMaximumConstraint(cover, {previous_cover, break_cover});
2303 const int route_end_is_not_covered = solver->AddReifiedLinearConstraint(
2304 1, std::numeric_limits<int64_t>::max(),
2305 {{lp_cumuls.back(), 1}, {previous_cover, -1}});
2306 const int break_start_cover_ct = solver->AddLinearConstraint(
2307 0, std::numeric_limits<int64_t>::max(),
2308 {{previous_cover, 1}, {lp_break_start[br], -1}});
2309 solver->SetEnforcementLiteral(break_start_cover_ct,
2310 route_end_is_not_covered);
2312 previous_cover = cover;
2314 solver->AddLinearConstraint(0, std::numeric_limits<int64_t>::max(),
2315 {{previous_cover, 1}, {lp_cumuls.back(), -1}});
2321bool DimensionCumulOptimizerCore::SetGlobalConstraints(
2322 const std::function<int64_t(int64_t)>& next_accessor, int64_t cumul_offset,
2323 bool optimize_costs,
bool optimize_resource_assignment,
2324 RoutingLinearSolverWrapper* solver) {
2327 const int64_t global_span_coeff = dimension_->global_span_cost_coefficient();
2328 if (optimize_costs && global_span_coeff > 0) {
2330 const int global_span_var = solver->CreateNewPositiveVariable();
2332 solver->AddLinearConstraint(
2334 {{global_span_var, 1}, {max_end_cumul_, -1}, {min_start_cumul_, 1}});
2339 solver->SetObjectiveCoefficient(global_span_var, global_span_coeff);
2343 for (
const RoutingDimension::NodePrecedence& precedence :
2344 dimension_->GetNodePrecedences()) {
2345 const int first_cumul_var = index_to_cumul_variable_[precedence.first_node];
2346 const int second_cumul_var =
2347 index_to_cumul_variable_[precedence.second_node];
2348 if (first_cumul_var < 0 || second_cumul_var < 0) {
2353 DCHECK_NE(first_cumul_var, second_cumul_var)
2354 <<
"Dimension " << dimension_->name()
2355 <<
" has a self-precedence on node " << precedence.first_node <<
".";
2358 const int ct = solver->CreateNewConstraint(
2359 precedence.offset, std::numeric_limits<int64_t>::max());
2360 solver->SetCoefficient(
ct, second_cumul_var, 1);
2361 solver->SetCoefficient(
ct, first_cumul_var, -1);
2364 if (optimize_resource_assignment &&
2365 !SetGlobalConstraintsForResourceAssignment(next_accessor, cumul_offset,
2372bool DimensionCumulOptimizerCore::SetGlobalConstraintsForResourceAssignment(
2373 const std::function<int64_t(int64_t)>& next_accessor, int64_t cumul_offset,
2374 RoutingLinearSolverWrapper* solver) {
2375 if (!solver->IsCPSATSolver()) {
2381 const RoutingModel&
model = *dimension_->model();
2382 const int num_vehicles =
model.vehicles();
2383 const auto& resource_groups =
model.GetResourceGroups();
2384 for (
int rg_index :
model.GetDimensionResourceGroupIndices(dimension_)) {
2393 const ResourceGroup& resource_group = *resource_groups[rg_index];
2394 DCHECK(!resource_group.GetVehiclesRequiringAResource().empty());
2396 const std::vector<ResourceGroup::Resource>& resources =
2397 resource_group.GetResources();
2398 int num_required_resources = 0;
2399 static const int kNoConstraint = -1;
2402 std::vector<int> vehicle_constraints(
model.vehicles(), kNoConstraint);
2403 for (
int v : resource_group.GetVehiclesRequiringAResource()) {
2404 if (
model.IsEnd(next_accessor(
model.Start(v))) &&
2405 !
model.IsVehicleUsedWhenEmpty(v)) {
2409 num_required_resources++;
2410 vehicle_constraints[v] = solver->CreateNewConstraint(1, 1);
2414 const int num_resources = resources.size();
2415 std::vector<int> resource_constraints(num_resources, kNoConstraint);
2416 int num_available_resources = 0;
2417 for (
int r = 0; r < num_resources; r++) {
2418 const ResourceGroup::Attributes& attributes =
2419 resources[r].GetDimensionAttributes(dimension_);
2420 if (attributes.start_domain().Max() < cumul_offset ||
2421 attributes.end_domain().Max() < cumul_offset) {
2427 num_available_resources++;
2428 resource_constraints[r] = solver->CreateNewConstraint(0, 1);
2431 if (num_required_resources > num_available_resources) {
2437 std::vector<int>& resource_to_vehicle_assignment_variables =
2438 resource_group_to_resource_to_vehicle_assignment_variables_[rg_index];
2439 resource_to_vehicle_assignment_variables.assign(
2440 num_resources * num_vehicles, -1);
2446 DCHECK_EQ(resource_group.Index(), rg_index);
2447 for (
int v : resource_group.GetVehiclesRequiringAResource()) {
2448 if (vehicle_constraints[v] == kNoConstraint)
continue;
2449 IntVar*
const resource_var =
model.ResourceVar(v, rg_index);
2450 std::unique_ptr<IntVarIterator> it(
2451 resource_var->MakeDomainIterator(
false));
2452 for (
int r : InitAndGetValues(it.get())) {
2453 if (r < 0)
continue;
2454 DCHECK_LT(r, num_resources);
2455 if (resource_constraints[r] == kNoConstraint)
continue;
2457 const int assign_r_to_v = solver->AddVariable(0, 1);
2459 solver, assign_r_to_v,
2460 absl::StrFormat(
"assign_r_to_v(%ld, %ld)", r, v));
2461 resource_to_vehicle_assignment_variables[r * num_vehicles + v] =
2466 if (vehicle_constraints[v] < resource_constraints[r]) {
2467 solver->SetCoefficient(vehicle_constraints[v], assign_r_to_v, 1);
2468 solver->SetCoefficient(resource_constraints[r], assign_r_to_v, 1);
2470 solver->SetCoefficient(resource_constraints[r], assign_r_to_v, 1);
2471 solver->SetCoefficient(vehicle_constraints[v], assign_r_to_v, 1);
2474 const auto& add_domain_constraint =
2475 [&solver, cumul_offset, assign_r_to_v](
const Domain& domain,
2476 int cumul_variable) {
2480 ClosedInterval cumul_bounds;
2481 if (!GetDomainOffsetBounds(domain, cumul_offset, &cumul_bounds)) {
2483 solver->SetVariableBounds(assign_r_to_v, 0, 0);
2486 const int cumul_constraint = solver->AddLinearConstraint(
2487 cumul_bounds.start, cumul_bounds.end, {{cumul_variable, 1}});
2488 solver->SetEnforcementLiteral(cumul_constraint, assign_r_to_v);
2490 const ResourceGroup::Attributes& attributes =
2491 resources[r].GetDimensionAttributes(dimension_);
2492 add_domain_constraint(attributes.start_domain(),
2493 index_to_cumul_variable_[
model.Start(v)]);
2494 add_domain_constraint(attributes.end_domain(),
2495 index_to_cumul_variable_[
model.End(v)]);
2502#undef SET_DEBUG_VARIABLE_NAME
2504void DimensionCumulOptimizerCore::SetValuesFromLP(
2505 const std::vector<int>& lp_variables, int64_t offset,
2506 RoutingLinearSolverWrapper* solver, std::vector<int64_t>* lp_values)
const {
2507 if (lp_values ==
nullptr)
return;
2508 lp_values->assign(lp_variables.size(), std::numeric_limits<int64_t>::min());
2509 for (
int i = 0;
i < lp_variables.size();
i++) {
2510 const int lp_var = lp_variables[
i];
2511 if (lp_var < 0)
continue;
2512 const double lp_value_double = solver->GetValue(lp_var);
2513 const int64_t lp_value_int64 =
2514 (lp_value_double >= std::numeric_limits<int64_t>::max())
2515 ? std::numeric_limits<int64_t>::max()
2516 : MathUtil::FastInt64Round(lp_value_double);
2517 (*lp_values)[
i] =
CapAdd(lp_value_int64, offset);
2521void DimensionCumulOptimizerCore::SetResourceIndices(
2522 RoutingLinearSolverWrapper* solver,
2523 std::vector<std::vector<int>>* resource_indices_per_group)
const {
2524 if (resource_indices_per_group ==
nullptr ||
2525 resource_group_to_resource_to_vehicle_assignment_variables_.empty()) {
2528 const RoutingModel&
model = *dimension_->model();
2529 const int num_vehicles =
model.vehicles();
2530 DCHECK(!
model.GetDimensionResourceGroupIndices(dimension_).empty());
2531 const auto& resource_groups =
model.GetResourceGroups();
2532 resource_indices_per_group->resize(resource_groups.size());
2533 for (
int rg_index :
model.GetDimensionResourceGroupIndices(dimension_)) {
2534 const ResourceGroup& resource_group = *resource_groups[rg_index];
2535 DCHECK(!resource_group.GetVehiclesRequiringAResource().empty());
2537 const int num_resources = resource_group.Size();
2538 std::vector<int>& resource_indices =
2539 resource_indices_per_group->at(rg_index);
2540 resource_indices.assign(num_vehicles, -1);
2542 const std::vector<int>& resource_to_vehicle_assignment_variables =
2543 resource_group_to_resource_to_vehicle_assignment_variables_[rg_index];
2544 DCHECK_EQ(resource_to_vehicle_assignment_variables.size(),
2545 num_resources * num_vehicles);
2546 for (
int v : resource_group.GetVehiclesRequiringAResource()) {
2547 for (
int r = 0; r < num_resources; r++) {
2548 const int assignment_var =
2549 resource_to_vehicle_assignment_variables[r * num_vehicles + v];
2550 if (assignment_var >= 0 && solver->GetValue(assignment_var) == 1) {
2552 resource_indices[v] = r;
2562GlobalDimensionCumulOptimizer::GlobalDimensionCumulOptimizer(
2563 const RoutingDimension* dimension,
2564 RoutingSearchParameters::SchedulingSolver solver_type)
2565 : optimizer_core_(dimension,
2567 !dimension->GetNodePrecedences().empty()) {
2568 switch (solver_type) {
2569 case RoutingSearchParameters::SCHEDULING_GLOP: {
2570 solver_ = std::make_unique<RoutingGlopWrapper>(
2572 ->GetDimensionResourceGroupIndices(
dimension)
2574 GetGlopParametersForGlobalLP());
2577 case RoutingSearchParameters::SCHEDULING_CP_SAT: {
2578 solver_ = std::make_unique<RoutingCPSatWrapper>();
2582 LOG(DFATAL) <<
"Unrecognized solver type: " << solver_type;
2588 const std::function<int64_t(int64_t)>& next_accessor,
2589 int64_t* optimal_cost_without_transits) {
2590 return optimizer_core_.
Optimize(next_accessor, {}, solver_.get(),
nullptr,
2592 optimal_cost_without_transits,
nullptr);
2596 const std::function<int64_t(int64_t)>& next_accessor,
2597 const std::vector<RoutingModel::RouteDimensionTravelInfo>&
2598 dimension_travel_info_per_route,
2599 std::vector<int64_t>* optimal_cumuls, std::vector<int64_t>* optimal_breaks,
2600 std::vector<std::vector<int>>* optimal_resource_indices) {
2601 return optimizer_core_.
Optimize(next_accessor,
2602 dimension_travel_info_per_route,
2603 solver_.get(), optimal_cumuls, optimal_breaks,
2604 optimal_resource_indices,
nullptr,
nullptr);
2608 const std::function<int64_t(int64_t)>& next_accessor,
2609 const std::vector<RoutingModel::RouteDimensionTravelInfo>&
2610 dimension_travel_info_per_route,
2611 std::vector<int64_t>* packed_cumuls, std::vector<int64_t>* packed_breaks) {
2613 next_accessor, dimension_travel_info_per_route, solver_.get(),
2614 packed_cumuls, packed_breaks);
2619bool AllValuesContainedExcept(
const IntVar&
var,
const std::vector<int>& values,
2620 const absl::flat_hash_set<int>& ignored_values) {
2621 for (
int val : values) {
2622 if (!ignored_values.contains(val) && !
var.
Contains(val))
return false;
2627template <
typename T>
2628void MoveValuesToIndicesFrom(std::vector<T>* out_values,
2629 const std::vector<int>& out_indices_to_evaluate,
2630 const std::function<
int(
int)>& index_evaluator,
2631 std::vector<T>& values_to_copy) {
2632 if (out_values ==
nullptr) {
2633 DCHECK(values_to_copy.empty());
2636 DCHECK_EQ(values_to_copy.size(), out_indices_to_evaluate.size());
2637 for (
int i = 0;
i < out_indices_to_evaluate.size();
i++) {
2638 const int output_index = index_evaluator(out_indices_to_evaluate[i]);
2639 DCHECK_LT(output_index, out_values->size());
2640 out_values->at(output_index) = std::move(values_to_copy[i]);
2647 int v,
const RoutingModel::ResourceGroup& resource_group,
2649 absl::flat_hash_set<int>>&
2650 ignored_resources_per_class,
2651 const std::function<int64_t(int64_t)>& next_accessor,
2652 const std::function<int64_t(int64_t, int64_t)>& transit_accessor,
2658 DCHECK(lp_optimizer !=
nullptr);
2659 DCHECK(mp_optimizer !=
nullptr);
2665 const RoutingDimension* dimension = lp_optimizer->
dimension();
2666 DCHECK_EQ(dimension, mp_optimizer->
dimension());
2667 RoutingModel*
const model = dimension->model();
2668 if (!resource_group.VehicleRequiresAResource(v) ||
2669 (!
model->IsVehicleUsedWhenEmpty(v) &&
2670 next_accessor(
model->Start(v)) ==
model->End(v))) {
2673 if (
model->CheckLimit()) {
2678 IntVar*
const resource_var =
model->ResourceVar(v, resource_group.Index());
2679 const int num_resource_classes = resource_group.GetResourceClassesCount();
2680 std::vector<int> considered_resource_indices;
2681 considered_resource_indices.reserve(
2682 std::min<int>(resource_var->
Size(), num_resource_classes));
2683 std::vector<bool> resource_class_considered(num_resource_classes,
false);
2689 const RoutingModel::ResourceClassIndex resource_class =
2690 resource_group.GetResourceClassIndex(res);
2691 const int rc_index = resource_class.value();
2692 const absl::flat_hash_set<int>& ignored_resources =
2693 ignored_resources_per_class[resource_class];
2694 if (resource_class_considered[rc_index] ||
2695 ignored_resources.contains(res)) {
2698 resource_class_considered[rc_index] =
true;
2703 DCHECK(AllValuesContainedExcept(
2704 *resource_var, resource_group.GetResourceIndicesInClass(resource_class),
2705 ignored_resources));
2706 considered_resource_indices.push_back(res);
2708 const bool use_mp_optimizer =
2709 dimension->HasBreakConstraints() &&
2710 !dimension->GetBreakIntervalsOfVehicle(v).empty();
2712 use_mp_optimizer ? mp_optimizer : lp_optimizer;
2714 const std::vector<ResourceGroup::Resource>& resources =
2715 resource_group.GetResources();
2716 std::vector<int64_t> considered_assignment_costs;
2717 std::vector<std::vector<int64_t>> considered_cumul_values;
2718 std::vector<std::vector<int64_t>> considered_break_values;
2719 std::vector<DimensionSchedulingStatus> statuses =
2721 v, next_accessor, transit_accessor, resources,
2722 considered_resource_indices, optimize_vehicle_costs,
2723 &considered_assignment_costs,
2724 cumul_values ==
nullptr ?
nullptr : &considered_cumul_values,
2725 break_values ==
nullptr ?
nullptr : &considered_break_values);
2727 if (statuses.empty() ||
2728 (statuses.size() == 1 &&
2742 const auto resource_to_class_index = [&resource_group](
int resource_index) {
2743 return resource_group.GetResourceClassIndex(resource_index).value();
2746 resource_to_class_index, considered_assignment_costs);
2747 MoveValuesToIndicesFrom(
cumul_values, considered_resource_indices,
2748 resource_to_class_index, considered_cumul_values);
2749 MoveValuesToIndicesFrom(
break_values, considered_resource_indices,
2750 resource_to_class_index, considered_break_values);
2752 if (use_mp_optimizer) {
2757 [](int64_t cost) {
return cost >= 0; });
2760 std::vector<int> mp_resource_indices;
2761 DCHECK_EQ(statuses.size(), considered_resource_indices.size());
2762 for (
int i = 0; i < considered_resource_indices.size(); i++) {
2764 mp_resource_indices.push_back(considered_resource_indices[i]);
2768 std::vector<int64_t> mp_assignment_costs;
2769 std::vector<std::vector<int64_t>> mp_cumul_values;
2770 std::vector<std::vector<int64_t>> mp_break_values;
2772 v, next_accessor, transit_accessor, resources, mp_resource_indices,
2773 optimize_vehicle_costs, &mp_assignment_costs,
2776 if (!mp_resource_indices.empty() && mp_assignment_costs.empty()) {
2782 resource_to_class_index, mp_assignment_costs);
2783 MoveValuesToIndicesFrom(
cumul_values, mp_resource_indices,
2784 resource_to_class_index, mp_cumul_values);
2785 MoveValuesToIndicesFrom(
break_values, mp_resource_indices,
2786 resource_to_class_index, mp_break_values);
2789 [](int64_t cost) {
return cost >= 0; });
2793 const std::vector<int>& vehicles,
2795 std::vector<int>>& resource_indices_per_class,
2797 absl::flat_hash_set<int>>&
2798 ignored_resources_per_class,
2799 std::function<
const std::vector<int64_t>*(
int)>
2800 vehicle_to_resource_class_assignment_costs,
2801 std::vector<int>* resource_indices) {
2802 const int total_num_resources = absl::c_accumulate(
2803 resource_indices_per_class, 0,
2804 [](
int acc,
const std::vector<int>& res) {
return acc + res.size(); });
2805 DCHECK_GE(total_num_resources, 1);
2806 const int num_ignored_resources =
2807 absl::c_accumulate(ignored_resources_per_class, 0,
2808 [](
int acc,
const absl::flat_hash_set<int>& res) {
2809 return acc + res.size();
2811 const int num_resources = total_num_resources - num_ignored_resources;
2812 const int num_vehicles = vehicles.size();
2813 int num_total_vehicles = -1;
2814 if (resource_indices !=
nullptr) {
2815 num_total_vehicles = resource_indices->size();
2818 resource_indices->clear();
2819 DCHECK_GE(num_total_vehicles, num_vehicles);
2820 for (
int v : vehicles) {
2822 DCHECK_LT(v, num_total_vehicles);
2830 const int num_resource_classes = resource_indices_per_class.size();
2831 std::vector<const std::vector<int64_t>*> vi_to_rc_cost(num_vehicles);
2832 int num_vehicles_to_assign = 0;
2833 for (
int i = 0; i < num_vehicles; ++i) {
2834 vi_to_rc_cost[i] = vehicle_to_resource_class_assignment_costs(vehicles[i]);
2835 if (!vi_to_rc_cost[i]->empty()) {
2836 DCHECK_EQ(vi_to_rc_cost[i]->
size(), num_resource_classes);
2837 ++num_vehicles_to_assign;
2840 if (num_vehicles_to_assign > num_resources) {
2841 VLOG(3) <<
"Less resources (" << num_resources <<
") than the vehicles"
2842 <<
" requiring one (" << num_vehicles_to_assign <<
")";
2850 for (
int i = 0; i < num_vehicles; ++i) {
2851 if (!vi_to_rc_cost[i]->empty() &&
2852 *absl::c_max_element(*vi_to_rc_cost[i]) < 0) {
2853 VLOG(3) <<
"Vehicle #" << vehicles[i] <<
" has no feasible resource";
2861 int64_t max_arc_cost = 0;
2862 for (
const std::vector<int64_t>* costs : vi_to_rc_cost) {
2863 if (costs->empty())
continue;
2864 max_arc_cost = std::max(max_arc_cost, *absl::c_max_element(*costs));
2869 const int real_num_nodes = 4 + num_vehicles + num_resource_classes;
2870 const int64_t max_acceptable_arc_cost =
kint64max / (4 * real_num_nodes) - 1;
2873 int cost_right_shift = 0;
2874 while ((max_arc_cost >> cost_right_shift) > max_acceptable_arc_cost) {
2884 2 + num_vehicles + num_resource_classes,
2885 num_vehicles + num_vehicles * num_resource_classes +
2886 num_resource_classes);
2887 const int source_index = num_vehicles + num_resource_classes;
2888 const int sink_index = source_index + 1;
2889 const auto flow_rc_index = [num_vehicles](
int rc) {
2890 return num_vehicles + rc;
2895 if (resource_indices !=
nullptr) {
2896 vehicle_to_rc_arc_index =
2899 for (
int vi = 0; vi < num_vehicles; ++vi) {
2907 for (
int rc = 0; rc < num_resource_classes; rc++) {
2909 if (assignment_cost < 0)
continue;
2911 vi, flow_rc_index(rc), 1, assignment_cost >> cost_right_shift);
2912 if (resource_indices !=
nullptr) {
2913 vehicle_to_rc_arc_index[vi][rc] =
arc;
2920 using RCIndex = RoutingModel::ResourceClassIndex;
2921 for (
int rc = 0; rc < num_resource_classes; rc++) {
2922 const RCIndex rci(rc);
2923 const int num_available_res = resource_indices_per_class[rci].size() -
2924 ignored_resources_per_class[rci].size();
2925 DCHECK_GE(num_available_res, 0);
2927 num_available_res, 0);
2936 VLOG(3) <<
"Non-OPTIMAL flow result";
2940 if (resource_indices !=
nullptr) {
2942 resource_indices->assign(num_total_vehicles, -1);
2943 std::vector<int> current_resource_pos_for_class(num_resource_classes, 0);
2944 for (
int vi = 0; vi < num_vehicles; ++vi) {
2945 if (vi_to_rc_cost[vi]->empty()) {
2949 for (
int rc = 0; rc < num_resource_classes; rc++) {
2950 const ArcIndex arc = vehicle_to_rc_arc_index[vi][rc];
2952 const RCIndex rci(rc);
2953 const std::vector<int>& class_resource_indices =
2954 resource_indices_per_class[rci];
2955 const absl::flat_hash_set<int>& ignored_resources =
2956 ignored_resources_per_class[rci];
2957 int& pos = current_resource_pos_for_class[rc];
2958 DCHECK_LT(pos, class_resource_indices.size());
2959 while (ignored_resources.contains(class_resource_indices[pos])) {
2961 DCHECK_LT(pos, class_resource_indices.size());
2963 resource_indices->at(vehicles[vi]) = class_resource_indices[pos++];
2971 DCHECK_LE(cost,
kint64max >> cost_right_shift);
2972 return cost << cost_right_shift;
2976 if (number ==
kint64min)
return "-infty";
2977 if (number ==
kint64max)
return "+infty";
2978 return std::to_string(number);
2982 const ::google::protobuf::RepeatedField<int64_t>* domain) {
2983 if (domain->size() > 2 && domain->size() % 2 == 0) {
2984 std::string s =
"∈ ";
2985 for (
int i = 0; i < domain->size(); i += 2) {
2986 s += absl::StrFormat(
"[%s, %s]",
Int64ToStr(domain->Get(i)),
2988 if (i < domain->
size() - 2) s +=
" ∪ ";
2991 }
else if (domain->size() == 2) {
2992 if (domain->Get(0) == domain->Get(1)) {
2993 return absl::StrFormat(
"= %s",
Int64ToStr(domain->Get(0)));
2994 }
else if (domain->Get(0) == 0 && domain->Get(1) == 1) {
2996 }
else if (domain->Get(0) == std::numeric_limits<int64_t>::min() &&
2997 domain->Get(1) == std::numeric_limits<int64_t>::max()) {
2999 }
else if (domain->Get(0) == std::numeric_limits<int64_t>::min()) {
3000 return absl::StrFormat(
"≤ %s",
Int64ToStr(domain->Get(1)));
3001 }
else if (domain->Get(1) == std::numeric_limits<int64_t>::max()) {
3002 return absl::StrFormat(
"≥ %s",
Int64ToStr(domain->Get(0)));
3004 return absl::StrFormat(
"∈ [%ls, %s]",
Int64ToStr(domain->Get(0)),
3006 }
else if (domain->size() == 1) {
3007 return absl::StrFormat(
"= %s",
Int64ToStr(domain->Get(0)));
3009 return absl::StrFormat(
"∈ Unknown domain (size=%ld)", domain->size());
3014 std::pair<sat::IntegerVariableProto, int>& variable_pair,
3015 const sat::CpSolverResponse& response_) {
3017 sat::IntegerVariableProto& variable = variable_pair.first;
3018 const int index = variable_pair.second;
3019 if (response_.IsInitialized() && variable.IsInitialized() &&
3020 (response_.status() == sat::CpSolverStatus::OPTIMAL ||
3021 response_.status() == sat::CpSolverStatus::FEASIBLE)) {
3022 const double lp_value_double = response_.solution(
index);
3023 const int64_t lp_value_int64 =
3024 (lp_value_double >= std::numeric_limits<int64_t>::max())
3025 ? std::numeric_limits<int64_t>::max()
3036 const sat::CpModelProto& model_,
3037 bool show_enforcement =
true) {
3039 if (constraint.has_linear()) {
3040 const auto& linear = constraint.linear();
3041 for (
int j = 0; j < linear.vars().
size(); ++j) {
3042 const std::string sign = linear.coeffs(j) > 0 ?
"+" :
"-";
3043 const std::string mult =
3044 std::abs(linear.coeffs(j)) != 1
3045 ? std::to_string(std::abs(linear.coeffs(j))) +
" * "
3047 if (j > 0 || sign !=
"+") s += sign +
" ";
3048 s += mult + model_.variables(linear.vars(j)).name() +
" ";
3053 if (show_enforcement) {
3054 for (
int j = 0; j < constraint.enforcement_literal_size(); ++j) {
3055 s += (j == 0) ?
"\t if " :
" and ";
3056 s += model_.variables(constraint.enforcement_literal(j)).name();
3060 s += constraint.ShortDebugString();
3066 absl::flat_hash_map<std::string, std::pair<sat::IntegerVariableProto, int>>&
3068 absl::flat_hash_map<std::string, std::vector<int>>& variable_instances,
3069 absl::flat_hash_map<std::string, absl::flat_hash_set<std::string>>&
3071 const sat::CpSolverResponse& response_, absl::string_view variable,
3072 std::string prefix =
"") {
3073 if (variable.empty()) {
3075 const auto& childs = variable_childs[
""];
3076 for (
const std::string& child : childs) {
3079 response_, child, prefix) +
3085 const auto& instances = variable_instances[variable];
3086 std::string variable_display(variable);
3087 std::size_t bracket_pos = variable.find_last_of(
')');
3088 if (bracket_pos != std::string::npos) {
3089 variable_display = variable.substr(bracket_pos + 1);
3091 std::string s = variable_display +
" | ";
3092 prefix += std::string(variable_display.length(),
' ') +
" | ";
3093 for (
int i = 0; i < instances.size(); ++i) {
3094 const std::string instance_name =
3095 absl::StrFormat(
"%s(%ld)", variable, instances[i]);
3096 if (i > 0) s += prefix;
3097 s += absl::StrFormat(
"%ld: %s", instances[i],
3101 const auto& childs = variable_childs[instance_name];
3102 for (
const std::string& child : childs) {
3103 s +=
"\n" + prefix +
"| ";
3105 response_, child, prefix +
"| ");
3107 if (childs.empty()) s +=
"\n";
3114 std::vector<std::vector<std::string>> constraints_apart;
3115 constraints_apart.push_back(
3116 {
"compression_cost",
"travel_compression_absolute"});
3121 absl::flat_hash_map<std::string, std::vector<int>> variable_instances;
3125 absl::flat_hash_map<std::string, absl::flat_hash_set<std::string>>
3128 absl::flat_hash_map<std::string, std::pair<sat::IntegerVariableProto, int>>
3130 variable_children[
""] = {};
3132 const int num_constraints = model_.constraints_size();
3133 const int num_variables = model_.variables_size();
3134 int num_binary_variables = 0;
3135 for (
int i = 0; i < num_variables; ++i) {
3136 const auto& variable = model_.variables(i);
3137 const auto&
name = variable.name();
3138 const int pos_bracket =
name.find_last_of(
'(');
3139 if (pos_bracket != std::string::npos) {
3140 const std::string lemma =
name.substr(0, pos_bracket);
3141 const int pos_closing_bracket =
name.find_last_of(
')');
3142 CHECK_NE(pos_closing_bracket, std::string::npos);
3144 std::stoi(
name.substr(pos_bracket + 1, pos_closing_bracket));
3145 std::vector<int>* instances =
gtl::FindOrNull(variable_instances, lemma);
3146 if (instances !=
nullptr) {
3147 instances->push_back(
index);
3149 variable_instances[lemma] = {
index};
3151 variable_children[
name] = {};
3153 std::string parent =
"";
3154 const int pos_parent_closing_bracket = lemma.find_last_of(
')');
3155 if (pos_parent_closing_bracket != std::string::npos) {
3156 parent = lemma.substr(0, pos_parent_closing_bracket + 1);
3158 variable_children[parent].emplace(lemma);
3159 variables[
name] = std::make_pair(variable, i);
3160 if (variable.domain(0) == 0 & variable.domain(1) == 1) {
3161 ++num_binary_variables;
3172 absl::flat_hash_map<std::string, std::vector<sat::ConstraintProto>>
3174 absl::flat_hash_map<std::vector<std::string>,
3175 std::vector<sat::ConstraintProto>>
3177 for (
int i = 0; i < num_constraints; ++i) {
3178 const auto& constraint = model_.constraints(i);
3179 std::string enforcement =
"";
3180 if (constraint.enforcement_literal_size() == 1) {
3181 enforcement = model_.variables(constraint.enforcement_literal(0)).name();
3182 }
else if (constraint.enforcement_literal_size() > 1) {
3183 enforcement =
"multiple";
3185 if (constraint.has_linear()) {
3186 const auto& linear = constraint.linear();
3187 std::vector<std::string> key;
3188 for (
int j = 0; j < linear.vars().
size(); ++j) {
3189 std::string var_name = model_.variables(linear.vars(j)).name();
3190 std::string lemma = var_name.substr(0, var_name.find_last_of(
'('));
3191 key.push_back(lemma);
3194 if (constraint_group !=
nullptr) {
3195 constraint_group->push_back(constraint);
3197 constraint_groups[key] = {constraint};
3201 auto* constraints_enforced =
gtl::FindOrNull(constraints, enforcement);
3202 if (constraints_enforced !=
nullptr) {
3203 constraints[enforcement].push_back(constraint);
3205 constraints[enforcement] = {constraint};
3209 const std::string prefix_constraint =
" • ";
3210 std::string s =
"Using RoutingCPSatWrapper.\n";
3213 for (
int i = 0; i < objective_coefficients_.size(); ++i) {
3214 double coeff = objective_coefficients_[i];
3216 s += absl::StrFormat(
" | %f * %s\n", coeff, model_.variables(i).name());
3220 s += absl::StrFormat(
"\nVariables %d (%d Binary - %d Non Binary)\n",
3221 num_variables, num_binary_variables,
3222 num_variables - num_binary_variables);
3224 response_,
"",
" | ");
3225 s += absl::StrFormat(
"\n\nConstraints (%d)\n", num_constraints);
3228 s +=
"\n- Not enforced\n";
3229 bool at_least_one_not_enforced =
false;
3230 for (
const auto& pair : constraint_groups) {
3231 if (!std::count(constraints_apart.begin(), constraints_apart.end(),
3233 for (
const auto& constraint : pair.second) {
3236 at_least_one_not_enforced =
true;
3240 if (!at_least_one_not_enforced) {
3241 s += prefix_constraint +
"None\n";
3245 s +=
"\n- Single enforcement\n";
3246 bool at_least_one_single_enforced =
false;
3247 for (
const auto& pair : variable_instances) {
3248 const std::string lemma = pair.first;
3249 bool found_one_constraint =
false;
3250 std::string prefix =
"";
3251 for (
int instance : pair.second) {
3252 const std::string enforcement =
3253 absl::StrFormat(
"%s(%d)", lemma, instance);
3254 auto* constraints_enforced =
gtl::FindOrNull(constraints, enforcement);
3255 std::string prefix_instance =
"";
3256 if (constraints_enforced !=
nullptr) {
3257 at_least_one_single_enforced =
true;
3258 if (!found_one_constraint) {
3259 found_one_constraint =
true;
3260 s += prefix_constraint +
"if " + lemma +
" | ";
3262 std::string(prefix_constraint.size() + 1 + lemma.size(),
' ') +
3267 s += absl::StrFormat(
"%d: | ", instance);
3268 prefix_instance = prefix +
" | ";
3270 for (
const auto& constraint : *constraints_enforced) {
3272 s += prefix_instance;
3280 if (!at_least_one_single_enforced) {
3281 s += prefix_constraint +
"None\n";
3285 s +=
"\n- Multiple enforcement\n";
3286 auto* constraints_multiple_enforced =
3288 if (constraints_multiple_enforced !=
nullptr) {
3289 for (
const auto& constraint : *constraints_multiple_enforced) {
3294 s += prefix_constraint +
"None\n";
3298 s +=
"\n- Set apart\n";
3299 bool at_least_one_apart =
false;
3300 for (
const auto& pair : constraint_groups) {
3301 if (std::count(constraints_apart.begin(), constraints_apart.end(),
3303 for (
const auto& constraint : pair.second) {
3306 at_least_one_apart =
true;
3310 if (!at_least_one_apart) {
3311 s += prefix_constraint +
"None\n";
CumulBoundsPropagator(const RoutingDimension *dimension)
bool PropagateCumulBounds(const std::function< int64_t(int64_t)> &next_accessor, int64_t cumul_offset, const std::vector< RoutingModel::RouteDimensionTravelInfo > *dimension_travel_info_per_route=nullptr)
const RoutingDimension & dimension() const
DimensionSchedulingStatus Optimize(const std::function< int64_t(int64_t)> &next_accessor, const std::vector< RouteDimensionTravelInfo > &dimension_travel_info_per_route, 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_without_transits, int64_t *transit_cost, bool clear_lp=true, bool optimize_resource_assignment=true)
const RoutingDimension * dimension() const
DimensionSchedulingStatus OptimizeAndPack(const std::function< int64_t(int64_t)> &next_accessor, const std::vector< RouteDimensionTravelInfo > &dimension_travel_info_per_route, RoutingLinearSolverWrapper *solver, std::vector< int64_t > *cumul_values, std::vector< int64_t > *break_values)
DimensionSchedulingStatus OptimizeSingleRouteWithResource(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, const RouteDimensionTravelInfo &dimension_travel_info, const Resource *resource, bool optimize_vehicle_costs, RoutingLinearSolverWrapper *solver, std::vector< int64_t > *cumul_values, std::vector< int64_t > *break_values, int64_t *cost_without_transit, int64_t *transit_cost, bool clear_lp=true)
DimensionCumulOptimizerCore(const RoutingDimension *dimension, bool use_precedence_propagator)
std::vector< DimensionSchedulingStatus > OptimizeSingleRouteWithResources(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, const std::function< int64_t(int64_t, int64_t)> &transit_accessor, const RouteDimensionTravelInfo &dimension_travel_info, const std::vector< Resource > &resources, const std::vector< int > &resource_indices, bool optimize_vehicle_costs, RoutingLinearSolverWrapper *solver, std::vector< std::vector< int64_t > > *cumul_values, std::vector< std::vector< int64_t > > *break_values, std::vector< int64_t > *costs_without_transits, int64_t *transit_cost, bool clear_lp=true)
DimensionSchedulingStatus OptimizeAndPackSingleRoute(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, const RouteDimensionTravelInfo &dimension_travel_info, const Resource *resource, RoutingLinearSolverWrapper *solver, std::vector< int64_t > *cumul_values, std::vector< int64_t > *break_values)
DimensionSchedulingStatus ComputeSingleRouteSolutionCostWithoutFixedTransits(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, const RouteDimensionTravelInfo &dimension_travel_info, RoutingLinearSolverWrapper *solver, absl::Span< const int64_t > solution_cumul_values, absl::Span< const int64_t > solution_break_values, int64_t *cost_without_transits, int64_t *cost_offset=nullptr, bool reuse_previous_model_if_possible=true, bool clear_lp=false, bool clear_solution_constraints=true, absl::Duration *solve_duration=nullptr)
static Domain AllValues()
NOTE(user): T=bool is not yet supported (the [] operator doesn't work).
DimensionSchedulingStatus ComputeCumulCostWithoutFixedTransits(const std::function< int64_t(int64_t)> &next_accessor, int64_t *optimal_cost_without_transits)
const RoutingDimension * dimension() const
DimensionSchedulingStatus ComputeCumuls(const std::function< int64_t(int64_t)> &next_accessor, const std::vector< RoutingModel::RouteDimensionTravelInfo > &dimension_travel_info_per_route, std::vector< int64_t > *optimal_cumuls, std::vector< int64_t > *optimal_breaks, std::vector< std::vector< int > > *optimal_resource_indices_per_group)
DimensionSchedulingStatus ComputePackedCumuls(const std::function< int64_t(int64_t)> &next_accessor, const std::vector< RoutingModel::RouteDimensionTravelInfo > &dimension_travel_info_per_route, std::vector< int64_t > *packed_cumuls, std::vector< int64_t > *packed_breaks)
virtual IntVarIterator * MakeDomainIterator(bool reversible) const =0
virtual bool Contains(int64_t v) const =0
virtual uint64_t Size() const =0
This method returns the number of values in the domain of the variable.
DimensionSchedulingStatus ComputeRouteSolutionCostWithoutFixedTransits(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, const RoutingModel::RouteDimensionTravelInfo &dimension_travel_info, const std::vector< int64_t > &solution_cumul_values, const std::vector< int64_t > &solution_break_values, int64_t *solution_cost, int64_t *cost_offset=nullptr, bool reuse_previous_model_if_possible=false, bool clear_lp=true, absl::Duration *solve_duration=nullptr)
const RoutingDimension * dimension() const
DimensionSchedulingStatus ComputeRouteCumulsAndCostWithoutFixedTransits(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, const RoutingModel::RouteDimensionTravelInfo &dimension_travel_info, std::vector< int64_t > *optimal_cumuls, std::vector< int64_t > *optimal_breaks, int64_t *optimal_cost_without_transits)
LocalDimensionCumulOptimizer(const RoutingDimension *dimension, RoutingSearchParameters::SchedulingSolver solver_type)
LocalDimensionCumulOptimizer.
DimensionSchedulingStatus ComputeRouteCumuls(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, const RoutingModel::RouteDimensionTravelInfo &dimension_travel_info, const RoutingModel::ResourceGroup::Resource *resource, std::vector< int64_t > *optimal_cumuls, std::vector< int64_t > *optimal_breaks)
DimensionSchedulingStatus ComputeRouteCumulCost(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, int64_t *optimal_cost)
DimensionSchedulingStatus ComputeRouteCumulCostWithoutFixedTransits(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, int64_t *optimal_cost_without_transits)
std::vector< DimensionSchedulingStatus > ComputeRouteCumulCostsForResourcesWithoutFixedTransits(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, const std::function< int64_t(int64_t, int64_t)> &transit_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)
DimensionSchedulingStatus ComputePackedRouteCumuls(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, const RoutingModel::RouteDimensionTravelInfo &dimension_travel_info, const RoutingModel::ResourceGroup::Resource *resource, std::vector< int64_t > *packed_cumuls, std::vector< int64_t > *packed_breaks)
static int64_t FastInt64Round(double x)
std::string PrintModel() const override
Prints an understandable view of the model.
int64_t GetObjectiveValue() const override
virtual int64_t GetObjectiveValue() const =0
virtual DimensionSchedulingStatus Solve(absl::Duration duration_limit)=0
virtual int64_t GetVariableUpperBound(int index) const =0
virtual bool SolutionIsInteger() const =0
virtual double GetValue(int index) const =0
virtual void SetObjectiveCoefficient(int index, double coefficient)=0
virtual void ClearObjective()=0
virtual bool SetVariableBounds(int index, int64_t lower_bound, int64_t upper_bound)=0
virtual void SetParameters(const std::string ¶meters)=0
This function is meant to override the parameters of the solver.
virtual void AddObjectiveConstraint()=0
virtual bool ModelIsEmpty() const
Returns if the model is empty or not.
virtual int64_t GetVariableLowerBound(int index) const =0
virtual bool IsCPSATSolver()=0
ArcIndex AddArcWithCapacityAndUnitCost(NodeIndex tail, NodeIndex head, FlowQuantity capacity, CostValue unit_cost)
void SetNodeSupply(NodeIndex node, FlowQuantity supply)
CostValue OptimalCost() const
FlowQuantity Flow(ArcIndex arc) const
const std::string name
A name for logging purposes.
absl::Span< const double > coefficients
const Collection::value_type::second_type * FindOrNull(const Collection &collection, const typename Collection::value_type::first_type &key)
double FindBestScalingAndComputeErrors(const std::vector< double > &coefficients, absl::Span< const double > lower_bounds, absl::Span< const double > upper_bounds, int64_t max_absolute_activity, double wanted_absolute_activity_precision, double *relative_coeff_error, double *scaled_sum_error)
In SWIG mode, we don't want anything besides these top-level includes.
int64_t CapAdd(int64_t x, int64_t y)
std::string ConstraintToString(const sat::ConstraintProto &constraint, const sat::CpModelProto &model_, bool show_enforcement=true)
std::string DomainToString(const ::google::protobuf::RepeatedField< int64_t > *domain)
DimensionSchedulingStatus
@ INFEASIBLE
A solution could not be found.
@ OPTIMAL
An optimal solution was found respecting all constraints.
int64_t CapSub(int64_t x, int64_t y)
PiecewiseEvaluationStatus ComputePiecewiseLinearFormulationValue(const RoutingModel::RouteDimensionTravelInfo::TransitionInfo::PiecewiseLinearFormulation &pwl, int64_t x, int64_t *value, double delta)
PiecewiseEvaluationStatus
@ SMALLER_THAN_LOWER_BOUND
@ LARGER_THAN_UPPER_BOUND
std::vector< bool > SlopeAndYInterceptToConvexityRegions(const std::vector< SlopeAndYIntercept > &slope_and_y_intercept)
int64_t ComputeConvexPiecewiseLinearFormulationValue(const RoutingModel::RouteDimensionTravelInfo::TransitionInfo::PiecewiseLinearFormulation &pwl, int64_t x, double delta)
bool ComputeVehicleToResourceClassAssignmentCosts(int v, const RoutingModel::ResourceGroup &resource_group, const util_intops::StrongVector< RoutingModel::ResourceClassIndex, absl::flat_hash_set< int > > &ignored_resources_per_class, const std::function< int64_t(int64_t)> &next_accessor, const std::function< int64_t(int64_t, int64_t)> &transit_accessor, bool optimize_vehicle_costs, LocalDimensionCumulOptimizer *lp_optimizer, LocalDimensionCumulOptimizer *mp_optimizer, std::vector< int64_t > *assignment_costs, std::vector< std::vector< int64_t > > *cumul_values, std::vector< std::vector< int64_t > > *break_values)
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::string VariablesToString(absl::flat_hash_map< std::string, std::pair< sat::IntegerVariableProto, int > > &variables, absl::flat_hash_map< std::string, std::vector< int > > &variable_instances, absl::flat_hash_map< std::string, absl::flat_hash_set< std::string > > &variable_childs, const sat::CpSolverResponse &response_, absl::string_view variable, std::string prefix="")
std::string Int64ToStr(int64_t number)
std::vector< SlopeAndYIntercept > PiecewiseLinearFormulationToSlopeAndYIntercept(const RoutingModel::RouteDimensionTravelInfo::TransitionInfo::PiecewiseLinearFormulation &pwl_function, int index_start, int index_end)
int64_t ComputeBestVehicleToResourceAssignment(const std::vector< int > &vehicles, const util_intops::StrongVector< RoutingModel::ResourceClassIndex, std::vector< int > > &resource_indices_per_class, const util_intops::StrongVector< RoutingModel::ResourceClassIndex, absl::flat_hash_set< int > > &ignored_resources_per_class, std::function< const std::vector< int64_t > *(int)> vehicle_to_resource_class_assignment_costs, std::vector< int > *resource_indices)
std::string VariableToString(std::pair< sat::IntegerVariableProto, int > &variable_pair, const sat::CpSolverResponse &response_)
std::vector< int64_t > assignment_costs
std::vector< std::vector< int64_t > > cumul_values
std::vector< std::vector< int64_t > > break_values
#define SET_DEBUG_VARIABLE_NAME(solver, var, name)
std::vector< double > lower_bounds
std::vector< double > upper_bounds
static const int64_t kint64max
static const int64_t kint64min