29#include "absl/algorithm/container.h"
30#include "absl/container/flat_hash_map.h"
31#include "absl/container/flat_hash_set.h"
32#include "absl/log/check.h"
33#include "absl/strings/str_format.h"
34#include "absl/strings/string_view.h"
35#include "absl/time/time.h"
36#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"
62glop::GlopParameters GetGlopParametersForLocalLP() {
63 glop::GlopParameters parameters;
64 parameters.set_use_dual_simplex(
true);
65 parameters.set_use_preprocessing(
false);
69glop::GlopParameters GetGlopParametersForGlobalLP() {
70 glop::GlopParameters parameters;
71 parameters.set_use_dual_simplex(
true);
75bool GetCumulBoundsWithOffset(
const RoutingDimension& dimension,
76 int64_t node_index, int64_t cumul_offset,
77 int64_t* lower_bound, int64_t* upper_bound) {
78 DCHECK(lower_bound !=
nullptr);
79 DCHECK(upper_bound !=
nullptr);
81 const IntVar& cumul_var = *dimension.CumulVar(node_index);
82 *upper_bound = cumul_var.
Max();
83 if (*upper_bound < cumul_offset) {
87 const int64_t first_after_offset =
88 std::max(dimension.GetFirstPossibleGreaterOrEqualValueForNode(
89 node_index, cumul_offset),
91 DCHECK_LT(first_after_offset, std::numeric_limits<int64_t>::max());
92 *lower_bound =
CapSub(first_after_offset, cumul_offset);
93 DCHECK_GE(*lower_bound, 0);
95 if (*upper_bound == std::numeric_limits<int64_t>::max()) {
98 *upper_bound =
CapSub(*upper_bound, cumul_offset);
99 DCHECK_GE(*upper_bound, *lower_bound);
103int64_t GetFirstPossibleValueForCumulWithOffset(
104 const RoutingDimension& dimension, int64_t node_index,
105 int64_t lower_bound_without_offset, int64_t cumul_offset) {
107 dimension.GetFirstPossibleGreaterOrEqualValueForNode(
108 node_index,
CapAdd(lower_bound_without_offset, cumul_offset)),
112int64_t GetLastPossibleValueForCumulWithOffset(
113 const RoutingDimension& dimension, int64_t node_index,
114 int64_t upper_bound_without_offset, int64_t cumul_offset) {
116 dimension.GetLastPossibleLessOrEqualValueForNode(
117 node_index,
CapAdd(upper_bound_without_offset, cumul_offset)),
121using PDPosition = RoutingModel::PickupDeliveryPosition;
127void StoreVisitedPickupDeliveryPairsOnRoute(
128 const RoutingDimension& dimension,
int vehicle,
129 const std::function<int64_t(int64_t)>& next_accessor,
130 std::vector<int>* visited_pairs,
131 std::vector<std::pair<int64_t, int64_t>>*
132 visited_pickup_delivery_indices_for_pair) {
134 DCHECK_EQ(visited_pickup_delivery_indices_for_pair->size(),
135 dimension.model()->GetPickupAndDeliveryPairs().size());
136 DCHECK(std::all_of(visited_pickup_delivery_indices_for_pair->begin(),
137 visited_pickup_delivery_indices_for_pair->end(),
138 [](std::pair<int64_t, int64_t> p) {
139 return p.first == -1 && p.second == -1;
141 visited_pairs->clear();
142 if (!dimension.HasPickupToDeliveryLimits()) {
145 const RoutingModel& model = *dimension.model();
147 int64_t node_index = model.Start(vehicle);
148 while (!model.IsEnd(node_index)) {
149 if (model.IsPickup(node_index)) {
151 const std::optional<PDPosition> pickup_position =
152 model.GetPickupPosition(node_index);
153 DCHECK(pickup_position.has_value());
154 const int pair_index = pickup_position->pd_pair_index;
155 (*visited_pickup_delivery_indices_for_pair)[pair_index].first =
157 visited_pairs->push_back(pair_index);
158 }
else if (model.IsDelivery(node_index)) {
161 const std::optional<PDPosition> delivery_position =
162 model.GetDeliveryPosition(node_index);
163 DCHECK(delivery_position.has_value());
164 const int pair_index = delivery_position->pd_pair_index;
165 std::pair<int64_t, int64_t>& pickup_delivery_index =
166 (*visited_pickup_delivery_indices_for_pair)[pair_index];
167 if (pickup_delivery_index.first < 0) {
170 node_index = next_accessor(node_index);
173 pickup_delivery_index.second = node_index;
175 node_index = next_accessor(node_index);
185 RoutingSearchParameters::SchedulingSolver solver_type)
189 const int vehicles =
dimension->model()->vehicles();
190 solver_.resize(vehicles);
191 switch (solver_type) {
192 case RoutingSearchParameters::SCHEDULING_GLOP: {
193 const glop::GlopParameters parameters = GetGlopParametersForLocalLP();
194 for (
int vehicle = 0; vehicle < vehicles; ++vehicle) {
198 std::make_unique<RoutingGlopWrapper>(
false, parameters);
202 case RoutingSearchParameters::SCHEDULING_CP_SAT: {
203 for (
int vehicle = 0; vehicle < vehicles; ++vehicle) {
204 solver_[vehicle] = std::make_unique<RoutingCPSatWrapper>();
209 LOG(DFATAL) <<
"Unrecognized solver type: " << solver_type;
214 int vehicle,
double solve_duration_ratio,
215 const std::function<int64_t(int64_t)>& next_accessor,
216 int64_t* optimal_cost) {
217 DCHECK_GT(solve_duration_ratio, 0);
218 DCHECK_LE(solve_duration_ratio, 1);
219 int64_t transit_cost = 0;
221 optimizer_core_.OptimizeSingleRouteWithResource(
222 vehicle, solve_duration_ratio, next_accessor,
225 optimal_cost !=
nullptr,
226 solver_[vehicle].get(),
nullptr,
227 nullptr, optimal_cost, &transit_cost);
229 optimal_cost !=
nullptr) {
230 DCHECK_GE(*optimal_cost, 0);
231 *optimal_cost =
CapAdd(*optimal_cost, transit_cost);
238 int vehicle,
double solve_duration_ratio,
239 const std::function<int64_t(int64_t)>& next_accessor,
240 const RoutingModel::ResourceGroup::Resource* resource,
241 int64_t* optimal_cost_without_transits) {
242 DCHECK_GT(solve_duration_ratio, 0);
243 DCHECK_LE(solve_duration_ratio, 1);
244 return optimizer_core_.OptimizeSingleRouteWithResource(
245 vehicle, solve_duration_ratio, next_accessor,
247 optimal_cost_without_transits !=
nullptr,
248 solver_[vehicle].get(),
nullptr,
249 nullptr, optimal_cost_without_transits,
nullptr);
254 int vehicle,
double solve_duration_ratio,
255 const std::function<int64_t(int64_t)>& next_accessor,
256 const std::function<int64_t(int64_t, int64_t)>& transit_accessor,
257 absl::Span<const RoutingModel::ResourceGroup::Resource> resources,
258 absl::Span<const int> resource_indices,
bool optimize_vehicle_costs,
259 std::vector<int64_t>* optimal_costs_without_transits,
260 std::vector<std::vector<int64_t>>* optimal_cumuls,
261 std::vector<std::vector<int64_t>>* optimal_breaks) {
262 DCHECK_GT(solve_duration_ratio, 0);
263 DCHECK_LE(solve_duration_ratio, 1);
264 return optimizer_core_.OptimizeSingleRouteWithResources(
265 vehicle, solve_duration_ratio, next_accessor, transit_accessor,
nullptr,
266 resources, resource_indices, optimize_vehicle_costs,
267 solver_[vehicle].get(), optimal_cumuls, optimal_breaks,
268 optimal_costs_without_transits,
nullptr);
272 int vehicle,
double solve_duration_ratio,
273 const std::function<int64_t(int64_t)>& next_accessor,
274 const RoutingModel::RouteDimensionTravelInfo* dimension_travel_info,
275 const RoutingModel::ResourceGroup::Resource* resource,
276 std::vector<int64_t>* optimal_cumuls,
277 std::vector<int64_t>* optimal_breaks) {
278 DCHECK_GT(solve_duration_ratio, 0);
279 DCHECK_LE(solve_duration_ratio, 1);
280 return optimizer_core_.OptimizeSingleRouteWithResource(
281 vehicle, solve_duration_ratio, next_accessor, dimension_travel_info,
282 resource,
true, solver_[vehicle].get(),
283 optimal_cumuls, optimal_breaks,
nullptr,
289 int vehicle,
double solve_duration_ratio,
290 const std::function<int64_t(int64_t)>& next_accessor,
291 const RoutingModel::RouteDimensionTravelInfo* dimension_travel_info,
292 std::vector<int64_t>* optimal_cumuls, std::vector<int64_t>* optimal_breaks,
293 int64_t* optimal_cost_without_transits) {
294 DCHECK_GT(solve_duration_ratio, 0);
295 DCHECK_LE(solve_duration_ratio, 1);
296 return optimizer_core_.OptimizeSingleRouteWithResource(
297 vehicle, solve_duration_ratio, next_accessor, dimension_travel_info,
298 nullptr,
true, solver_[vehicle].get(),
299 optimal_cumuls, optimal_breaks, optimal_cost_without_transits,
nullptr);
304 int vehicle,
double solve_duration_ratio,
305 const std::function<int64_t(int64_t)>& next_accessor,
306 const RoutingModel::RouteDimensionTravelInfo* dimension_travel_info,
307 absl::Span<const int64_t> solution_cumul_values,
308 absl::Span<const int64_t> solution_break_values, int64_t* solution_cost,
309 int64_t* cost_offset,
bool reuse_previous_model_if_possible,
bool clear_lp,
310 absl::Duration* solve_duration) {
311 DCHECK_GT(solve_duration_ratio, 0);
312 DCHECK_LE(solve_duration_ratio, 1);
314 return optimizer_core_.ComputeSingleRouteSolutionCostWithoutFixedTransits(
315 vehicle, solve_duration_ratio, next_accessor, dimension_travel_info,
316 solver, solution_cumul_values, solution_break_values, solution_cost,
317 cost_offset, reuse_previous_model_if_possible, clear_lp,
318 true, solve_duration);
323 int vehicle,
double solve_duration_ratio,
324 const std::function<int64_t(int64_t)>& next_accessor,
325 const RoutingModel::RouteDimensionTravelInfo* dimension_travel_info,
326 const RoutingModel::ResourceGroup::Resource* resource,
327 std::vector<int64_t>* packed_cumuls, std::vector<int64_t>* packed_breaks) {
328 DCHECK_GT(solve_duration_ratio, 0);
329 DCHECK_LE(solve_duration_ratio, 1);
330 return optimizer_core_.OptimizeAndPackSingleRoute(
331 vehicle, solve_duration_ratio, next_accessor, dimension_travel_info,
332 resource, solver_[vehicle].get(), packed_cumuls, packed_breaks);
335const int CumulBoundsPropagator::kNoParent = -2;
336const int CumulBoundsPropagator::kParentToBePropagated = -1;
340 outgoing_arcs_.resize(num_nodes_);
341 node_in_queue_.resize(num_nodes_,
false);
342 tree_parent_node_of_.resize(num_nodes_, kNoParent);
343 propagated_bounds_.resize(num_nodes_);
344 visited_pickup_delivery_indices_for_pair_.resize(
345 dimension->model()->GetPickupAndDeliveryPairs().size(), {-1, -1});
348void CumulBoundsPropagator::AddArcs(
int first_index,
int second_index,
351 outgoing_arcs_[PositiveNode(first_index)].push_back(
352 {PositiveNode(second_index), offset});
353 AddNodeToQueue(PositiveNode(first_index));
355 outgoing_arcs_[NegativeNode(second_index)].push_back(
356 {NegativeNode(first_index), offset});
357 AddNodeToQueue(NegativeNode(second_index));
360bool CumulBoundsPropagator::InitializeArcsAndBounds(
361 const std::function<int64_t(int64_t)>& next_accessor, int64_t cumul_offset,
362 const std::vector<RoutingModel::RouteDimensionTravelInfo>*
363 dimension_travel_info_per_route) {
364 propagated_bounds_.assign(num_nodes_, std::numeric_limits<int64_t>::min());
366 for (std::vector<ArcInfo>& arcs : outgoing_arcs_) {
370 RoutingModel*
const model = dimension_.model();
371 std::vector<int64_t>& lower_bounds = propagated_bounds_;
373 for (
int vehicle = 0; vehicle < model->vehicles(); vehicle++) {
374 const std::function<int64_t(int64_t, int64_t)>& transit_accessor =
375 dimension_.transit_evaluator(vehicle);
377 int node = model->Start(vehicle);
378 int index_on_route = 0;
380 int64_t cumul_lb, cumul_ub;
381 if (!GetCumulBoundsWithOffset(dimension_, node, cumul_offset, &cumul_lb,
385 lower_bounds[PositiveNode(node)] = cumul_lb;
386 if (cumul_ub < std::numeric_limits<int64_t>::max()) {
387 lower_bounds[NegativeNode(node)] = -cumul_ub;
390 if (model->IsEnd(node)) {
394 const int next = next_accessor(node);
395 int64_t transit = transit_accessor(node, next);
396 if (dimension_travel_info_per_route !=
nullptr &&
397 !dimension_travel_info_per_route->empty()) {
398 const RoutingModel::RouteDimensionTravelInfo::TransitionInfo&
399 transition_info = (*dimension_travel_info_per_route)[vehicle]
400 .transition_info[index_on_route];
401 transit = transition_info.compressed_travel_value_lower_bound +
402 transition_info.pre_travel_transit_value +
403 transition_info.post_travel_transit_value;
406 const IntVar& slack_var = *dimension_.SlackVar(node);
409 AddArcs(node, next,
CapAdd(transit, slack_var.Min()));
410 if (slack_var.Max() < std::numeric_limits<int64_t>::max()) {
412 AddArcs(next, node,
CapSub(-slack_var.Max(), transit));
419 const int64_t span_ub = dimension_.GetSpanUpperBoundForVehicle(vehicle);
420 if (span_ub < std::numeric_limits<int64_t>::max()) {
421 AddArcs(model->End(vehicle), model->Start(vehicle), -span_ub);
425 std::vector<int> visited_pairs;
426 StoreVisitedPickupDeliveryPairsOnRoute(
427 dimension_, vehicle, next_accessor, &visited_pairs,
428 &visited_pickup_delivery_indices_for_pair_);
429 for (
int pair_index : visited_pairs) {
430 const int64_t pickup_index =
431 visited_pickup_delivery_indices_for_pair_[pair_index].first;
432 const int64_t delivery_index =
433 visited_pickup_delivery_indices_for_pair_[pair_index].second;
434 visited_pickup_delivery_indices_for_pair_[pair_index] = {-1, -1};
436 DCHECK_GE(pickup_index, 0);
437 if (delivery_index < 0) {
442 const int64_t limit = dimension_.GetPickupToDeliveryLimitForPair(
443 pair_index, model->GetPickupPosition(pickup_index)->alternative_index,
444 model->GetDeliveryPosition(delivery_index)->alternative_index);
445 if (limit < std::numeric_limits<int64_t>::max()) {
447 AddArcs(delivery_index, pickup_index, -limit);
452 for (
const auto [first_node, second_node, offset, performed_constraint] :
453 dimension_.GetNodePrecedences()) {
454 const bool first_node_unperformed =
455 lower_bounds[PositiveNode(first_node)] ==
456 std::numeric_limits<int64_t>::min();
457 const bool second_node_unperformed =
458 lower_bounds[PositiveNode(second_node)] ==
459 std::numeric_limits<int64_t>::min();
460 switch (RoutingDimension::GetPrecedenceStatus(first_node_unperformed,
461 second_node_unperformed,
462 performed_constraint)) {
463 case RoutingDimension::PrecedenceStatus::kActive:
465 case RoutingDimension::PrecedenceStatus::kInactive:
467 case RoutingDimension::PrecedenceStatus::kInvalid:
470 AddArcs(first_node, second_node, offset);
476bool CumulBoundsPropagator::UpdateCurrentLowerBoundOfNode(
int node,
479 const int cumul_var_index = node / 2;
481 if (node == PositiveNode(cumul_var_index)) {
483 propagated_bounds_[node] = GetFirstPossibleValueForCumulWithOffset(
484 dimension_, cumul_var_index, new_lb, offset);
487 const int64_t new_ub =
CapSub(0, new_lb);
488 propagated_bounds_[node] =
489 CapSub(0, GetLastPossibleValueForCumulWithOffset(
490 dimension_, cumul_var_index, new_ub, offset));
494 const int64_t cumul_lower_bound =
495 propagated_bounds_[PositiveNode(cumul_var_index)];
497 const int64_t negated_cumul_upper_bound =
498 propagated_bounds_[NegativeNode(cumul_var_index)];
500 return CapAdd(negated_cumul_upper_bound, cumul_lower_bound) <= 0;
503bool CumulBoundsPropagator::DisassembleSubtree(
int source,
int target) {
504 tmp_dfs_stack_.clear();
505 tmp_dfs_stack_.push_back(source);
506 while (!tmp_dfs_stack_.empty()) {
507 const int tail = tmp_dfs_stack_.back();
508 tmp_dfs_stack_.pop_back();
509 for (
const ArcInfo& arc : outgoing_arcs_[tail]) {
510 const int child_node = arc.head;
511 if (tree_parent_node_of_[child_node] != tail)
continue;
512 if (child_node == target)
return false;
513 tree_parent_node_of_[child_node] = kParentToBePropagated;
514 tmp_dfs_stack_.push_back(child_node);
521 const std::function<int64_t(int64_t)>& next_accessor, int64_t cumul_offset,
522 const std::vector<RoutingModel::RouteDimensionTravelInfo>*
523 dimension_travel_info_per_route) {
524 tree_parent_node_of_.assign(num_nodes_, kNoParent);
525 DCHECK(std::none_of(node_in_queue_.begin(), node_in_queue_.end(),
526 [](
bool b) { return b; }));
527 DCHECK(bf_queue_.empty());
529 if (!InitializeArcsAndBounds(next_accessor, cumul_offset,
530 dimension_travel_info_per_route)) {
531 return CleanupAndReturnFalse();
534 std::vector<int64_t>& current_lb = propagated_bounds_;
537 while (!bf_queue_.empty()) {
538 const int node = bf_queue_.front();
539 bf_queue_.pop_front();
540 node_in_queue_[node] =
false;
542 if (tree_parent_node_of_[node] == kParentToBePropagated) {
548 const int64_t lower_bound = current_lb[node];
549 for (
const ArcInfo& arc : outgoing_arcs_[node]) {
552 const int64_t induced_lb =
553 (lower_bound == std::numeric_limits<int64_t>::min())
554 ? std::numeric_limits<int64_t>::min()
555 :
CapAdd(lower_bound, arc.offset);
557 const int head_node = arc.head;
558 if (induced_lb <= current_lb[head_node]) {
563 if (!UpdateCurrentLowerBoundOfNode(head_node, induced_lb, cumul_offset) ||
564 !DisassembleSubtree(head_node, node)) {
567 return CleanupAndReturnFalse();
570 tree_parent_node_of_[head_node] = node;
571 AddNodeToQueue(head_node);
578 const RoutingDimension*
dimension,
bool use_precedence_propagator)
580 visited_pickup_delivery_indices_for_pair_(
581 dimension->model()->GetPickupAndDeliveryPairs().size(), {-1, -1}) {
582 if (use_precedence_propagator) {
583 propagator_ = std::make_unique<CumulBoundsPropagator>(dimension);
585 const RoutingModel& model = *dimension_->model();
586 if (dimension_->HasBreakConstraints()) {
590 const int num_vehicles = model.vehicles();
591 vehicle_to_all_break_variables_offset_.reserve(num_vehicles);
592 int num_break_vars = 0;
593 for (
int vehicle = 0; vehicle < num_vehicles; ++vehicle) {
594 vehicle_to_all_break_variables_offset_.push_back(num_break_vars);
595 const auto& intervals = dimension_->GetBreakIntervalsOfVehicle(vehicle);
596 num_break_vars += 2 * intervals.size();
598 all_break_variables_.resize(num_break_vars, -1);
600 if (!model.GetDimensionResourceGroupIndices(dimension_).empty()) {
601 resource_class_to_vehicle_assignment_variables_per_group_.resize(
602 model.GetResourceGroups().size());
603 resource_class_ignored_resources_per_group_.resize(
604 model.GetResourceGroups().size());
610 int vehicle,
double solve_duration_ratio,
611 const std::function<int64_t(int64_t)>& next_accessor,
612 const RouteDimensionTravelInfo* dimension_travel_info,
614 absl::Span<const int64_t> solution_cumul_values,
615 absl::Span<const int64_t> solution_break_values,
616 int64_t* cost_without_transits, int64_t* cost_offset,
617 bool reuse_previous_model_if_possible,
bool clear_lp,
618 bool clear_solution_constraints, absl::Duration*
const solve_duration) {
619 absl::Duration solve_duration_value;
620 int64_t cost_offset_value;
621 if (!reuse_previous_model_if_possible || solver->
ModelIsEmpty()) {
622 InitOptimizer(solver);
625 DCHECK_EQ(propagator_.get(),
nullptr);
627 RoutingModel*
const model = dimension_->model();
628 const bool optimize_vehicle_costs =
629 !model->IsEnd(next_accessor(model->Start(vehicle))) ||
630 model->IsVehicleUsedWhenEmpty(vehicle);
631 if (!SetRouteCumulConstraints(
632 vehicle, next_accessor, dimension_->transit_evaluator(vehicle),
633 dimension_travel_info,
634 dimension_->GetLocalOptimizerOffsetForVehicle(vehicle),
635 optimize_vehicle_costs, solver,
nullptr, &cost_offset_value)) {
638 if (model->CheckLimit()) {
641 solve_duration_value = model->RemainingTime() * solve_duration_ratio;
642 if (solve_duration !=
nullptr) *solve_duration = solve_duration_value;
643 if (cost_offset !=
nullptr) *cost_offset = cost_offset_value;
645 CHECK(cost_offset !=
nullptr)
646 <<
"Cannot reuse model without the cost_offset";
647 cost_offset_value = *cost_offset;
648 CHECK(solve_duration !=
nullptr)
649 <<
"Cannot reuse model without the solve_duration";
650 solve_duration_value = *solve_duration * solve_duration_ratio;
654 DCHECK_EQ(solution_cumul_values.size(),
655 current_route_cumul_variables_.size());
656 for (
int i = 0; i < current_route_cumul_variables_.size(); ++i) {
657 if (solution_cumul_values[i] < current_route_min_cumuls_[i] ||
658 solution_cumul_values[i] > current_route_max_cumuls_[i]) {
662 solution_cumul_values[i],
663 solution_cumul_values[i]);
667 DCHECK_EQ(solution_break_values.size(),
668 current_route_break_variables_.size());
669 std::vector<int64_t> current_route_min_breaks(
670 current_route_break_variables_.size());
671 std::vector<int64_t> current_route_max_breaks(
672 current_route_break_variables_.size());
673 for (
int i = 0; i < current_route_break_variables_.size(); ++i) {
674 current_route_min_breaks[i] =
676 current_route_max_breaks[i] =
679 solution_break_values[i],
680 solution_break_values[i]);
689 if (cost_without_transits !=
nullptr) {
690 *cost_without_transits =
696 }
else if (clear_solution_constraints) {
697 for (
int i = 0; i < current_route_cumul_variables_.size(); ++i) {
699 current_route_min_cumuls_[i],
700 current_route_max_cumuls_[i]);
702 for (
int i = 0; i < current_route_break_variables_.size(); ++i) {
704 current_route_min_breaks[i],
705 current_route_max_breaks[i]);
713void ClearIfNonNull(std::vector<T>* v) {
722 int vehicle,
double solve_duration_ratio,
723 const std::function<int64_t(int64_t)>& next_accessor,
724 const RouteDimensionTravelInfo* dimension_travel_info,
725 const RoutingModel::ResourceGroup::Resource* resource,
727 std::vector<int64_t>* cumul_values, std::vector<int64_t>* break_values,
728 int64_t* cost_without_transits, int64_t* transit_cost,
bool clear_lp) {
729 if (cost_without_transits !=
nullptr) *cost_without_transits = -1;
730 ClearIfNonNull(cumul_values);
731 ClearIfNonNull(break_values);
733 const std::vector<Resource> resources =
734 resource ==
nullptr ? std::vector<Resource>()
735 : std::vector<Resource>({*resource});
736 const std::vector<int> resource_indices =
737 resource ==
nullptr ? std::vector<int>() : std::vector<int>({0});
738 std::vector<int64_t> costs_without_transits;
739 std::vector<std::vector<int64_t>> cumul_values_vec;
740 std::vector<std::vector<int64_t>> break_values_vec;
741 const std::vector<DimensionSchedulingStatus> statuses =
743 vehicle, solve_duration_ratio, next_accessor,
744 dimension_->transit_evaluator(vehicle), dimension_travel_info,
745 resources, resource_indices, optimize_vehicle_costs, solver,
746 cumul_values !=
nullptr ? &cumul_values_vec :
nullptr,
747 break_values !=
nullptr ? &break_values_vec :
nullptr,
748 cost_without_transits !=
nullptr ? &costs_without_transits :
nullptr,
749 transit_cost, clear_lp);
751 if (
dimension()->model()->CheckLimit()) {
754 DCHECK_EQ(statuses.size(), 1);
759 if (cost_without_transits !=
nullptr) {
760 *cost_without_transits = costs_without_transits[0];
762 if (cumul_values !=
nullptr) *cumul_values = std::move(cumul_values_vec[0]);
763 if (break_values !=
nullptr) *break_values = std::move(break_values_vec[0]);
770using ResourceGroup = RoutingModel::ResourceGroup;
772bool GetDomainOffsetBounds(
const Domain& domain, int64_t offset,
774 const int64_t lower_bound =
775 std::max<int64_t>(
CapSub(domain.
Min(), offset), 0);
776 const int64_t upper_bound =
777 domain.
Max() == std::numeric_limits<int64_t>::max()
778 ? std::numeric_limits<int64_t>::max()
779 :
CapSub(domain.Max(), offset);
780 if (lower_bound > upper_bound)
return false;
786bool GetIntervalIntersectionWithOffsetDomain(
const ClosedInterval& interval,
789 ClosedInterval* intersection) {
791 *intersection = interval;
795 if (!GetDomainOffsetBounds(domain, offset, &offset_domain)) {
798 const int64_t intersection_lb = std::max(interval.start, offset_domain.start);
799 const int64_t intersection_ub = std::min(interval.end, offset_domain.end);
800 if (intersection_lb > intersection_ub)
return false;
809 solver.GetVariableUpperBound(index));
812bool TightenStartEndVariableBoundsWithResource(
813 const RoutingDimension& dimension,
const ResourceGroup::Resource& resource,
817 const ResourceGroup::Attributes& attributes =
818 resource.GetDimensionAttributes(&dimension);
821 return GetIntervalIntersectionWithOffsetDomain(start_bounds,
822 attributes.start_domain(),
823 offset, &new_start_bounds) &&
824 solver->SetVariableBounds(start_index, new_start_bounds.start,
825 new_start_bounds.end) &&
826 GetIntervalIntersectionWithOffsetDomain(
827 end_bounds, attributes.end_domain(), offset, &new_end_bounds) &&
828 solver->SetVariableBounds(end_index, new_end_bounds.start,
832bool TightenStartEndVariableBoundsWithAssignedResources(
833 const RoutingDimension& dimension,
int v,
int start_index,
int end_index,
835 const RoutingModel* model = dimension.model();
836 for (
int rg_index : model->GetDimensionResourceGroupIndices(&dimension)) {
837 const IntVar* res_var = model->ResourceVars(rg_index)[v];
838 DCHECK(res_var->Bound());
839 const int res_index = res_var->Value();
842 DCHECK(!model->GetResourceGroup(rg_index)->VehicleRequiresAResource(v));
845 const ResourceGroup::Resource& resource =
846 model->GetResourceGroup(rg_index)->GetResource(res_index);
847 if (!TightenStartEndVariableBoundsWithResource(
848 dimension, resource, GetVariableBounds(start_index, *solver),
849 start_index, GetVariableBounds(end_index, *solver), end_index,
859std::vector<DimensionSchedulingStatus>
861 int vehicle,
double solve_duration_ratio,
862 const std::function<int64_t(int64_t)>& next_accessor,
863 const std::function<int64_t(int64_t, int64_t)>& transit_accessor,
864 const RouteDimensionTravelInfo* dimension_travel_info,
865 absl::Span<const RoutingModel::ResourceGroup::Resource> resources,
866 absl::Span<const int> resource_indices,
bool optimize_vehicle_costs,
868 std::vector<std::vector<int64_t>>* cumul_values,
869 std::vector<std::vector<int64_t>>* break_values,
870 std::vector<int64_t>* costs_without_transits, int64_t* transit_cost,
872 ClearIfNonNull(costs_without_transits);
873 const bool optimize_with_resources = !resource_indices.empty();
874 if (!optimize_with_resources && !resources.empty())
return {};
876 InitOptimizer(solver);
879 DCHECK_EQ(propagator_.get(),
nullptr);
881 RoutingModel*
const model =
dimension()->model();
882 if (model->IsEnd(next_accessor(model->Start(vehicle))) &&
883 !model->IsVehicleUsedWhenEmpty(vehicle)) {
885 DCHECK(!optimize_with_resources);
886 optimize_vehicle_costs =
false;
889 const int64_t cumul_offset =
890 dimension_->GetLocalOptimizerOffsetForVehicle(vehicle);
891 int64_t cost_offset = 0;
892 if (!SetRouteCumulConstraints(vehicle, next_accessor, transit_accessor,
893 dimension_travel_info, cumul_offset,
894 optimize_vehicle_costs, solver, transit_cost,
896 if (costs_without_transits !=
nullptr) {
897 costs_without_transits->assign(1, -1);
901 DCHECK_GE(current_route_cumul_variables_.size(), 2);
907 const int num_solves = resource_indices.empty() ? 1 : resource_indices.size();
908 if (costs_without_transits !=
nullptr) {
909 costs_without_transits->assign(num_solves, -1);
911 if (cumul_values !=
nullptr) cumul_values->assign(num_solves, {});
912 if (break_values !=
nullptr) break_values->assign(num_solves, {});
914 const int start_cumul = current_route_cumul_variables_[0];
915 const ClosedInterval start_bounds = GetVariableBounds(start_cumul, *solver);
916 const int end_cumul = current_route_cumul_variables_.back();
917 const ClosedInterval end_bounds = GetVariableBounds(end_cumul, *solver);
918 std::vector<DimensionSchedulingStatus> statuses;
919 statuses.reserve(num_solves);
920 for (
int i = 0; i < num_solves; i++) {
921 if (model->CheckLimit()) {
923 ClearIfNonNull(costs_without_transits);
924 ClearIfNonNull(cumul_values);
925 ClearIfNonNull(break_values);
929 if (optimize_with_resources &&
930 !TightenStartEndVariableBoundsWithResource(
931 *dimension_, resources[resource_indices[i]], start_bounds,
932 start_cumul, end_bounds, end_cumul, cumul_offset, solver)) {
939 solver->
Solve(model->RemainingTime() * solve_duration_ratio));
943 if (costs_without_transits !=
nullptr) {
944 costs_without_transits->at(i) =
945 optimize_vehicle_costs
950 if (cumul_values !=
nullptr) {
951 SetValuesFromLP(current_route_cumul_variables_, cumul_offset, solver,
952 &cumul_values->at(i));
954 if (break_values !=
nullptr) {
955 SetValuesFromLP(current_route_break_variables_, cumul_offset, solver,
956 &break_values->at(i));
967 const std::function<int64_t(int64_t)>& next_accessor,
968 const std::vector<RouteDimensionTravelInfo>&
969 dimension_travel_info_per_route,
971 std::vector<int64_t>* break_values,
972 std::vector<std::vector<int>>* resource_indices_per_group,
973 int64_t* cost_without_transits, int64_t* transit_cost,
bool clear_lp,
974 bool optimize_resource_assignment) {
975 InitOptimizer(solver);
976 if (!optimize_resource_assignment) {
977 DCHECK_EQ(resource_indices_per_group,
nullptr);
982 const bool optimize_costs =
983 (cumul_values !=
nullptr) || (cost_without_transits !=
nullptr);
984 bool has_vehicles_being_optimized =
false;
986 const int64_t cumul_offset = dimension_->GetGlobalOptimizerOffset();
988 if (propagator_ !=
nullptr &&
989 !propagator_->PropagateCumulBounds(next_accessor, cumul_offset,
990 &dimension_travel_info_per_route)) {
994 int64_t total_transit_cost = 0;
995 int64_t total_cost_offset = 0;
996 const RoutingModel* model = dimension_->model();
997 for (
int vehicle = 0; vehicle < model->vehicles(); vehicle++) {
998 int64_t route_transit_cost = 0;
999 int64_t route_cost_offset = 0;
1000 const bool vehicle_is_used =
1001 !model->IsEnd(next_accessor(model->Start(vehicle))) ||
1002 model->IsVehicleUsedWhenEmpty(vehicle);
1003 const bool optimize_vehicle_costs = optimize_costs && vehicle_is_used;
1004 const RouteDimensionTravelInfo*
const dimension_travel_info =
1005 dimension_travel_info_per_route.empty()
1007 : &dimension_travel_info_per_route[vehicle];
1008 if (!SetRouteCumulConstraints(
1009 vehicle, next_accessor, dimension_->transit_evaluator(vehicle),
1010 dimension_travel_info, cumul_offset, optimize_vehicle_costs, solver,
1011 &route_transit_cost, &route_cost_offset)) {
1014 DCHECK_GE(current_route_cumul_variables_.size(), 2);
1015 if (vehicle_is_used && !optimize_resource_assignment) {
1017 if (!TightenStartEndVariableBoundsWithAssignedResources(
1018 *dimension_, vehicle, current_route_cumul_variables_[0],
1019 current_route_cumul_variables_.back(), cumul_offset, solver)) {
1023 total_transit_cost =
CapAdd(total_transit_cost, route_transit_cost);
1024 total_cost_offset =
CapAdd(total_cost_offset, route_cost_offset);
1025 has_vehicles_being_optimized |= optimize_vehicle_costs;
1027 if (transit_cost !=
nullptr) {
1028 *transit_cost = total_transit_cost;
1031 if (!SetGlobalConstraints(next_accessor, cumul_offset,
1032 has_vehicles_being_optimized,
1033 optimize_resource_assignment, solver)) {
1038 solver->
Solve(model->RemainingTime());
1046 SetValuesFromLP(index_to_cumul_variable_, cumul_offset, solver, cumul_values);
1047 SetValuesFromLP(all_break_variables_, cumul_offset, solver, break_values);
1048 SetResourceIndices(solver, resource_indices_per_group);
1050 if (cost_without_transits !=
nullptr) {
1051 *cost_without_transits =
1062 const std::function<int64_t(int64_t)>& next_accessor,
1063 const std::vector<RouteDimensionTravelInfo>&
1064 dimension_travel_info_per_route,
1066 std::vector<int64_t>* break_values) {
1070 const glop::GlopParameters original_params = GetGlopParametersForGlobalLP();
1071 glop::GlopParameters packing_parameters;
1073 packing_parameters = original_params;
1074 packing_parameters.set_use_dual_simplex(
false);
1075 packing_parameters.set_use_preprocessing(
true);
1076 solver->
SetParameters(packing_parameters.SerializeAsString());
1079 Optimize(next_accessor, dimension_travel_info_per_route, solver,
1085 std::vector<int> vehicles(
dimension()->model()->vehicles());
1086 std::iota(vehicles.begin(), vehicles.end(), 0);
1089 status = PackRoutes(std::move(vehicles), 1.0,
1090 solver, packing_parameters);
1100 const int64_t global_offset = dimension_->GetGlobalOptimizerOffset();
1101 SetValuesFromLP(index_to_cumul_variable_, global_offset, solver,
1103 SetValuesFromLP(all_break_variables_, global_offset, solver, break_values);
1110 int vehicle,
double solve_duration_ratio,
1111 const std::function<int64_t(int64_t)>& next_accessor,
1112 const RouteDimensionTravelInfo* dimension_travel_info,
1113 const RoutingModel::ResourceGroup::Resource* resource,
1115 std::vector<int64_t>* break_values) {
1116 const glop::GlopParameters original_params = GetGlopParametersForLocalLP();
1117 glop::GlopParameters packing_parameters;
1119 packing_parameters = original_params;
1120 packing_parameters.set_use_dual_simplex(
false);
1121 packing_parameters.set_use_preprocessing(
true);
1122 solver->
SetParameters(packing_parameters.SerializeAsString());
1129 vehicle, solve_duration_ratio, next_accessor, dimension_travel_info,
1130 resource,
true, solver,
1137 PackRoutes({vehicle}, solve_duration_ratio, solver, packing_parameters);
1146 const int64_t local_offset =
1147 dimension_->GetLocalOptimizerOffsetForVehicle(vehicle);
1148 SetValuesFromLP(current_route_cumul_variables_, local_offset, solver,
1150 SetValuesFromLP(current_route_break_variables_, local_offset, solver,
1157 std::vector<int> vehicles,
double solve_duration_ratio,
1159 const glop::GlopParameters& packing_parameters) {
1160 const RoutingModel* model = dimension_->model();
1176 for (
int vehicle : vehicles) {
1178 index_to_cumul_variable_[model->End(vehicle)], 1);
1181 glop::GlopParameters current_params;
1182 const auto retry_solving = [¤t_params, model, solver,
1183 solve_duration_ratio]() {
1187 current_params.set_use_dual_simplex(!current_params.use_dual_simplex());
1188 solver->SetParameters(current_params.SerializeAsString());
1189 return solver->Solve(model->RemainingTime() * solve_duration_ratio);
1191 if (solver->Solve(model->RemainingTime() * solve_duration_ratio) ==
1193 if (solver->IsCPSATSolver()) {
1197 current_params = packing_parameters;
1205 solver->ClearObjective();
1206 for (
int vehicle : vehicles) {
1207 const int end_cumul_var = index_to_cumul_variable_[model->End(vehicle)];
1209 solver->SetVariableBounds(
1210 end_cumul_var, solver->GetVariableLowerBound(end_cumul_var),
1214 solver->SetObjectiveCoefficient(
1215 index_to_cumul_variable_[model->Start(vehicle)], -1);
1219 solver->Solve(model->RemainingTime() * solve_duration_ratio);
1220 if (!solver->IsCPSATSolver() &&
1222 status = retry_solving();
1227#define SET_DEBUG_VARIABLE_NAME(solver, var, name) \
1230 solver->SetVariableName(var, name); \
1234void DimensionCumulOptimizerCore::InitOptimizer(
1235 RoutingLinearSolverWrapper* solver) {
1237 index_to_cumul_variable_.assign(dimension_->cumuls().size(), -1);
1238 max_end_cumul_ = solver->CreateNewPositiveVariable();
1240 min_start_cumul_ = solver->CreateNewPositiveVariable();
1244bool DimensionCumulOptimizerCore::ExtractRouteCumulBounds(
1245 absl::Span<const int64_t> route, int64_t cumul_offset) {
1246 const int route_size = route.size();
1247 current_route_min_cumuls_.resize(route_size);
1248 current_route_max_cumuls_.resize(route_size);
1251 for (
int pos = 0; pos < route_size; ++pos) {
1252 if (!GetCumulBoundsWithOffset(*dimension_, route[pos], cumul_offset,
1253 ¤t_route_min_cumuls_[pos],
1254 ¤t_route_max_cumuls_[pos])) {
1261bool DimensionCumulOptimizerCore::TightenRouteCumulBounds(
1262 absl::Span<const int64_t> route, absl::Span<const int64_t> min_transits,
1263 int64_t cumul_offset) {
1264 const int route_size = route.size();
1265 if (propagator_ !=
nullptr) {
1266 for (
int pos = 0; pos < route_size; pos++) {
1267 const int64_t node = route[pos];
1268 current_route_min_cumuls_[pos] = propagator_->CumulMin(node);
1269 DCHECK_GE(current_route_min_cumuls_[pos], 0);
1270 current_route_max_cumuls_[pos] = propagator_->CumulMax(node);
1271 DCHECK_GE(current_route_max_cumuls_[pos], current_route_min_cumuls_[pos]);
1278 for (
int pos = 1; pos < route_size; ++pos) {
1279 const int64_t slack_min = dimension_->SlackVar(route[pos - 1])->Min();
1280 current_route_min_cumuls_[pos] = std::max(
1281 current_route_min_cumuls_[pos],
1283 CapAdd(current_route_min_cumuls_[pos - 1], min_transits[pos - 1]),
1285 current_route_min_cumuls_[pos] = GetFirstPossibleValueForCumulWithOffset(
1286 *dimension_, route[pos], current_route_min_cumuls_[pos], cumul_offset);
1287 if (current_route_min_cumuls_[pos] > current_route_max_cumuls_[pos]) {
1292 for (
int pos = route_size - 2; pos >= 0; --pos) {
1295 if (current_route_max_cumuls_[pos + 1] <
1296 std::numeric_limits<int64_t>::max()) {
1297 const int64_t slack_min = dimension_->SlackVar(route[pos])->Min();
1298 current_route_max_cumuls_[pos] = std::min(
1299 current_route_max_cumuls_[pos],
1300 CapSub(
CapSub(current_route_max_cumuls_[pos + 1], min_transits[pos]),
1302 current_route_max_cumuls_[pos] = GetLastPossibleValueForCumulWithOffset(
1303 *dimension_, route[pos], current_route_max_cumuls_[pos],
1305 if (current_route_max_cumuls_[pos] < current_route_min_cumuls_[pos]) {
1316 const auto& x_anchors = pwl_function.
x_anchors();
1317 const auto& y_anchors = pwl_function.
y_anchors();
1318 if (index_end < 0) index_end = x_anchors.size() - 1;
1319 const int num_segments = index_end - index_start;
1320 DCHECK_GE(num_segments, 1);
1321 std::vector<SlopeAndYIntercept> slope_and_y_intercept(num_segments);
1322 for (
int seg = index_start; seg < index_end; ++seg) {
1323 auto& [slope, y_intercept] = slope_and_y_intercept[seg - index_start];
1324 slope = (y_anchors[seg + 1] - y_anchors[seg]) /
1325 static_cast<double>(x_anchors[seg + 1] - x_anchors[seg]);
1326 y_intercept = y_anchors[seg] - slope * x_anchors[seg];
1328 return slope_and_y_intercept;
1332 absl::Span<const SlopeAndYIntercept> slope_and_y_intercept) {
1333 CHECK(!slope_and_y_intercept.empty());
1334 std::vector<bool> convex(slope_and_y_intercept.size(),
false);
1335 double previous_slope = std::numeric_limits<double>::max();
1336 for (
int i = 0; i < slope_and_y_intercept.size(); ++i) {
1337 const auto& pair = slope_and_y_intercept[i];
1338 if (pair.slope < previous_slope) {
1341 previous_slope = pair.slope;
1349double FindBestScaling(absl::Span<const double> coefficients,
1350 absl::Span<const double> lower_bounds,
1351 absl::Span<const double> upper_bounds,
1352 int64_t max_absolute_activity,
1353 double wanted_absolute_activity_precision) {
1354 double unused_relative_coeff_error = 0;
1355 double unused_scaled_sum_error = 0;
1357 coefficients, lower_bounds, upper_bounds, max_absolute_activity,
1358 wanted_absolute_activity_precision, &unused_relative_coeff_error,
1359 &unused_scaled_sum_error);
1363bool DimensionCumulOptimizerCore::SetRouteTravelConstraints(
1364 const RouteDimensionTravelInfo* dimension_travel_info,
1365 absl::Span<const int> lp_slacks, absl::Span<const int64_t> fixed_transit,
1367 const std::vector<int>& lp_cumuls = current_route_cumul_variables_;
1368 const int path_size = lp_cumuls.size();
1370 if (dimension_travel_info ==
nullptr ||
1371 dimension_travel_info->transition_info.empty()) {
1376 for (
int pos = 0; pos < path_size - 1; ++pos) {
1378 solver->CreateNewConstraint(fixed_transit[pos], fixed_transit[pos]);
1379 solver->SetCoefficient(ct, lp_cumuls[pos + 1], 1);
1380 solver->SetCoefficient(ct, lp_cumuls[pos], -1);
1381 solver->SetCoefficient(ct, lp_slacks[pos], -1);
1388 for (
int pos = 0; pos < path_size - 1; ++pos) {
1392 const int compression_cost = solver->CreateNewPositiveVariable();
1394 absl::StrFormat(
"compression_cost(%ld)", pos));
1401 const int relative_compression_cost = solver->CreateNewPositiveVariable();
1403 solver, relative_compression_cost,
1404 absl::StrFormat(
"relative_compression_cost(%ld)", pos));
1406 const RoutingModel::RouteDimensionTravelInfo::TransitionInfo&
1407 transition_info = dimension_travel_info->transition_info[pos];
1408 const FloatSlopePiecewiseLinearFunction& travel_function =
1409 transition_info.travel_start_dependent_travel;
1410 const auto& travel_x_anchors = travel_function.x_anchors();
1414 const int64_t pre_travel_transit = transition_info.pre_travel_transit_value;
1415 const int64_t post_travel_transit =
1416 transition_info.post_travel_transit_value;
1417 const int64_t compressed_travel_value_lower_bound =
1418 transition_info.compressed_travel_value_lower_bound;
1419 const int64_t travel_value_upper_bound =
1420 transition_info.travel_value_upper_bound;
1426 const int travel_value = solver->AddVariable(
1427 compressed_travel_value_lower_bound, travel_value_upper_bound);
1429 absl::StrFormat(
"travel_value(%ld)", pos));
1430 const int travel_start = solver->AddVariable(
1431 current_route_min_cumuls_[pos] + pre_travel_transit,
1432 current_route_max_cumuls_[pos + 1] - post_travel_transit -
1433 compressed_travel_value_lower_bound);
1435 absl::StrFormat(
"travel_start(%ld)", pos));
1438 solver->AddLinearConstraint(pre_travel_transit, pre_travel_transit,
1439 {{travel_start, 1}, {lp_cumuls[pos], -1}});
1444 const int num_pwl_anchors = travel_x_anchors.size();
1445 int index_anchor_start = 0;
1446 while (index_anchor_start < num_pwl_anchors - 1 &&
1447 travel_x_anchors[index_anchor_start + 1] <=
1448 current_route_min_cumuls_[pos] + pre_travel_transit) {
1449 ++index_anchor_start;
1451 int index_anchor_end = num_pwl_anchors - 1;
1452 while (index_anchor_end > 0 &&
1453 travel_x_anchors[index_anchor_end - 1] >=
1454 current_route_max_cumuls_[pos] + pre_travel_transit) {
1458 if (index_anchor_start >= index_anchor_end)
return false;
1463 const std::vector<SlopeAndYIntercept> slope_and_y_intercept =
1465 travel_function, index_anchor_start, index_anchor_end);
1468 const std::vector<bool> convexities =
1471 int nb_bin_variables = 0;
1472 for (
const bool convexity : convexities) {
1475 if (nb_bin_variables >= 2)
break;
1478 const bool need_bins = (nb_bin_variables > 1);
1480 const int travel_start_in_one_segment_ct =
1481 need_bins ? solver->CreateNewConstraint(1, 1)
1484 int belongs_to_this_segment_var;
1485 for (
int seg = 0; seg < convexities.size(); ++seg) {
1486 if (need_bins && convexities[seg]) {
1487 belongs_to_this_segment_var = solver->AddVariable(0, 1);
1489 solver, belongs_to_this_segment_var,
1490 absl::StrFormat(
"travel_start(%ld)belongs_to_seg(%ld)", pos, seg));
1491 solver->SetCoefficient(travel_start_in_one_segment_ct,
1492 belongs_to_this_segment_var, 1);
1497 const int64_t lower_bound_interval =
1498 seg > 0 ? travel_x_anchors[index_anchor_start + seg]
1499 : current_route_min_cumuls_[pos] + pre_travel_transit;
1500 int64_t end_of_seg = seg + 1;
1501 const int num_convexities = convexities.size();
1502 while (end_of_seg < num_convexities && !convexities[end_of_seg]) {
1505 const int64_t higher_bound_interval =
1506 end_of_seg < num_pwl_anchors - 1
1507 ? travel_x_anchors[index_anchor_start + end_of_seg]
1508 : current_route_max_cumuls_[pos] + pre_travel_transit;
1509 const int travel_start_in_segment_ct = solver->AddLinearConstraint(
1510 lower_bound_interval, higher_bound_interval, {{travel_start, 1}});
1511 solver->SetEnforcementLiteral(travel_start_in_segment_ct,
1512 belongs_to_this_segment_var);
1517 const auto [slope, y_intercept] = slope_and_y_intercept[seg];
1519 DCHECK_GE(slope, -1.0) <<
"Travel value PWL should have a slope >= -1";
1529 const double upper_bound = current_route_max_cumuls_[pos];
1530 const double factor = FindBestScaling(
1531 {1.0, -slope, y_intercept - 0.5},
1532 {
static_cast<double>(compressed_travel_value_lower_bound), 0, 1},
1534 {
static_cast<double>(travel_value_upper_bound), upper_bound, 1},
1540 if (factor <= 0)
return false;
1542 const int linearization_ct = solver->AddLinearConstraint(
1544 std::numeric_limits<int64_t>::max(),
1548 solver->SetEnforcementLiteral(linearization_ct,
1549 belongs_to_this_segment_var);
1575 const int compressed_travel_value = solver->AddVariable(
1576 compressed_travel_value_lower_bound, travel_value_upper_bound);
1578 solver, compressed_travel_value,
1579 absl::StrFormat(
"compressed_travel_value(%ld)", pos));
1580 solver->AddLinearConstraint(post_travel_transit + pre_travel_transit,
1581 post_travel_transit + pre_travel_transit,
1582 {{compressed_travel_value, -1},
1583 {lp_cumuls[pos + 1], 1},
1584 {lp_cumuls[pos], -1},
1585 {lp_slacks[pos], -1}});
1592 const int travel_compression_absolute = solver->AddVariable(
1593 0, travel_value_upper_bound - compressed_travel_value_lower_bound);
1595 solver, travel_compression_absolute,
1596 absl::StrFormat(
"travel_compression_absolute(%ld)", pos));
1598 solver->AddLinearConstraint(0, 0,
1599 {{travel_compression_absolute, 1},
1601 {compressed_travel_value, 1}});
1607 solver->SetObjectiveCoefficient(
1608 travel_value, dimension_travel_info->travel_cost_coefficient);
1612 const FloatSlopePiecewiseLinearFunction& cost_function =
1613 transition_info.travel_compression_cost;
1614 const auto& cost_x_anchors = cost_function.x_anchors();
1616 const std::vector<SlopeAndYIntercept> cost_slope_and_y_intercept =
1618 const double cost_max = cost_function.ComputeConvexValue(
1619 travel_value_upper_bound - compressed_travel_value_lower_bound);
1620 double previous_slope = 0;
1621 for (
int seg = 0; seg < cost_x_anchors.size() - 1; ++seg) {
1622 const auto [slope, y_intercept] = cost_slope_and_y_intercept[seg];
1624 DCHECK_GE(slope, previous_slope)
1625 <<
"Compression error is not convex. Segment " << (1 + seg)
1626 <<
" out of " << (cost_x_anchors.size() - 1);
1627 previous_slope = slope;
1628 const double factor = FindBestScaling(
1629 {1.0, -slope, y_intercept},
1630 {0,
static_cast<double>(compressed_travel_value_lower_bound), 1},
1632 {cost_max,
static_cast<double>(travel_value_upper_bound), 1},
1633 (
static_cast<int64_t
>(1) << 62),
1638 if (factor <= 0)
return false;
1640 solver->AddLinearConstraint(
1642 std::numeric_limits<int64_t>::max(),
1643 {{compression_cost, std::round(factor)},
1644 {travel_compression_absolute,
1664 solver->AddLinearConstraint(
1665 0, std::numeric_limits<int64_t>::max(),
1666 {{relative_compression_cost, 1}, {compression_cost, -1}});
1668 solver->SetObjectiveCoefficient(relative_compression_cost, 1.0);
1674bool RouteIsValid(
const RoutingModel& model,
int vehicle,
1675 const std::function<int64_t(int64_t)>& next_accessor) {
1676 absl::flat_hash_set<int64_t> visited;
1677 int node = model.Start(vehicle);
1678 visited.insert(node);
1679 while (!model.IsEnd(node)) {
1680 node = next_accessor(node);
1681 if (visited.contains(node))
return false;
1682 visited.insert(node);
1684 return visited.size() >= 2;
1688bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
1689 int vehicle,
const std::function<int64_t(int64_t)>& next_accessor,
1690 const std::function<int64_t(int64_t, int64_t)>& transit_accessor,
1691 const RouteDimensionTravelInfo* dimension_travel_info, int64_t cumul_offset,
1693 int64_t* route_transit_cost, int64_t* route_cost_offset) {
1694 RoutingModel*
const model = dimension_->model();
1696 std::vector<int64_t> path;
1698 DCHECK(RouteIsValid(*model, vehicle, next_accessor));
1699 int node = model->Start(vehicle);
1700 path.push_back(node);
1701 while (!model->IsEnd(node)) {
1702 node = next_accessor(node);
1703 path.push_back(node);
1705 DCHECK_GE(path.size(), 2);
1707 const int path_size = path.size();
1709 std::vector<int64_t> fixed_transit(path_size - 1);
1710 int64_t total_fixed_transit = 0;
1712 for (
int pos = 1; pos < path_size; ++pos) {
1713 int64_t& transit = fixed_transit[pos - 1];
1714 transit = transit_accessor(path[pos - 1], path[pos]);
1715 total_fixed_transit =
CapAdd(total_fixed_transit, transit);
1718 if (!ExtractRouteCumulBounds(path, cumul_offset)) {
1721 if (dimension_travel_info ==
nullptr ||
1722 dimension_travel_info->transition_info.empty()) {
1723 if (!TightenRouteCumulBounds(path, fixed_transit, cumul_offset)) {
1728 std::vector<int64_t> min_transit(path_size - 1);
1729 for (
int pos = 0; pos < path_size - 1; ++pos) {
1730 const RouteDimensionTravelInfo::TransitionInfo& transition =
1731 dimension_travel_info->transition_info[pos];
1732 min_transit[pos] = transition.pre_travel_transit_value +
1733 transition.compressed_travel_value_lower_bound +
1734 transition.post_travel_transit_value;
1736 if (!TightenRouteCumulBounds(path, min_transit, cumul_offset)) {
1743 std::vector<int>& lp_cumuls = current_route_cumul_variables_;
1744 lp_cumuls.assign(path_size, -1);
1745 for (
int pos = 0; pos < path_size; ++pos) {
1746 const int lp_cumul = solver->CreateNewPositiveVariable();
1748 absl::StrFormat(
"lp_cumul(%ld)", pos));
1749 index_to_cumul_variable_[path[pos]] = lp_cumul;
1750 lp_cumuls[pos] = lp_cumul;
1751 if (!solver->SetVariableBounds(lp_cumul, current_route_min_cumuls_[pos],
1752 current_route_max_cumuls_[pos])) {
1755 const SortedDisjointIntervalList& forbidden =
1756 dimension_->forbidden_intervals()[path[pos]];
1757 if (forbidden.NumIntervals() > 0) {
1758 std::vector<int64_t> starts;
1759 std::vector<int64_t> ends;
1760 for (
const ClosedInterval interval :
1761 dimension_->GetAllowedIntervalsInRange(
1762 path[pos],
CapAdd(current_route_min_cumuls_[pos], cumul_offset),
1763 CapAdd(current_route_max_cumuls_[pos], cumul_offset))) {
1764 starts.push_back(
CapSub(interval.start, cumul_offset));
1765 ends.push_back(
CapSub(interval.end, cumul_offset));
1767 solver->SetVariableDisjointBounds(lp_cumul, starts, ends);
1770 solver->AddRoute(path, lp_cumuls);
1772 std::vector<int> lp_slacks(path_size - 1, -1);
1773 for (
int pos = 0; pos < path_size - 1; ++pos) {
1774 const IntVar* cp_slack = dimension_->SlackVar(path[pos]);
1775 lp_slacks[pos] = solver->CreateNewPositiveVariable();
1777 absl::StrFormat(
"lp_slacks(%ld)", pos));
1778 if (!solver->SetVariableBounds(lp_slacks[pos], cp_slack->Min(),
1784 if (!SetRouteTravelConstraints(dimension_travel_info, lp_slacks,
1785 fixed_transit, solver)) {
1789 if (route_cost_offset !=
nullptr) *route_cost_offset = 0;
1790 if (optimize_costs) {
1792 for (
int pos = 0; pos < path_size; ++pos) {
1793 if (!dimension_->HasCumulVarSoftUpperBound(path[pos]))
continue;
1794 const int64_t coef =
1795 dimension_->GetCumulVarSoftUpperBoundCoefficient(path[pos]);
1796 if (coef == 0)
continue;
1797 int64_t
bound = dimension_->GetCumulVarSoftUpperBound(path[pos]);
1798 if (bound < cumul_offset && route_cost_offset !=
nullptr) {
1800 *route_cost_offset =
CapAdd(*route_cost_offset,
1803 bound = std::max<int64_t>(0,
CapSub(bound, cumul_offset));
1804 if (current_route_max_cumuls_[pos] <= bound) {
1808 const int soft_ub_diff = solver->CreateNewPositiveVariable();
1810 absl::StrFormat(
"soft_ub_diff(%ld)", pos));
1811 solver->SetObjectiveCoefficient(soft_ub_diff, coef);
1813 const int ct = solver->CreateNewConstraint(
1814 std::numeric_limits<int64_t>::min(), bound);
1815 solver->SetCoefficient(ct, lp_cumuls[pos], 1);
1816 solver->SetCoefficient(ct, soft_ub_diff, -1);
1819 for (
int pos = 0; pos < path_size; ++pos) {
1820 if (!dimension_->HasCumulVarSoftLowerBound(path[pos]))
continue;
1821 const int64_t coef =
1822 dimension_->GetCumulVarSoftLowerBoundCoefficient(path[pos]);
1823 if (coef == 0)
continue;
1824 const int64_t
bound = std::max<int64_t>(
1825 0,
CapSub(dimension_->GetCumulVarSoftLowerBound(path[pos]),
1827 if (current_route_min_cumuls_[pos] >= bound) {
1831 const int soft_lb_diff = solver->CreateNewPositiveVariable();
1833 absl::StrFormat(
"soft_lb_diff(%ld)", pos));
1834 solver->SetObjectiveCoefficient(soft_lb_diff, coef);
1836 const int ct = solver->CreateNewConstraint(
1837 bound, std::numeric_limits<int64_t>::max());
1838 solver->SetCoefficient(ct, lp_cumuls[pos], 1);
1839 solver->SetCoefficient(ct, soft_lb_diff, 1);
1843 std::vector<int> visited_pairs;
1844 StoreVisitedPickupDeliveryPairsOnRoute(
1845 *dimension_, vehicle, next_accessor, &visited_pairs,
1846 &visited_pickup_delivery_indices_for_pair_);
1847 for (
int pair_index : visited_pairs) {
1848 const int64_t pickup_index =
1849 visited_pickup_delivery_indices_for_pair_[pair_index].first;
1850 const int64_t delivery_index =
1851 visited_pickup_delivery_indices_for_pair_[pair_index].second;
1852 visited_pickup_delivery_indices_for_pair_[pair_index] = {-1, -1};
1854 DCHECK_GE(pickup_index, 0);
1855 if (delivery_index < 0) {
1860 const int64_t limit = dimension_->GetPickupToDeliveryLimitForPair(
1861 pair_index, model->GetPickupPosition(pickup_index)->alternative_index,
1862 model->GetDeliveryPosition(delivery_index)->alternative_index);
1863 if (limit < std::numeric_limits<int64_t>::max()) {
1865 const int ct = solver->CreateNewConstraint(
1866 std::numeric_limits<int64_t>::min(), limit);
1867 solver->SetCoefficient(ct, index_to_cumul_variable_[delivery_index], 1);
1868 solver->SetCoefficient(ct, index_to_cumul_variable_[pickup_index], -1);
1873 const int64_t span_bound = dimension_->GetSpanUpperBoundForVehicle(vehicle);
1874 if (span_bound < std::numeric_limits<int64_t>::max()) {
1876 const int ct = solver->CreateNewConstraint(
1877 std::numeric_limits<int64_t>::min(), span_bound);
1878 solver->SetCoefficient(ct, lp_cumuls.back(), 1);
1879 solver->SetCoefficient(ct, lp_cumuls.front(), -1);
1884 const int64_t span_cost_coef =
1885 dimension_->GetSpanCostCoefficientForVehicle(vehicle);
1886 const int64_t slack_cost_coef =
CapAdd(
1887 span_cost_coef, dimension_->GetSlackCostCoefficientForVehicle(vehicle));
1888 if (optimize_costs && slack_cost_coef > 0) {
1891 const int span_without_fixed_transit_var =
1892 solver->CreateNewPositiveVariable();
1894 "span_without_fixed_transit_var");
1895 solver->AddLinearConstraint(total_fixed_transit, total_fixed_transit,
1896 {{lp_cumuls.back(), 1},
1897 {lp_cumuls.front(), -1},
1898 {span_without_fixed_transit_var, -1}});
1899 solver->SetObjectiveCoefficient(span_without_fixed_transit_var,
1903 if (optimize_costs && dimension_->HasSoftSpanUpperBounds()) {
1904 const BoundCost bound_cost =
1905 dimension_->GetSoftSpanUpperBoundForVehicle(vehicle);
1906 if (bound_cost.bound < std::numeric_limits<int64_t>::max() &&
1907 bound_cost.cost > 0) {
1908 const int span_violation = solver->CreateNewPositiveVariable();
1911 const int violation = solver->CreateNewConstraint(
1912 std::numeric_limits<int64_t>::min(), bound_cost.bound);
1913 solver->SetCoefficient(violation, lp_cumuls.back(), 1.0);
1914 solver->SetCoefficient(violation, lp_cumuls.front(), -1.0);
1915 solver->SetCoefficient(violation, span_violation, -1.0);
1917 solver->SetObjectiveCoefficient(span_violation, bound_cost.cost);
1920 if (optimize_costs && solver->IsCPSATSolver() &&
1921 dimension_->HasQuadraticCostSoftSpanUpperBounds()) {
1923 const BoundCost bound_cost =
1924 dimension_->GetQuadraticCostSoftSpanUpperBoundForVehicle(vehicle);
1925 if (bound_cost.bound < std::numeric_limits<int64_t>::max() &&
1926 bound_cost.cost > 0) {
1927 const int span_violation = solver->CreateNewPositiveVariable();
1929 solver, span_violation,
1930 absl::StrFormat(
"quadratic_span_violation(%ld)", vehicle));
1932 const int violation = solver->CreateNewConstraint(
1933 std::numeric_limits<int64_t>::min(), bound_cost.bound);
1934 solver->SetCoefficient(violation, lp_cumuls.back(), 1.0);
1935 solver->SetCoefficient(violation, lp_cumuls.front(), -1.0);
1936 solver->SetCoefficient(violation, span_violation, -1.0);
1938 const int squared_span_violation = solver->CreateNewPositiveVariable();
1940 solver, squared_span_violation,
1941 absl::StrFormat(
"squared_span_violation(%ld)", vehicle));
1942 solver->AddProductConstraint(squared_span_violation,
1943 {span_violation, span_violation});
1945 solver->SetObjectiveCoefficient(squared_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) {
2024 std::vector<LpBreak> lp_breaks;
2025 if (solver->IsCPSATSolver()) {
2026 lp_breaks.resize(num_breaks, {.start = -1, .end = -1, .duration = -1});
2029 std::vector<int> slack_exact_lower_bound_ct(path_size - 1, -1);
2030 std::vector<int> slack_linear_lower_bound_ct(path_size - 1, -1);
2032 const int64_t vehicle_start_min = current_route_min_cumuls_.front();
2033 const int64_t vehicle_start_max = current_route_max_cumuls_.front();
2034 const int64_t vehicle_end_min = current_route_min_cumuls_.back();
2035 const int64_t vehicle_end_max = current_route_max_cumuls_.back();
2036 const int all_break_variables_offset =
2037 vehicle_to_all_break_variables_offset_[vehicle];
2038 for (
int br = 0; br < num_breaks; ++br) {
2039 const IntervalVar& break_var = *breaks[br];
2040 if (!break_var.MustBePerformed())
continue;
2041 const int64_t break_start_min =
CapSub(break_var.StartMin(), cumul_offset);
2042 const int64_t break_start_max =
CapSub(break_var.StartMax(), cumul_offset);
2043 const int64_t break_end_min =
CapSub(break_var.EndMin(), cumul_offset);
2044 const int64_t break_end_max =
CapSub(break_var.EndMax(), cumul_offset);
2045 const int64_t break_duration_min = break_var.DurationMin();
2046 const int64_t break_duration_max = break_var.DurationMax();
2049 if (solver->IsCPSATSolver()) {
2050 if (break_end_max <= vehicle_start_min ||
2051 vehicle_end_max <= break_start_min) {
2052 all_break_variables_[all_break_variables_offset + 2 * br] = -1;
2053 all_break_variables_[all_break_variables_offset + 2 * br + 1] = -1;
2054 current_route_break_variables_.push_back(-1);
2055 current_route_break_variables_.push_back(-1);
2059 .start = solver->AddVariable(break_start_min, break_start_max),
2060 .end = solver->AddVariable(break_end_min, break_end_max),
2062 solver->AddVariable(break_duration_min, break_duration_max)};
2063 const auto [break_start, break_end, break_duration] = lp_breaks[br];
2065 absl::StrFormat(
"lp_break_start(%ld)", br));
2067 absl::StrFormat(
"lp_break_end(%ld)", br));
2069 absl::StrFormat(
"lp_break_duration(%ld)", br));
2071 solver->AddLinearConstraint(
2072 0, 0, {{break_end, 1}, {break_start, -1}, {break_duration, -1}});
2074 all_break_variables_[all_break_variables_offset + 2 * br] = break_start;
2075 all_break_variables_[all_break_variables_offset + 2 * br + 1] = break_end;
2076 current_route_break_variables_.push_back(break_start);
2077 current_route_break_variables_.push_back(break_end);
2079 if (break_end_min <= vehicle_start_max ||
2080 vehicle_end_min <= break_start_max) {
2081 all_break_variables_[all_break_variables_offset + 2 * br] = -1;
2082 all_break_variables_[all_break_variables_offset + 2 * br + 1] = -1;
2083 current_route_break_variables_.push_back(-1);
2084 current_route_break_variables_.push_back(-1);
2092 const int break_in_one_slack_ct = solver->CreateNewConstraint(1, 1);
2094 if (solver->IsCPSATSolver()) {
2096 if (break_end_min <= vehicle_start_max) {
2097 const int ct = solver->AddLinearConstraint(
2098 0, std::numeric_limits<int64_t>::max(),
2099 {{lp_cumuls.front(), 1}, {lp_breaks[br].end, -1}});
2100 const int break_is_before_route = solver->AddVariable(0, 1);
2102 solver, break_is_before_route,
2103 absl::StrFormat(
"break_is_before_route(%ld)", br));
2104 solver->SetEnforcementLiteral(ct, break_is_before_route);
2105 solver->SetCoefficient(break_in_one_slack_ct, break_is_before_route, 1);
2108 if (vehicle_end_min <= break_start_max) {
2109 const int ct = solver->AddLinearConstraint(
2110 0, std::numeric_limits<int64_t>::max(),
2111 {{lp_breaks[br].start, 1}, {lp_cumuls.back(), -1}});
2112 const int break_is_after_route = solver->AddVariable(0, 1);
2114 solver, break_is_after_route,
2115 absl::StrFormat(
"break_is_after_route(%ld)", br));
2116 solver->SetEnforcementLiteral(ct, break_is_after_route);
2117 solver->SetCoefficient(break_in_one_slack_ct, break_is_after_route, 1);
2122 for (
int pos = 0; pos < path_size - 1; ++pos) {
2125 const int64_t slack_start_min =
2126 CapAdd(current_route_min_cumuls_[pos], pre_travel[pos]);
2127 if (slack_start_min > break_start_max)
break;
2128 const int64_t slack_end_max =
2129 CapSub(current_route_max_cumuls_[pos + 1], post_travel[pos]);
2130 if (break_end_min > slack_end_max)
continue;
2131 const int64_t slack_duration_max =
2132 std::min(
CapSub(
CapSub(current_route_max_cumuls_[pos + 1],
2133 current_route_min_cumuls_[pos]),
2134 fixed_transit[pos]),
2135 dimension_->SlackVar(path[pos])->Max());
2136 if (slack_duration_max < break_duration_min)
continue;
2143 const int break_in_slack = solver->AddVariable(0, 1);
2145 solver, break_in_slack,
2146 absl::StrFormat(
"break_in_slack(%ld, %ld)", br, pos));
2147 if (slack_linear_lower_bound_ct[pos] == -1) {
2148 slack_linear_lower_bound_ct[pos] = solver->AddLinearConstraint(
2149 std::numeric_limits<int64_t>::min(), 0, {{lp_slacks[pos], -1}});
2154 if (break_in_one_slack_ct < slack_linear_lower_bound_ct[pos]) {
2155 solver->SetCoefficient(break_in_one_slack_ct, break_in_slack, 1);
2156 solver->SetCoefficient(slack_linear_lower_bound_ct[pos], break_in_slack,
2157 break_duration_min);
2159 solver->SetCoefficient(slack_linear_lower_bound_ct[pos], break_in_slack,
2160 break_duration_min);
2161 solver->SetCoefficient(break_in_one_slack_ct, break_in_slack, 1);
2164 if (solver->IsCPSATSolver()) {
2169 const int break_duration_in_slack =
2170 solver->AddVariable(0, slack_duration_max);
2172 solver, break_duration_in_slack,
2173 absl::StrFormat(
"break_duration_in_slack(%ld, %ld)", br, pos));
2174 solver->AddProductConstraint(break_duration_in_slack,
2175 {break_in_slack, lp_breaks[br].duration});
2176 if (slack_exact_lower_bound_ct[pos] == -1) {
2177 slack_exact_lower_bound_ct[pos] = solver->AddLinearConstraint(
2178 std::numeric_limits<int64_t>::min(), 0, {{lp_slacks[pos], -1}});
2180 solver->SetCoefficient(slack_exact_lower_bound_ct[pos],
2181 break_duration_in_slack, 1);
2184 const int break_start_after_current_ct = solver->AddLinearConstraint(
2185 pre_travel[pos], std::numeric_limits<int64_t>::max(),
2186 {{lp_breaks[br].start, 1}, {lp_cumuls[pos], -1}});
2187 solver->SetEnforcementLiteral(break_start_after_current_ct,
2190 const int break_ends_before_next_ct = solver->AddLinearConstraint(
2191 post_travel[pos], std::numeric_limits<int64_t>::max(),
2192 {{lp_cumuls[pos + 1], 1}, {lp_breaks[br].end, -1}});
2193 solver->SetEnforcementLiteral(break_ends_before_next_ct,
2199 for (
const auto& distance_duration :
2200 dimension_->GetBreakDistanceDurationOfVehicle(vehicle)) {
2201 const int64_t limit = distance_duration.first;
2202 const int64_t min_break_duration = distance_duration.second;
2203 int64_t min_num_breaks = 0;
2206 std::max<int64_t>(0,
CapSub(total_fixed_transit, 1) / limit);
2208 if (
CapSub(current_route_min_cumuls_.back(),
2209 current_route_max_cumuls_.front()) > limit) {
2210 min_num_breaks = std::max<int64_t>(min_num_breaks, 1);
2212 if (num_breaks < min_num_breaks)
return false;
2213 if (min_num_breaks == 0)
continue;
2235 std::vector<int64_t> sum_transits(path_size);
2237 sum_transits[0] = 0;
2238 for (
int pos = 1; pos < path_size; ++pos) {
2239 sum_transits[pos] = sum_transits[pos - 1] + fixed_transit[pos - 1];
2246 std::vector<int> slack_sum_vars(path_size, -1);
2251 auto trigger = [&](
int k,
int pl,
int pr) ->
bool {
2252 const int64_t r_pre_travel = pr < path_size - 1 ? pre_travel[pr] : 0;
2253 const int64_t l_post_travel = pl >= 1 ? post_travel[pl - 1] : 0;
2254 const int64_t extra_travel =
CapAdd(r_pre_travel, l_post_travel);
2256 const int64_t span_lb =
CapAdd(
CapSub(current_route_min_cumuls_[pr],
2257 current_route_max_cumuls_[pl]),
2259 if (span_lb > limit)
return true;
2261 return CapAdd(
CapSub(sum_transits[pr], sum_transits[pl]), extra_travel) >
2264 int min_sum_var_index = path_size;
2265 int max_sum_var_index = -1;
2266 for (
int k = 1; k <= min_num_breaks; ++k) {
2268 for (
int pl = 0; pl < path_size - 1; ++pl) {
2269 pr = std::max(pr, pl + 1);
2271 while (pr < path_size && !trigger(k, pl, pr)) ++pr;
2272 if (pr == path_size)
break;
2274 while (pl < pr && trigger(k, pl + 1, pr)) ++pl;
2277 if (pl == pr)
return false;
2282 if (k < min_num_breaks && trigger(k + 1, pl, pr))
continue;
2286 if (solver->IsCPSATSolver()) {
2289 solver->AddLinearConstraint(
2291 CapProd(k, min_break_duration)),
2292 kint64max, {{lp_cumuls[pr], 1}, {lp_cumuls[pl], -1}});
2294 if (slack_sum_vars[pl] == -1) {
2295 slack_sum_vars[pl] = solver->CreateNewPositiveVariable();
2297 absl::StrFormat(
"slack_sum_vars(%ld)", pl));
2298 min_sum_var_index = std::min(min_sum_var_index, pl);
2300 if (slack_sum_vars[pr] == -1) {
2301 slack_sum_vars[pr] = solver->CreateNewPositiveVariable();
2303 absl::StrFormat(
"slack_sum_vars(%ld)", pr));
2304 max_sum_var_index = std::max(max_sum_var_index, pr);
2307 solver->AddLinearConstraint(
2309 {{slack_sum_vars[pr], 1}, {slack_sum_vars[pl], -1}});
2313 if (min_sum_var_index < max_sum_var_index) {
2314 slack_sum_vars[min_sum_var_index] = solver->AddVariable(0, 0);
2315 int prev_index = min_sum_var_index;
2316 for (
int pos = min_sum_var_index + 1; pos <= max_sum_var_index; ++pos) {
2317 if (slack_sum_vars[pos] == -1)
continue;
2320 const int ct = solver->AddLinearConstraint(
2321 0, 0, {{slack_sum_vars[pos], 1}, {slack_sum_vars[prev_index], -1}});
2322 for (
int p = prev_index; p < pos; ++p) {
2323 solver->SetCoefficient(ct, lp_slacks[p], -1);
2329 if (!solver->IsCPSATSolver())
return true;
2330 if (!dimension_->GetBreakDistanceDurationOfVehicle(vehicle).empty()) {
2333 for (
const IntervalVar* interval :
2334 dimension_->GetBreakIntervalsOfVehicle(vehicle)) {
2335 if (!interval->MustBePerformed())
return true;
2338 for (
int br = 1; br < num_breaks; ++br) {
2339 if (lp_breaks[br].start == -1 || lp_breaks[br - 1].start == -1)
continue;
2340 solver->AddLinearConstraint(
2341 0, std::numeric_limits<int64_t>::max(),
2342 {{lp_breaks[br - 1].end, -1}, {lp_breaks[br].start, 1}});
2345 for (
const auto& distance_duration :
2346 dimension_->GetBreakDistanceDurationOfVehicle(vehicle)) {
2347 const int64_t limit = distance_duration.first;
2348 const int64_t min_break_duration = distance_duration.second;
2376 int previous_cover = solver->AddVariable(
CapAdd(vehicle_start_min, limit),
2377 CapAdd(vehicle_start_max, limit));
2379 solver->AddLinearConstraint(limit, limit,
2380 {{previous_cover, 1}, {lp_cumuls.front(), -1}});
2381 for (
int br = 0; br < num_breaks; ++br) {
2382 const LpBreak& lp_break = lp_breaks[br];
2383 if (lp_break.start == -1)
continue;
2384 const int64_t break_end_min =
CapSub(breaks[br]->EndMin(), cumul_offset);
2385 const int64_t break_end_max =
CapSub(breaks[br]->EndMax(), cumul_offset);
2388 const int break_is_eligible = solver->AddVariable(0, 1);
2390 absl::StrFormat(
"break_is_eligible(%ld)", br));
2391 const int break_is_not_eligible = solver->AddVariable(0, 1);
2393 solver, break_is_not_eligible,
2394 absl::StrFormat(
"break_is_not_eligible(%ld)", br));
2396 solver->AddLinearConstraint(
2397 1, 1, {{break_is_eligible, 1}, {break_is_not_eligible, 1}});
2398 const int positive_ct = solver->AddLinearConstraint(
2399 min_break_duration, std::numeric_limits<int64_t>::max(),
2400 {{lp_break.end, 1}, {lp_break.start, -1}});
2401 solver->SetEnforcementLiteral(positive_ct, break_is_eligible);
2402 const int negative_ct = solver->AddLinearConstraint(
2403 std::numeric_limits<int64_t>::min(), min_break_duration - 1,
2404 {{lp_break.end, 1}, {lp_break.start, -1}});
2405 solver->SetEnforcementLiteral(negative_ct, break_is_not_eligible);
2412 const int break_cover = solver->AddVariable(
2413 CapAdd(std::min(vehicle_start_min, break_end_min), limit),
2414 CapAdd(std::max(vehicle_start_min, break_end_max), limit));
2416 absl::StrFormat(
"break_cover(%ld)", br));
2417 const int limit_cover_ct = solver->AddLinearConstraint(
2418 limit, limit, {{break_cover, 1}, {lp_break.end, -1}});
2419 solver->SetEnforcementLiteral(limit_cover_ct, break_is_eligible);
2420 const int empty_cover_ct = solver->AddLinearConstraint(
2421 CapAdd(vehicle_start_min, limit),
CapAdd(vehicle_start_min, limit),
2422 {{break_cover, 1}});
2423 solver->SetEnforcementLiteral(empty_cover_ct, break_is_not_eligible);
2426 solver->AddVariable(
CapAdd(vehicle_start_min, limit),
2427 std::numeric_limits<int64_t>::max());
2429 solver->AddMaximumConstraint(cover, {previous_cover, break_cover});
2432 const int route_end_is_not_covered = solver->AddReifiedLinearConstraint(
2433 1, std::numeric_limits<int64_t>::max(),
2434 {{lp_cumuls.back(), 1}, {previous_cover, -1}});
2435 const int break_start_cover_ct = solver->AddLinearConstraint(
2436 0, std::numeric_limits<int64_t>::max(),
2437 {{previous_cover, 1}, {lp_break.start, -1}});
2438 solver->SetEnforcementLiteral(break_start_cover_ct,
2439 route_end_is_not_covered);
2441 previous_cover = cover;
2443 solver->AddLinearConstraint(0, std::numeric_limits<int64_t>::max(),
2444 {{previous_cover, 1}, {lp_cumuls.back(), -1}});
2451bool AllValuesContainedExcept(
const IntVar& var, absl::Span<const int> values,
2452 const absl::flat_hash_set<int>& ignored_values) {
2453 for (
int val : values) {
2454 if (!ignored_values.contains(val) && !var.Contains(val))
return false;
2460bool DimensionCumulOptimizerCore::SetGlobalConstraints(
2461 const std::function<int64_t(int64_t)>& next_accessor, int64_t cumul_offset,
2462 bool optimize_costs,
bool optimize_resource_assignment,
2466 const int64_t global_span_coeff = dimension_->global_span_cost_coefficient();
2467 if (optimize_costs && global_span_coeff > 0) {
2469 const int global_span_var = solver->CreateNewPositiveVariable();
2471 solver->AddLinearConstraint(
2473 {{global_span_var, 1}, {max_end_cumul_, -1}, {min_start_cumul_, 1}});
2478 solver->SetObjectiveCoefficient(global_span_var, global_span_coeff);
2482 for (
const auto [first_node, second_node, offset, performed_constraint] :
2483 dimension_->GetNodePrecedences()) {
2484 const int first_cumul_var = index_to_cumul_variable_[first_node];
2485 const int second_cumul_var = index_to_cumul_variable_[second_node];
2486 switch (RoutingDimension::GetPrecedenceStatus(
2487 first_cumul_var < 0, second_cumul_var < 0, performed_constraint)) {
2488 case RoutingDimension::PrecedenceStatus::kActive:
2490 case RoutingDimension::PrecedenceStatus::kInactive:
2492 case RoutingDimension::PrecedenceStatus::kInvalid:
2495 DCHECK_NE(first_cumul_var, second_cumul_var)
2496 <<
"Dimension " << dimension_->name()
2497 <<
" has a self-precedence on node " << first_node <<
".";
2500 const int ct = solver->CreateNewConstraint(
2501 offset, std::numeric_limits<int64_t>::max());
2502 solver->SetCoefficient(ct, second_cumul_var, 1);
2503 solver->SetCoefficient(ct, first_cumul_var, -1);
2506 if (optimize_resource_assignment &&
2507 !SetGlobalConstraintsForResourceAssignment(next_accessor, cumul_offset,
2514bool DimensionCumulOptimizerCore::SetGlobalConstraintsForResourceAssignment(
2515 const std::function<int64_t(int64_t)>& next_accessor, int64_t cumul_offset,
2517 if (!solver->IsCPSATSolver()) {
2523 using RCIndex = RoutingModel::ResourceClassIndex;
2524 const RoutingModel& model = *dimension_->model();
2525 const int num_vehicles = model.vehicles();
2526 const auto& resource_groups = model.GetResourceGroups();
2527 for (
int rg_index : model.GetDimensionResourceGroupIndices(dimension_)) {
2539 const ResourceGroup& resource_group = *resource_groups[rg_index];
2540 DCHECK(!resource_group.GetVehiclesRequiringAResource().empty());
2543 int num_required_resources = 0;
2546 std::vector<int> vehicle_constraints(model.vehicles(), kNoConstraint);
2547 const int num_resource_classes = resource_group.GetResourceClassesCount();
2548 std::vector<absl::flat_hash_set<int>>& ignored_resources_per_class =
2549 resource_class_ignored_resources_per_group_[rg_index];
2550 ignored_resources_per_class.assign(num_resource_classes, {});
2551 for (
int v : resource_group.GetVehiclesRequiringAResource()) {
2552 const IntVar& resource_var = *model.ResourceVar(v, rg_index);
2553 if (model.IsEnd(next_accessor(model.Start(v))) &&
2554 !model.IsVehicleUsedWhenEmpty(v)) {
2555 if (resource_var.Bound() && resource_var.Value() >= 0) {
2563 if (resource_var.Bound()) {
2564 const int resource_index = resource_var.Value();
2565 if (resource_index < 0) {
2569 ignored_resources_per_class
2570 [resource_group.GetResourceClassIndex(resource_index).value()]
2571 .insert(resource_index);
2573 const int start_index = index_to_cumul_variable_[model.Start(v)];
2574 const int end_index = index_to_cumul_variable_[model.End(v)];
2575 if (!TightenStartEndVariableBoundsWithResource(
2576 *dimension_, resource_group.GetResource(resource_index),
2577 GetVariableBounds(start_index, *solver), start_index,
2578 GetVariableBounds(end_index, *solver), end_index, cumul_offset,
2584 num_required_resources++;
2585 vehicle_constraints[v] = solver->CreateNewConstraint(1, 1);
2590 std::vector<int> resource_class_cstrs(num_resource_classes, kNoConstraint);
2591 int num_available_resources = 0;
2592 for (RCIndex rc_index(0); rc_index < num_resource_classes; rc_index++) {
2593 const ResourceGroup::Attributes& attributes =
2594 resource_group.GetDimensionAttributesForClass(dimension_, rc_index);
2595 if (attributes.start_domain().Max() < cumul_offset ||
2596 attributes.end_domain().Max() < cumul_offset) {
2602 const int rc = rc_index.value();
2603 const int num_available_class_resources =
2604 resource_group.GetResourceIndicesInClass(rc_index).size() -
2605 ignored_resources_per_class[rc].size();
2606 DCHECK_GE(num_available_class_resources, 0);
2607 if (num_available_class_resources > 0) {
2608 num_available_resources += num_available_class_resources;
2609 resource_class_cstrs[rc] =
2610 solver->CreateNewConstraint(0, num_available_class_resources);
2614 if (num_required_resources > num_available_resources) {
2620 std::vector<int>& resource_class_to_vehicle_assignment_vars =
2621 resource_class_to_vehicle_assignment_variables_per_group_[rg_index];
2622 resource_class_to_vehicle_assignment_vars.assign(
2623 num_resource_classes * num_vehicles, -1);
2629 DCHECK_EQ(resource_group.Index(), rg_index);
2630 for (
int v : resource_group.GetVehiclesRequiringAResource()) {
2631 if (vehicle_constraints[v] == kNoConstraint)
continue;
2632 IntVar*
const resource_var = model.ResourceVar(v, rg_index);
2633 std::unique_ptr<IntVarIterator> it(
2634 resource_var->MakeDomainIterator(
false));
2635 std::vector<bool> resource_class_considered(num_resource_classes,
false);
2636 for (
int r : InitAndGetValues(it.get())) {
2637 if (r < 0)
continue;
2638 const RCIndex rc_index = resource_group.GetResourceClassIndex(r);
2639 const int rc = rc_index.value();
2640 if (resource_class_considered[rc]) {
2643 resource_class_considered[rc] =
true;
2644 if (resource_class_cstrs[rc] == kNoConstraint)
continue;
2650 DCHECK(AllValuesContainedExcept(
2651 *resource_var, resource_group.GetResourceIndicesInClass(rc_index),
2652 ignored_resources_per_class[rc]))
2654 resource_group.GetResourceIndicesInClass(rc_index),
2655 resource_var->Min(), resource_var->Max());
2657 const int assign_rc_to_v = solver->AddVariable(0, 1);
2659 solver, assign_rc_to_v,
2660 absl::StrFormat(
"assign_rc_to_v(%ld, %ld)", rc, v));
2661 resource_class_to_vehicle_assignment_vars[rc * num_vehicles + v] =
2666 DCHECK_LT(vehicle_constraints[v], resource_class_cstrs[rc]);
2667 solver->SetCoefficient(vehicle_constraints[v], assign_rc_to_v, 1);
2668 solver->SetCoefficient(resource_class_cstrs[rc], assign_rc_to_v, 1);
2670 const auto& add_domain_constraint =
2671 [&solver, cumul_offset, assign_rc_to_v](
const Domain& domain,
2672 int cumul_variable) {
2676 ClosedInterval cumul_bounds;
2677 if (!GetDomainOffsetBounds(domain, cumul_offset, &cumul_bounds)) {
2679 solver->SetVariableBounds(assign_rc_to_v, 0, 0);
2682 const int cumul_constraint = solver->AddLinearConstraint(
2683 cumul_bounds.start, cumul_bounds.end, {{cumul_variable, 1}});
2684 solver->SetEnforcementLiteral(cumul_constraint, assign_rc_to_v);
2686 const ResourceGroup::Attributes& attributes =
2687 resource_group.GetDimensionAttributesForClass(dimension_, rc_index);
2688 add_domain_constraint(attributes.start_domain(),
2689 index_to_cumul_variable_[model.Start(v)]);
2690 add_domain_constraint(attributes.end_domain(),
2691 index_to_cumul_variable_[model.End(v)]);
2698#undef SET_DEBUG_VARIABLE_NAME
2700void DimensionCumulOptimizerCore::SetValuesFromLP(
2701 absl::Span<const int> lp_variables, int64_t offset,
2702 RoutingLinearSolverWrapper* solver, std::vector<int64_t>* lp_values)
const {
2703 if (lp_values ==
nullptr)
return;
2704 lp_values->assign(lp_variables.size(), std::numeric_limits<int64_t>::min());
2705 for (
int i = 0;
i < lp_variables.size();
i++) {
2706 const int lp_var = lp_variables[
i];
2707 if (lp_var < 0)
continue;
2708 const double lp_value_double = solver->GetValue(lp_var);
2709 const int64_t lp_value_int64 =
2710 (lp_value_double >= std::numeric_limits<int64_t>::max())
2711 ? std::numeric_limits<int64_t>::max()
2712 : MathUtil::FastInt64Round(lp_value_double);
2713 (*lp_values)[
i] =
CapAdd(lp_value_int64, offset);
2717void DimensionCumulOptimizerCore::SetResourceIndices(
2718 RoutingLinearSolverWrapper* solver,
2719 std::vector<std::vector<int>>* resource_indices_per_group)
const {
2720 if (resource_indices_per_group ==
nullptr ||
2721 resource_class_to_vehicle_assignment_variables_per_group_.empty()) {
2724 using RCIndex = RoutingModel::ResourceClassIndex;
2725 const RoutingModel& model = *dimension_->model();
2726 const int num_vehicles = model.vehicles();
2727 DCHECK(!model.GetDimensionResourceGroupIndices(dimension_).empty());
2728 const auto& resource_groups = model.GetResourceGroups();
2729 resource_indices_per_group->resize(resource_groups.size());
2730 for (
int rg_index : model.GetDimensionResourceGroupIndices(dimension_)) {
2731 const ResourceGroup& resource_group = *resource_groups[rg_index];
2732 DCHECK(!resource_group.GetVehiclesRequiringAResource().empty());
2735 resource_indices_per_class =
2736 resource_group.GetResourceIndicesPerClass();
2737 const int num_resource_classes = resource_group.GetResourceClassesCount();
2738 std::vector<int> current_resource_pos_for_class(num_resource_classes, 0);
2739 std::vector<int>& resource_indices =
2740 resource_indices_per_group->at(rg_index);
2741 resource_indices.assign(num_vehicles, -1);
2743 const std::vector<int>& resource_class_to_vehicle_assignment_vars =
2744 resource_class_to_vehicle_assignment_variables_per_group_[rg_index];
2745 DCHECK_EQ(resource_class_to_vehicle_assignment_vars.size(),
2746 num_resource_classes * num_vehicles);
2747 const std::vector<absl::flat_hash_set<int>>& ignored_resources_per_class =
2748 resource_class_ignored_resources_per_group_[rg_index];
2749 DCHECK_EQ(ignored_resources_per_class.size(), num_resource_classes);
2750 for (
int v : resource_group.GetVehiclesRequiringAResource()) {
2751 const IntVar& resource_var = *model.ResourceVar(v, rg_index);
2752 if (resource_var.Bound()) {
2753 resource_indices[v] = resource_var.Value();
2756 for (
int rc = 0; rc < num_resource_classes; rc++) {
2757 const int assignment_var =
2758 resource_class_to_vehicle_assignment_vars[rc * num_vehicles + v];
2759 if (assignment_var >= 0 && solver->GetValue(assignment_var) == 1) {
2761 const std::vector<int>& class_resource_indices =
2762 resource_indices_per_class[RCIndex(rc)];
2763 int& pos = current_resource_pos_for_class[rc];
2764 while (ignored_resources_per_class[rc].contains(
2765 class_resource_indices[pos])) {
2767 DCHECK_LT(pos, class_resource_indices.size());
2769 resource_indices[v] = class_resource_indices[pos++];
2781 RoutingSearchParameters::SchedulingSolver solver_type)
2784 !
dimension->GetNodePrecedences().empty()) {
2785 switch (solver_type) {
2786 case RoutingSearchParameters::SCHEDULING_GLOP: {
2787 solver_ = std::make_unique<RoutingGlopWrapper>(
2789 ->GetDimensionResourceGroupIndices(
dimension)
2791 GetGlopParametersForGlobalLP());
2794 case RoutingSearchParameters::SCHEDULING_CP_SAT: {
2795 solver_ = std::make_unique<RoutingCPSatWrapper>();
2799 LOG(DFATAL) <<
"Unrecognized solver type: " << solver_type;
2805 const std::function<int64_t(int64_t)>& next_accessor,
2806 int64_t* optimal_cost_without_transits) {
2807 return optimizer_core_.Optimize(next_accessor, {}, solver_.get(),
nullptr,
2809 optimal_cost_without_transits,
nullptr);
2813 const std::function<int64_t(int64_t)>& next_accessor,
2814 const std::vector<RoutingModel::RouteDimensionTravelInfo>&
2815 dimension_travel_info_per_route,
2816 std::vector<int64_t>* optimal_cumuls, std::vector<int64_t>* optimal_breaks,
2817 std::vector<std::vector<int>>* optimal_resource_indices) {
2818 return optimizer_core_.Optimize(next_accessor,
2819 dimension_travel_info_per_route,
2820 solver_.get(), optimal_cumuls, optimal_breaks,
2821 optimal_resource_indices,
nullptr,
nullptr);
2825 const std::function<int64_t(int64_t)>& next_accessor,
2826 const std::vector<RoutingModel::RouteDimensionTravelInfo>&
2827 dimension_travel_info_per_route,
2828 std::vector<int64_t>* packed_cumuls, std::vector<int64_t>* packed_breaks) {
2829 return optimizer_core_.OptimizeAndPack(
2830 next_accessor, dimension_travel_info_per_route, solver_.get(),
2831 packed_cumuls, packed_breaks);
2836template <
typename T>
2837void MoveValuesToIndicesFrom(std::vector<T>* out_values,
2838 absl::Span<const int> out_indices_to_evaluate,
2839 const std::function<
int(
int)>& index_evaluator,
2840 std::vector<T>& values_to_copy) {
2841 if (out_values ==
nullptr) {
2842 DCHECK(values_to_copy.empty());
2845 DCHECK_EQ(values_to_copy.size(), out_indices_to_evaluate.size());
2846 for (
int i = 0; i < out_indices_to_evaluate.size(); i++) {
2847 const int output_index = index_evaluator(out_indices_to_evaluate[i]);
2848 DCHECK_LT(output_index, out_values->size());
2849 out_values->at(output_index) = std::move(values_to_copy[i]);
2856 int v,
double solve_duration_ratio,
2857 const RoutingModel::ResourceGroup& resource_group,
2859 absl::flat_hash_set<int>>&
2860 ignored_resources_per_class,
2861 const std::function<int64_t(int64_t)>& next_accessor,
2862 const std::function<int64_t(int64_t, int64_t)>& transit_accessor,
2865 std::vector<int64_t>* assignment_costs,
2866 std::vector<std::vector<int64_t>>* cumul_values,
2867 std::vector<std::vector<int64_t>>* break_values) {
2868 DCHECK_GT(solve_duration_ratio, 0);
2869 DCHECK_LE(solve_duration_ratio, 1);
2870 DCHECK(lp_optimizer !=
nullptr);
2871 DCHECK(mp_optimizer !=
nullptr);
2872 DCHECK_NE(assignment_costs,
nullptr);
2873 assignment_costs->clear();
2874 ClearIfNonNull(cumul_values);
2875 ClearIfNonNull(break_values);
2877 const RoutingDimension* dimension = lp_optimizer->
dimension();
2878 DCHECK_EQ(dimension, mp_optimizer->
dimension());
2879 RoutingModel*
const model = dimension->model();
2880 if (!resource_group.VehicleRequiresAResource(v) ||
2881 (!model->IsVehicleUsedWhenEmpty(v) &&
2882 next_accessor(model->Start(v)) == model->End(v))) {
2885 if (model->CheckLimit()) {
2890 IntVar*
const resource_var = model->ResourceVar(v, resource_group.Index());
2891 const int num_resource_classes = resource_group.GetResourceClassesCount();
2892 std::vector<int> considered_resource_indices;
2893 considered_resource_indices.reserve(
2894 std::min<int>(resource_var->
Size(), num_resource_classes));
2895 std::vector<bool> resource_class_considered(num_resource_classes,
false);
2901 const RoutingModel::ResourceClassIndex resource_class =
2902 resource_group.GetResourceClassIndex(res);
2903 const int rc_index = resource_class.value();
2904 const absl::flat_hash_set<int>& ignored_resources =
2905 ignored_resources_per_class[resource_class];
2906 if (resource_class_considered[rc_index] ||
2907 ignored_resources.contains(res)) {
2910 resource_class_considered[rc_index] =
true;
2915 DCHECK(AllValuesContainedExcept(
2916 *resource_var, resource_group.GetResourceIndicesInClass(resource_class),
2917 ignored_resources));
2918 considered_resource_indices.push_back(res);
2920 const bool use_mp_optimizer =
2921 dimension->HasBreakConstraints() &&
2922 !dimension->GetBreakIntervalsOfVehicle(v).empty();
2924 use_mp_optimizer ? mp_optimizer : lp_optimizer;
2926 const std::vector<ResourceGroup::Resource>& resources =
2927 resource_group.GetResources();
2928 std::vector<int64_t> considered_assignment_costs;
2929 std::vector<std::vector<int64_t>> considered_cumul_values;
2930 std::vector<std::vector<int64_t>> considered_break_values;
2931 std::vector<DimensionSchedulingStatus> statuses =
2933 v, solve_duration_ratio, next_accessor, transit_accessor, resources,
2934 considered_resource_indices, optimize_vehicle_costs,
2935 &considered_assignment_costs,
2936 cumul_values ==
nullptr ?
nullptr : &considered_cumul_values,
2937 break_values ==
nullptr ?
nullptr : &considered_break_values);
2939 if (statuses.empty() ||
2940 (statuses.size() == 1 &&
2946 assignment_costs->resize(num_resource_classes, -1);
2947 if (cumul_values !=
nullptr) {
2948 cumul_values->resize(num_resource_classes, {});
2950 if (break_values !=
nullptr) {
2951 break_values->resize(num_resource_classes, {});
2954 const auto resource_to_class_index = [&resource_group](
int resource_index) {
2955 return resource_group.GetResourceClassIndex(resource_index).value();
2957 MoveValuesToIndicesFrom(assignment_costs, considered_resource_indices,
2958 resource_to_class_index, considered_assignment_costs);
2959 MoveValuesToIndicesFrom(cumul_values, considered_resource_indices,
2960 resource_to_class_index, considered_cumul_values);
2961 MoveValuesToIndicesFrom(break_values, considered_resource_indices,
2962 resource_to_class_index, considered_break_values);
2964 if (use_mp_optimizer) {
2968 return absl::c_any_of(*assignment_costs,
2969 [](int64_t cost) {
return cost >= 0; });
2972 std::vector<int> mp_resource_indices;
2973 DCHECK_EQ(statuses.size(), considered_resource_indices.size());
2974 for (
int i = 0; i < considered_resource_indices.size(); i++) {
2976 mp_resource_indices.push_back(considered_resource_indices[i]);
2980 std::vector<int64_t> mp_assignment_costs;
2981 std::vector<std::vector<int64_t>> mp_cumul_values;
2982 std::vector<std::vector<int64_t>> mp_break_values;
2984 v, solve_duration_ratio, next_accessor, transit_accessor, resources,
2985 mp_resource_indices, optimize_vehicle_costs, &mp_assignment_costs,
2986 cumul_values ==
nullptr ?
nullptr : &mp_cumul_values,
2987 break_values ==
nullptr ?
nullptr : &mp_break_values);
2988 if (!mp_resource_indices.empty() && mp_assignment_costs.empty()) {
2993 MoveValuesToIndicesFrom(assignment_costs, mp_resource_indices,
2994 resource_to_class_index, mp_assignment_costs);
2995 MoveValuesToIndicesFrom(cumul_values, mp_resource_indices,
2996 resource_to_class_index, mp_cumul_values);
2997 MoveValuesToIndicesFrom(break_values, mp_resource_indices,
2998 resource_to_class_index, mp_break_values);
3000 return absl::c_any_of(*assignment_costs,
3001 [](int64_t cost) {
return cost >= 0; });
3005 absl::Span<const int> vehicles,
3008 resource_indices_per_class,
3010 absl::flat_hash_set<int>>&
3011 ignored_resources_per_class,
3012 std::function<
const std::vector<int64_t>*(
int)>
3013 vehicle_to_resource_class_assignment_costs,
3014 std::vector<int>* resource_indices) {
3015 const int total_num_resources = absl::c_accumulate(
3016 resource_indices_per_class, 0,
3017 [](
int acc, absl::Span<const int> res) {
return acc + res.size(); });
3018 DCHECK_GE(total_num_resources, 1);
3019 const int num_ignored_resources =
3020 absl::c_accumulate(ignored_resources_per_class, 0,
3021 [](
int acc,
const absl::flat_hash_set<int>& res) {
3022 return acc + res.size();
3024 const int num_resources = total_num_resources - num_ignored_resources;
3025 const int num_vehicles = vehicles.size();
3026 int num_total_vehicles = -1;
3027 if (resource_indices !=
nullptr) {
3028 num_total_vehicles = resource_indices->size();
3031 resource_indices->clear();
3032 DCHECK_GE(num_total_vehicles, num_vehicles);
3033 for (
int v : vehicles) {
3035 DCHECK_LT(v, num_total_vehicles);
3043 const int num_resource_classes = resource_indices_per_class.size();
3044 std::vector<const std::vector<int64_t>*> vi_to_rc_cost(num_vehicles);
3045 int num_vehicles_to_assign = 0;
3046 for (
int i = 0; i < num_vehicles; ++i) {
3047 vi_to_rc_cost[i] = vehicle_to_resource_class_assignment_costs(vehicles[i]);
3048 if (!vi_to_rc_cost[i]->empty()) {
3049 DCHECK_EQ(vi_to_rc_cost[i]->size(), num_resource_classes);
3050 ++num_vehicles_to_assign;
3053 if (num_vehicles_to_assign > num_resources) {
3054 VLOG(3) <<
"Less resources (" << num_resources <<
") than the vehicles"
3055 <<
" requiring one (" << num_vehicles_to_assign <<
")";
3063 for (
int i = 0; i < num_vehicles; ++i) {
3064 if (!vi_to_rc_cost[i]->empty() &&
3065 *absl::c_max_element(*vi_to_rc_cost[i]) < 0) {
3066 VLOG(3) <<
"Vehicle #" << vehicles[i] <<
" has no feasible resource";
3074 int64_t max_arc_cost = 0;
3075 for (
const std::vector<int64_t>* costs : vi_to_rc_cost) {
3076 if (costs->empty())
continue;
3077 max_arc_cost = std::max(max_arc_cost, *absl::c_max_element(*costs));
3082 const int real_num_nodes = 4 + num_vehicles + num_resource_classes;
3083 const int64_t max_acceptable_arc_cost =
kint64max / (4 * real_num_nodes) - 1;
3086 int cost_right_shift = 0;
3087 while ((max_arc_cost >> cost_right_shift) > max_acceptable_arc_cost) {
3097 2 + num_vehicles + num_resource_classes,
3098 num_vehicles + num_vehicles * num_resource_classes +
3099 num_resource_classes);
3101 const int source_index = num_vehicles + num_resource_classes;
3102 const int sink_index = source_index + 1;
3103 const auto flow_rc_index = [num_vehicles](
int rc) {
3104 return num_vehicles + rc;
3109 if (resource_indices !=
nullptr) {
3110 vehicle_to_rc_arc_index =
3113 for (
int vi = 0; vi < num_vehicles; ++vi) {
3114 const std::vector<int64_t>& assignment_costs = *vi_to_rc_cost[vi];
3115 if (assignment_costs.empty())
continue;
3121 for (
int rc = 0; rc < num_resource_classes; rc++) {
3122 const int64_t assignment_cost = assignment_costs[rc];
3123 if (assignment_cost < 0)
continue;
3125 vi, flow_rc_index(rc), 1, assignment_cost >> cost_right_shift);
3126 if (resource_indices !=
nullptr) {
3127 vehicle_to_rc_arc_index[vi][rc] = arc;
3134 using RCIndex = RoutingModel::ResourceClassIndex;
3135 for (
int rc = 0; rc < num_resource_classes; rc++) {
3136 const RCIndex rci(rc);
3137 const int num_available_res = resource_indices_per_class[rci].size() -
3138 ignored_resources_per_class[rci].size();
3139 DCHECK_GE(num_available_res, 0);
3141 num_available_res, 0);
3150 VLOG(3) <<
"Non-OPTIMAL flow result";
3154 if (resource_indices !=
nullptr) {
3156 resource_indices->assign(num_total_vehicles, -1);
3157 std::vector<int> current_resource_pos_for_class(num_resource_classes, 0);
3158 for (
int vi = 0; vi < num_vehicles; ++vi) {
3159 if (vi_to_rc_cost[vi]->empty()) {
3163 for (
int rc = 0; rc < num_resource_classes; rc++) {
3164 const ArcIndex arc = vehicle_to_rc_arc_index[vi][rc];
3165 if (arc >= 0 && flow.
Flow(arc) > 0) {
3166 const RCIndex rci(rc);
3167 const std::vector<int>& class_resource_indices =
3168 resource_indices_per_class[rci];
3169 const absl::flat_hash_set<int>& ignored_resources =
3170 ignored_resources_per_class[rci];
3171 int& pos = current_resource_pos_for_class[rc];
3172 DCHECK_LT(pos, class_resource_indices.size());
3173 while (ignored_resources.contains(class_resource_indices[pos])) {
3175 DCHECK_LT(pos, class_resource_indices.size());
3177 resource_indices->at(vehicles[vi]) = class_resource_indices[pos++];
3185 DCHECK_LE(cost,
kint64max >> cost_right_shift);
3186 return cost << cost_right_shift;
3190 if (number ==
kint64min)
return "-infty";
3191 if (number ==
kint64max)
return "+infty";
3192 return std::to_string(number);
3196 const ::google::protobuf::RepeatedField<int64_t>* domain) {
3197 if (domain->size() > 2 && domain->size() % 2 == 0) {
3198 std::string s =
"∈ ";
3199 for (
int i = 0; i < domain->size(); i += 2) {
3200 s += absl::StrFormat(
"[%s, %s]",
Int64ToStr(domain->Get(i)),
3202 if (i < domain->size() - 2) s +=
" ∪ ";
3205 }
else if (domain->size() == 2) {
3206 if (domain->Get(0) == domain->Get(1)) {
3207 return absl::StrFormat(
"= %s",
Int64ToStr(domain->Get(0)));
3208 }
else if (domain->Get(0) == 0 && domain->Get(1) == 1) {
3210 }
else if (domain->Get(0) == std::numeric_limits<int64_t>::min() &&
3211 domain->Get(1) == std::numeric_limits<int64_t>::max()) {
3213 }
else if (domain->Get(0) == std::numeric_limits<int64_t>::min()) {
3214 return absl::StrFormat(
"≤ %s",
Int64ToStr(domain->Get(1)));
3215 }
else if (domain->Get(1) == std::numeric_limits<int64_t>::max()) {
3216 return absl::StrFormat(
"≥ %s",
Int64ToStr(domain->Get(0)));
3218 return absl::StrFormat(
"∈ [%ls, %s]",
Int64ToStr(domain->Get(0)),
3220 }
else if (domain->size() == 1) {
3221 return absl::StrFormat(
"= %s",
Int64ToStr(domain->Get(0)));
3223 return absl::StrFormat(
"∈ Unknown domain (size=%ld)", domain->size());
3228 std::pair<sat::IntegerVariableProto, int>& variable_pair,
3229 const sat::CpSolverResponse& response_) {
3231 sat::IntegerVariableProto& variable = variable_pair.first;
3232 const int index = variable_pair.second;
3233 if (response_.IsInitialized() && variable.IsInitialized() &&
3234 (response_.status() == sat::CpSolverStatus::OPTIMAL ||
3235 response_.status() == sat::CpSolverStatus::FEASIBLE)) {
3236 const double lp_value_double = response_.solution(index);
3237 const int64_t lp_value_int64 =
3238 (lp_value_double >= std::numeric_limits<int64_t>::max())
3239 ? std::numeric_limits<int64_t>::max()
3250 const sat::CpModelProto& model_,
3251 bool show_enforcement =
true) {
3253 if (constraint.has_linear()) {
3254 const auto& linear = constraint.linear();
3255 for (
int j = 0; j < linear.vars().size(); ++j) {
3256 const std::string sign = linear.coeffs(j) > 0 ?
"+" :
"-";
3257 const std::string mult =
3258 std::abs(linear.coeffs(j)) != 1
3259 ? std::to_string(std::abs(linear.coeffs(j))) +
" * "
3261 if (j > 0 || sign !=
"+") s += sign +
" ";
3262 s += mult + model_.variables(linear.vars(j)).name() +
" ";
3267 if (show_enforcement) {
3268 for (
int j = 0; j < constraint.enforcement_literal_size(); ++j) {
3269 s += (j == 0) ?
"\t if " :
" and ";
3270 s += model_.variables(constraint.enforcement_literal(j)).name();
3280 absl::flat_hash_map<std::string, std::pair<sat::IntegerVariableProto, int>>&
3282 absl::flat_hash_map<std::string, std::vector<int>>& variable_instances,
3283 absl::flat_hash_map<std::string, absl::flat_hash_set<std::string>>&
3285 const sat::CpSolverResponse& response_, absl::string_view variable,
3286 std::string prefix =
"") {
3287 if (variable.empty()) {
3289 const auto& childs = variable_childs[
""];
3290 for (
const std::string& child : childs) {
3293 response_, child, prefix) +
3299 const auto& instances = variable_instances[variable];
3300 std::string variable_display(variable);
3301 std::size_t bracket_pos = variable.find_last_of(
')');
3302 if (bracket_pos != std::string::npos) {
3303 variable_display = variable.substr(bracket_pos + 1);
3305 std::string s = variable_display +
" | ";
3306 prefix += std::string(variable_display.length(),
' ') +
" | ";
3307 for (
int i = 0; i < instances.size(); ++i) {
3308 const std::string instance_name =
3309 absl::StrFormat(
"%s(%ld)", variable, instances[i]);
3310 if (i > 0) s += prefix;
3311 s += absl::StrFormat(
"%ld: %s", instances[i],
3315 const auto& childs = variable_childs[instance_name];
3316 for (
const std::string& child : childs) {
3317 s +=
"\n" + prefix +
"| ";
3319 response_, child, prefix +
"| ");
3321 if (childs.empty()) s +=
"\n";
3328 std::vector<std::vector<std::string>> constraints_apart;
3329 constraints_apart.push_back(
3330 {
"compression_cost",
"travel_compression_absolute"});
3335 absl::flat_hash_map<std::string, std::vector<int>> variable_instances;
3339 absl::flat_hash_map<std::string, absl::flat_hash_set<std::string>>
3342 absl::flat_hash_map<std::string, std::pair<sat::IntegerVariableProto, int>>
3344 variable_children[
""] = {};
3346 const int num_constraints = model_.constraints_size();
3347 const int num_variables = model_.variables_size();
3348 int num_binary_variables = 0;
3349 for (
int i = 0; i < num_variables; ++i) {
3350 const auto& variable = model_.variables(i);
3351 const auto& name = variable.name();
3352 const int pos_bracket = name.find_last_of(
'(');
3353 if (pos_bracket != std::string::npos) {
3354 const std::string lemma = name.substr(0, pos_bracket);
3355 const int pos_closing_bracket = name.find_last_of(
')');
3356 CHECK_NE(pos_closing_bracket, std::string::npos);
3358 std::stoi(name.substr(pos_bracket + 1, pos_closing_bracket));
3359 std::vector<int>* instances =
gtl::FindOrNull(variable_instances, lemma);
3360 if (instances !=
nullptr) {
3361 instances->push_back(index);
3363 variable_instances[lemma] = {index};
3365 variable_children[name] = {};
3367 std::string parent =
"";
3368 const int pos_parent_closing_bracket = lemma.find_last_of(
')');
3369 if (pos_parent_closing_bracket != std::string::npos) {
3370 parent = lemma.substr(0, pos_parent_closing_bracket + 1);
3372 variable_children[parent].emplace(lemma);
3373 variables[name] = std::make_pair(variable, i);
3374 if (variable.domain(0) == 0 & variable.domain(1) == 1) {
3375 ++num_binary_variables;
3386 absl::flat_hash_map<std::string, std::vector<sat::ConstraintProto>>
3388 absl::flat_hash_map<std::vector<std::string>,
3389 std::vector<sat::ConstraintProto>>
3391 for (
int i = 0; i < num_constraints; ++i) {
3392 const auto& constraint = model_.constraints(i);
3393 std::string enforcement =
"";
3394 if (constraint.enforcement_literal_size() == 1) {
3395 enforcement = model_.variables(constraint.enforcement_literal(0)).name();
3396 }
else if (constraint.enforcement_literal_size() > 1) {
3397 enforcement =
"multiple";
3399 if (constraint.has_linear()) {
3400 const auto& linear = constraint.linear();
3401 std::vector<std::string> key;
3402 for (
int j = 0; j < linear.vars().size(); ++j) {
3403 std::string var_name = model_.variables(linear.vars(j)).name();
3404 std::string lemma = var_name.substr(0, var_name.find_last_of(
'('));
3405 key.push_back(lemma);
3408 if (constraint_group !=
nullptr) {
3409 constraint_group->push_back(constraint);
3411 constraint_groups[key] = {constraint};
3415 auto* constraints_enforced =
gtl::FindOrNull(constraints, enforcement);
3416 if (constraints_enforced !=
nullptr) {
3417 constraints[enforcement].push_back(constraint);
3419 constraints[enforcement] = {constraint};
3423 const std::string prefix_constraint =
" • ";
3424 std::string s =
"Using RoutingCPSatWrapper.\n";
3427 for (
int i = 0; i < objective_coefficients_.size(); ++i) {
3428 double coeff = objective_coefficients_[i];
3430 s += absl::StrFormat(
" | %f * %s\n", coeff, model_.variables(i).name());
3434 s += absl::StrFormat(
"\nVariables %d (%d Binary - %d Non Binary)\n",
3435 num_variables, num_binary_variables,
3436 num_variables - num_binary_variables);
3438 response_,
"",
" | ");
3439 s += absl::StrFormat(
"\n\nConstraints (%d)\n", num_constraints);
3442 s +=
"\n- Not enforced\n";
3443 bool at_least_one_not_enforced =
false;
3444 for (
const auto& pair : constraint_groups) {
3445 if (!std::count(constraints_apart.begin(), constraints_apart.end(),
3447 for (
const auto& constraint : pair.second) {
3450 at_least_one_not_enforced =
true;
3454 if (!at_least_one_not_enforced) {
3455 s += prefix_constraint +
"None\n";
3459 s +=
"\n- Single enforcement\n";
3460 bool at_least_one_single_enforced =
false;
3461 for (
const auto& pair : variable_instances) {
3462 const std::string lemma = pair.first;
3463 bool found_one_constraint =
false;
3464 std::string prefix =
"";
3465 for (
int instance : pair.second) {
3466 const std::string enforcement =
3467 absl::StrFormat(
"%s(%d)", lemma, instance);
3468 auto* constraints_enforced =
gtl::FindOrNull(constraints, enforcement);
3469 std::string prefix_instance =
"";
3470 if (constraints_enforced !=
nullptr) {
3471 at_least_one_single_enforced =
true;
3472 if (!found_one_constraint) {
3473 found_one_constraint =
true;
3474 s += prefix_constraint +
"if " + lemma +
" | ";
3476 std::string(prefix_constraint.size() + 1 + lemma.size(),
' ') +
3481 s += absl::StrFormat(
"%d: | ", instance);
3482 prefix_instance = prefix +
" | ";
3484 for (
const auto& constraint : *constraints_enforced) {
3486 s += prefix_instance;
3494 if (!at_least_one_single_enforced) {
3495 s += prefix_constraint +
"None\n";
3499 s +=
"\n- Multiple enforcement\n";
3500 auto* constraints_multiple_enforced =
3502 if (constraints_multiple_enforced !=
nullptr) {
3503 for (
const auto& constraint : *constraints_multiple_enforced) {
3508 s += prefix_constraint +
"None\n";
3512 s +=
"\n- Set apart\n";
3513 bool at_least_one_apart =
false;
3514 for (
const auto& pair : constraint_groups) {
3515 if (std::count(constraints_apart.begin(), constraints_apart.end(),
3517 for (
const auto& constraint : pair.second) {
3520 at_least_one_apart =
true;
3524 if (!at_least_one_apart) {
3525 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)
DimensionSchedulingStatus ComputeSingleRouteSolutionCostWithoutFixedTransits(int vehicle, double solve_duration_ratio, 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)
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)
DimensionCumulOptimizerCore(const RoutingDimension *dimension, bool use_precedence_propagator)
DimensionSchedulingStatus OptimizeSingleRouteWithResource(int vehicle, double solve_duration_ratio, 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)
DimensionSchedulingStatus OptimizeAndPackSingleRoute(int vehicle, double solve_duration_ratio, 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)
std::vector< DimensionSchedulingStatus > OptimizeSingleRouteWithResources(int vehicle, double solve_duration_ratio, 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, absl::Span< const Resource > resources, absl::Span< const 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)
static Domain AllValues()
NOTE(user): T=bool is not yet supported (the [] operator doesn't work).
const absl::InlinedVector< int64_t, 8 > & x_anchors() const
const absl::InlinedVector< int64_t, 8 > & y_anchors() const
DimensionSchedulingStatus ComputeCumulCostWithoutFixedTransits(const std::function< int64_t(int64_t)> &next_accessor, int64_t *optimal_cost_without_transits)
GlobalDimensionCumulOptimizer(const RoutingDimension *dimension, RoutingSearchParameters::SchedulingSolver solver_type)
GlobalDimensionCumulOptimizer.
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 int64_t Max() const =0
virtual IntVarIterator * MakeDomainIterator(bool reversible) const =0
virtual uint64_t Size() const =0
This method returns the number of values in the domain of the variable.
const RoutingDimension * dimension() const
DimensionSchedulingStatus ComputeRouteSolutionCostWithoutFixedTransits(int vehicle, double solve_duration_ratio, const std::function< int64_t(int64_t)> &next_accessor, const RoutingModel::RouteDimensionTravelInfo *dimension_travel_info, absl::Span< const int64_t > solution_cumul_values, absl::Span< const 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)
DimensionSchedulingStatus ComputePackedRouteCumuls(int vehicle, double solve_duration_ratio, 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)
LocalDimensionCumulOptimizer(const RoutingDimension *dimension, RoutingSearchParameters::SchedulingSolver solver_type)
LocalDimensionCumulOptimizer.
DimensionSchedulingStatus ComputeRouteCumuls(int vehicle, double solve_duration_ratio, 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 ComputeRouteCumulCostWithoutFixedTransits(int vehicle, double solve_duration_ratio, const std::function< int64_t(int64_t)> &next_accessor, const RoutingModel::ResourceGroup::Resource *resource, int64_t *optimal_cost_without_transits)
DimensionSchedulingStatus ComputeRouteCumulsAndCostWithoutFixedTransits(int vehicle, double solve_duration_ratio, 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)
std::vector< DimensionSchedulingStatus > ComputeRouteCumulCostsForResourcesWithoutFixedTransits(int vehicle, double solve_duration_ratio, const std::function< int64_t(int64_t)> &next_accessor, const std::function< int64_t(int64_t, int64_t)> &transit_accessor, absl::Span< const RoutingModel::ResourceGroup::Resource > resources, absl::Span< const 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 ComputeRouteCumulCost(int vehicle, double solve_duration_ratio, const std::function< int64_t(int64_t)> &next_accessor, int64_t *optimal_cost)
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 void SetObjectiveCoefficient(int index, double coefficient)=0
static const int kNoConstraint
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
CostValue OptimalCost() const
void SetNodeSupply(NodeIndex node, FlowQuantity supply)
FlowQuantity Flow(ArcIndex arc) const
ArcIndex AddArcWithCapacityAndUnitCost(NodeIndex tail, NodeIndex head, FlowQuantity capacity, CostValue unit_cost)
const Collection::value_type::second_type * FindOrNull(const Collection &collection, const typename Collection::value_type::first_type &key)
double FindBestScalingAndComputeErrors(absl::Span< const 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)
int64_t ComputeBestVehicleToResourceAssignment(absl::Span< const int > vehicles, const util_intops::StrongVector< RoutingModel::ResourceClassIndex, std::vector< int > > &resource_indices_per_class, const util_intops::StrongVector< RoutingModel::ResourceClassIndex, absl::flat_hash_set< int > > &ignored_resources_per_class, std::function< const std::vector< int64_t > *(int)> vehicle_to_resource_class_assignment_costs, std::vector< int > *resource_indices)
DimensionSchedulingStatus
@ INFEASIBLE
A solution could not be found.
int64_t CapSub(int64_t x, int64_t y)
std::vector< bool > SlopeAndYInterceptToConvexityRegions(absl::Span< const SlopeAndYIntercept > slope_and_y_intercept)
std::string ProtobufShortDebugString(const P &message)
bool ComputeVehicleToResourceClassAssignmentCosts(int v, double solve_duration_ratio, const RoutingModel::ResourceGroup &resource_group, const util_intops::StrongVector< RoutingModel::ResourceClassIndex, absl::flat_hash_set< int > > &ignored_resources_per_class, const std::function< int64_t(int64_t)> &next_accessor, const std::function< int64_t(int64_t, int64_t)> &transit_accessor, bool optimize_vehicle_costs, LocalDimensionCumulOptimizer *lp_optimizer, LocalDimensionCumulOptimizer *mp_optimizer, std::vector< int64_t > *assignment_costs, std::vector< std::vector< int64_t > > *cumul_values, std::vector< std::vector< int64_t > > *break_values)
int64_t CapProd(int64_t x, int64_t y)
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 > PiecewiseLinearFunctionToSlopeAndYIntercept(const FloatSlopePiecewiseLinearFunction &pwl_function, int index_start, int index_end)
void FillPathEvaluation(absl::Span< const int64_t > path, const RoutingModel::TransitCallback2 &evaluator, std::vector< int64_t > *values)
std::string VariableToString(std::pair< sat::IntegerVariableProto, int > &variable_pair, const sat::CpSolverResponse &response_)
#define SET_DEBUG_VARIABLE_NAME(solver, var, name)
static const int64_t kint64max
static const int64_t kint64min