Google OR-Tools v9.14
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
routing_lp_scheduling.cc
Go to the documentation of this file.
1// Copyright 2010-2025 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <algorithm>
17#include <cmath>
18#include <cstdint>
19#include <cstdlib>
20#include <functional>
21#include <limits>
22#include <memory>
23#include <numeric>
24#include <optional>
25#include <string>
26#include <utility>
27#include <vector>
28
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"
42#include "ortools/base/types.h"
55
56namespace operations_research {
57
58namespace {
59
60// The following sets of parameters give the fastest response time without
61// impacting solutions found negatively.
62glop::GlopParameters GetGlopParametersForLocalLP() {
63 glop::GlopParameters parameters;
64 parameters.set_use_dual_simplex(true);
65 parameters.set_use_preprocessing(false);
66 return parameters;
67}
68
69glop::GlopParameters GetGlopParametersForGlobalLP() {
70 glop::GlopParameters parameters;
71 parameters.set_use_dual_simplex(true);
72 return parameters;
73}
74
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);
80
81 const IntVar& cumul_var = *dimension.CumulVar(node_index);
82 *upper_bound = cumul_var.Max();
83 if (*upper_bound < cumul_offset) {
84 return false;
85 }
86
87 const int64_t first_after_offset =
88 std::max(dimension.GetFirstPossibleGreaterOrEqualValueForNode(
89 node_index, cumul_offset),
90 cumul_var.Min());
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);
94
95 if (*upper_bound == std::numeric_limits<int64_t>::max()) {
96 return true;
97 }
98 *upper_bound = CapSub(*upper_bound, cumul_offset);
99 DCHECK_GE(*upper_bound, *lower_bound);
100 return true;
101}
102
103int64_t GetFirstPossibleValueForCumulWithOffset(
104 const RoutingDimension& dimension, int64_t node_index,
105 int64_t lower_bound_without_offset, int64_t cumul_offset) {
106 return CapSub(
107 dimension.GetFirstPossibleGreaterOrEqualValueForNode(
108 node_index, CapAdd(lower_bound_without_offset, cumul_offset)),
109 cumul_offset);
110}
111
112int64_t GetLastPossibleValueForCumulWithOffset(
113 const RoutingDimension& dimension, int64_t node_index,
114 int64_t upper_bound_without_offset, int64_t cumul_offset) {
115 return CapSub(
116 dimension.GetLastPossibleLessOrEqualValueForNode(
117 node_index, CapAdd(upper_bound_without_offset, cumul_offset)),
118 cumul_offset);
119}
120
121using PDPosition = RoutingModel::PickupDeliveryPosition;
122// Finds the pickup/delivery pairs of nodes on a given vehicle's route.
123// Returns the vector of visited pair indices, and stores the corresponding
124// pickup/delivery indices in visited_pickup_delivery_indices_for_pair_.
125// NOTE: Supposes that visited_pickup_delivery_indices_for_pair is correctly
126// sized and initialized to {-1, -1} for all pairs.
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) {
133 // visited_pickup_delivery_indices_for_pair must be all {-1, -1}.
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;
140 }));
141 visited_pairs->clear();
142 if (!dimension.HasPickupToDeliveryLimits()) {
143 return;
144 }
145 const RoutingModel& model = *dimension.model();
146
147 int64_t node_index = model.Start(vehicle);
148 while (!model.IsEnd(node_index)) {
149 if (model.IsPickup(node_index)) {
150 // We store the node_index as visited pickup for this pair.
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 =
156 node_index;
157 visited_pairs->push_back(pair_index);
158 } else if (model.IsDelivery(node_index)) {
159 // We set the limit with this delivery's pickup if one has been visited
160 // for this pair.
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) {
168 // This case should not happen, as a delivery must have its pickup
169 // on the route, but we ignore it here.
170 node_index = next_accessor(node_index);
171 continue;
172 }
173 pickup_delivery_index.second = node_index;
174 }
175 node_index = next_accessor(node_index);
176 }
177}
178
179} // namespace
180
181// LocalDimensionCumulOptimizer
182
184 const RoutingDimension* dimension,
186 : optimizer_core_(dimension, /*use_precedence_propagator=*/false) {
187 // Using one solver per vehicle in the hope that if routes don't change this
188 // will be faster.
189 const int vehicles = dimension->model()->vehicles();
190 solver_.resize(vehicles);
191 switch (solver_type) {
193 const glop::GlopParameters parameters = GetGlopParametersForLocalLP();
194 for (int vehicle = 0; vehicle < vehicles; ++vehicle) {
195 // TODO(user): Instead of passing false, detect if the relaxation
196 // will always violate the MIPL constraints.
197 solver_[vehicle] =
198 std::make_unique<RoutingGlopWrapper>(false, parameters);
199 }
200 break;
201 }
203 for (int vehicle = 0; vehicle < vehicles; ++vehicle) {
204 solver_[vehicle] = std::make_unique<RoutingCPSatWrapper>();
205 }
206 break;
207 }
208 default:
209 LOG(DFATAL) << "Unrecognized solver type: " << solver_type;
210 }
211}
212
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;
220 const DimensionSchedulingStatus status =
221 optimizer_core_.OptimizeSingleRouteWithResource(
222 vehicle, solve_duration_ratio, next_accessor,
223 /*dimension_travel_info=*/nullptr,
224 /*resource=*/nullptr,
225 /*optimize_vehicle_costs=*/optimal_cost != nullptr,
226 solver_[vehicle].get(), /*cumul_values=*/nullptr,
227 /*break_values=*/nullptr, optimal_cost, &transit_cost);
229 optimal_cost != nullptr) {
230 DCHECK_GE(*optimal_cost, 0);
231 *optimal_cost = CapAdd(*optimal_cost, transit_cost);
232 }
233 return status;
234}
235
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,
246 /*dimension_travel_info=*/nullptr, resource,
247 /*optimize_vehicle_costs=*/optimal_cost_without_transits != nullptr,
248 solver_[vehicle].get(), /*cumul_values=*/nullptr,
249 /*break_values=*/nullptr, optimal_cost_without_transits, nullptr);
250}
251
252std::vector<DimensionSchedulingStatus> LocalDimensionCumulOptimizer::
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);
269}
270
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, /*optimize_vehicle_costs=*/true, solver_[vehicle].get(),
283 optimal_cumuls, optimal_breaks, /*cost_without_transit=*/nullptr,
284 /*transit_cost=*/nullptr);
285}
286
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, /*optimize_vehicle_costs=*/true, solver_[vehicle].get(),
299 optimal_cumuls, optimal_breaks, optimal_cost_without_transits, nullptr);
300}
301
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);
313 RoutingLinearSolverWrapper* solver = solver_[vehicle].get();
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 /*clear_solution_constraints=*/true, solve_duration);
319}
320
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);
333}
334
335const int CumulBoundsPropagator::kNoParent = -2;
336const int CumulBoundsPropagator::kParentToBePropagated = -1;
337
339 : dimension_(*dimension), num_nodes_(2 * dimension->cumuls().size()) {
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});
346}
347
348void CumulBoundsPropagator::AddArcs(int first_index, int second_index,
349 int64_t offset) {
350 // Add arc first_index + offset <= second_index
351 outgoing_arcs_[PositiveNode(first_index)].push_back(
352 {PositiveNode(second_index), offset});
353 AddNodeToQueue(PositiveNode(first_index));
354 // Add arc -second_index + transit <= -first_index
355 outgoing_arcs_[NegativeNode(second_index)].push_back(
356 {NegativeNode(first_index), offset});
357 AddNodeToQueue(NegativeNode(second_index));
358}
359
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());
365
366 for (std::vector<ArcInfo>& arcs : outgoing_arcs_) {
367 arcs.clear();
368 }
369
370 RoutingModel* const model = dimension_.model();
371 std::vector<int64_t>& lower_bounds = propagated_bounds_;
372
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);
376
377 int node = model->Start(vehicle);
378 int index_on_route = 0;
379 while (true) {
380 int64_t cumul_lb, cumul_ub;
381 if (!GetCumulBoundsWithOffset(dimension_, node, cumul_offset, &cumul_lb,
382 &cumul_ub)) {
383 return false;
384 }
385 lower_bounds[PositiveNode(node)] = cumul_lb;
386 if (cumul_ub < std::numeric_limits<int64_t>::max()) {
387 lower_bounds[NegativeNode(node)] = -cumul_ub;
388 }
389
390 if (model->IsEnd(node)) {
391 break;
392 }
393
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;
404 ++index_on_route;
405 }
406 const IntVar& slack_var = *dimension_.SlackVar(node);
407 // node + transit + slack_var == next
408 // Add arcs for node + transit + slack_min <= next
409 AddArcs(node, next, CapAdd(transit, slack_var.Min()));
410 if (slack_var.Max() < std::numeric_limits<int64_t>::max()) {
411 // Add arcs for node + transit + slack_max >= next.
412 AddArcs(next, node, CapSub(-slack_var.Max(), transit));
413 }
414
415 node = next;
416 }
417
418 // Add vehicle span upper bound: end - span_ub <= start.
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);
422 }
423
424 // Set pickup/delivery limits on route.
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};
435
436 DCHECK_GE(pickup_index, 0);
437 if (delivery_index < 0) {
438 // We didn't encounter a delivery for this pickup.
439 continue;
440 }
441
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()) {
446 // delivery_cumul - limit <= pickup_cumul.
447 AddArcs(delivery_index, pickup_index, -limit);
448 }
449 }
450 }
451
452 for (const auto [first_node, second_node, offset, performed_constraint] :
453 dimension_.GetNodePrecedences()) {
454 if (next_accessor(first_node) == -1 || next_accessor(second_node) == -1) {
455 continue;
456 }
457 const bool first_node_unperformed =
458 lower_bounds[PositiveNode(first_node)] ==
459 std::numeric_limits<int64_t>::min();
460 const bool second_node_unperformed =
461 lower_bounds[PositiveNode(second_node)] ==
462 std::numeric_limits<int64_t>::min();
463 switch (RoutingDimension::GetPrecedenceStatus(first_node_unperformed,
464 second_node_unperformed,
465 performed_constraint)) {
466 case RoutingDimension::PrecedenceStatus::kActive:
467 break;
468 case RoutingDimension::PrecedenceStatus::kInactive:
469 continue;
470 case RoutingDimension::PrecedenceStatus::kInvalid:
471 return false;
472 }
473 AddArcs(first_node, second_node, offset);
474 }
475
476 return true;
477}
478
479bool CumulBoundsPropagator::UpdateCurrentLowerBoundOfNode(int node,
480 int64_t new_lb,
481 int64_t offset) {
482 const int cumul_var_index = node / 2;
483
484 if (node == PositiveNode(cumul_var_index)) {
485 // new_lb is a lower bound of the cumul of variable 'cumul_var_index'.
486 propagated_bounds_[node] = GetFirstPossibleValueForCumulWithOffset(
487 dimension_, cumul_var_index, new_lb, offset);
488 } else {
489 // -new_lb is an upper bound of the cumul of variable 'cumul_var_index'.
490 const int64_t new_ub = CapSub(0, new_lb);
491 propagated_bounds_[node] =
492 CapSub(0, GetLastPossibleValueForCumulWithOffset(
493 dimension_, cumul_var_index, new_ub, offset));
494 }
495
496 // Test that the lower/upper bounds do not cross each other.
497 const int64_t cumul_lower_bound =
498 propagated_bounds_[PositiveNode(cumul_var_index)];
499
500 const int64_t negated_cumul_upper_bound =
501 propagated_bounds_[NegativeNode(cumul_var_index)];
502
503 return CapAdd(negated_cumul_upper_bound, cumul_lower_bound) <= 0;
504}
505
506bool CumulBoundsPropagator::DisassembleSubtree(int source, int target) {
507 tmp_dfs_stack_.clear();
508 tmp_dfs_stack_.push_back(source);
509 while (!tmp_dfs_stack_.empty()) {
510 const int tail = tmp_dfs_stack_.back();
511 tmp_dfs_stack_.pop_back();
512 for (const ArcInfo& arc : outgoing_arcs_[tail]) {
513 const int child_node = arc.head;
514 if (tree_parent_node_of_[child_node] != tail) continue;
515 if (child_node == target) return false;
516 tree_parent_node_of_[child_node] = kParentToBePropagated;
517 tmp_dfs_stack_.push_back(child_node);
518 }
519 }
520 return true;
521}
522
524 const std::function<int64_t(int64_t)>& next_accessor, int64_t cumul_offset,
525 const std::vector<RoutingModel::RouteDimensionTravelInfo>*
526 dimension_travel_info_per_route) {
527 tree_parent_node_of_.assign(num_nodes_, kNoParent);
528 DCHECK(std::none_of(node_in_queue_.begin(), node_in_queue_.end(),
529 [](bool b) { return b; }));
530 DCHECK(bf_queue_.empty());
531
532 if (!InitializeArcsAndBounds(next_accessor, cumul_offset,
533 dimension_travel_info_per_route)) {
534 return CleanupAndReturnFalse();
535 }
536
537 std::vector<int64_t>& current_lb = propagated_bounds_;
538
539 // Bellman-Ford-Tarjan algorithm.
540 while (!bf_queue_.empty()) {
541 const int node = bf_queue_.front();
542 bf_queue_.pop_front();
543 node_in_queue_[node] = false;
544
545 if (tree_parent_node_of_[node] == kParentToBePropagated) {
546 // The parent of this node is still in the queue, so no need to process
547 // node now, since it will be re-enqueued when its parent is processed.
548 continue;
549 }
550
551 const int64_t lower_bound = current_lb[node];
552 for (const ArcInfo& arc : outgoing_arcs_[node]) {
553 // NOTE: kint64min as a lower bound means no lower bound at all, so we
554 // don't use this value to propagate.
555 const int64_t induced_lb =
556 (lower_bound == std::numeric_limits<int64_t>::min())
557 ? std::numeric_limits<int64_t>::min()
558 : CapAdd(lower_bound, arc.offset);
559
560 const int head_node = arc.head;
561 if (induced_lb <= current_lb[head_node]) {
562 // No update necessary for the head_node, continue to next children of
563 // node.
564 continue;
565 }
566 if (!UpdateCurrentLowerBoundOfNode(head_node, induced_lb, cumul_offset) ||
567 !DisassembleSubtree(head_node, node)) {
568 // The new lower bound is infeasible, or a positive cycle was detected
569 // in the precedence graph by DisassembleSubtree().
570 return CleanupAndReturnFalse();
571 }
572
573 tree_parent_node_of_[head_node] = node;
574 AddNodeToQueue(head_node);
575 }
576 }
577 return true;
578}
579
581 const RoutingDimension* dimension, bool use_precedence_propagator)
582 : dimension_(dimension),
583 visited_pickup_delivery_indices_for_pair_(
584 dimension->model()->GetPickupAndDeliveryPairs().size(), {-1, -1}) {
585 if (use_precedence_propagator) {
586 propagator_ = std::make_unique<CumulBoundsPropagator>(dimension);
587 }
588 const RoutingModel& model = *dimension_->model();
589 if (dimension_->HasBreakConstraints()) {
590 // Initialize vehicle_to_first_index_ so the variables of the breaks of
591 // vehicle v are stored from vehicle_to_first_index_[v] to
592 // vehicle_to_first_index_[v+1] - 1.
593 const int num_vehicles = model.vehicles();
594 vehicle_to_all_break_variables_offset_.reserve(num_vehicles);
595 int num_break_vars = 0;
596 for (int vehicle = 0; vehicle < num_vehicles; ++vehicle) {
597 vehicle_to_all_break_variables_offset_.push_back(num_break_vars);
598 const auto& intervals = dimension_->GetBreakIntervalsOfVehicle(vehicle);
599 num_break_vars += 2 * intervals.size(); // 2 variables per break.
600 }
601 all_break_variables_.resize(num_break_vars, -1);
602 }
603 if (!model.GetDimensionResourceGroupIndices(dimension_).empty()) {
604 resource_class_to_vehicle_assignment_variables_per_group_.resize(
605 model.GetResourceGroups().size());
606 resource_class_ignored_resources_per_group_.resize(
607 model.GetResourceGroups().size());
608 }
609}
610
613 int vehicle, double solve_duration_ratio,
614 const std::function<int64_t(int64_t)>& next_accessor,
615 const RouteDimensionTravelInfo* dimension_travel_info,
617 absl::Span<const int64_t> solution_cumul_values,
618 absl::Span<const int64_t> solution_break_values,
619 int64_t* cost_without_transits, int64_t* cost_offset,
620 bool reuse_previous_model_if_possible, bool clear_lp,
621 bool clear_solution_constraints, absl::Duration* const solve_duration) {
622 absl::Duration solve_duration_value;
623 int64_t cost_offset_value;
624 if (!reuse_previous_model_if_possible || solver->ModelIsEmpty()) {
625 InitOptimizer(solver);
626 // Make sure SetRouteCumulConstraints will properly set the cumul bounds by
627 // looking at this route only.
628 DCHECK_EQ(propagator_.get(), nullptr);
629
630 RoutingModel* const model = dimension_->model();
631 const bool optimize_vehicle_costs =
632 !model->IsEnd(next_accessor(model->Start(vehicle))) ||
633 model->IsVehicleUsedWhenEmpty(vehicle);
634 if (!SetRouteCumulConstraints(
635 vehicle, next_accessor, dimension_->transit_evaluator(vehicle),
636 dimension_travel_info,
637 dimension_->GetLocalOptimizerOffsetForVehicle(vehicle),
638 optimize_vehicle_costs, solver, nullptr, &cost_offset_value)) {
640 }
641 if (model->CheckLimit()) {
643 }
644 solve_duration_value = model->RemainingTime() * solve_duration_ratio;
645 if (solve_duration != nullptr) *solve_duration = solve_duration_value;
646 if (cost_offset != nullptr) *cost_offset = cost_offset_value;
647 } else {
648 CHECK(cost_offset != nullptr)
649 << "Cannot reuse model without the cost_offset";
650 cost_offset_value = *cost_offset;
651 CHECK(solve_duration != nullptr)
652 << "Cannot reuse model without the solve_duration";
653 solve_duration_value = *solve_duration * solve_duration_ratio;
654 }
655
656 // Constrains the cumuls.
657 DCHECK_EQ(solution_cumul_values.size(),
658 current_route_cumul_variables_.size());
659 for (int i = 0; i < current_route_cumul_variables_.size(); ++i) {
660 if (solution_cumul_values[i] < current_route_min_cumuls_[i] ||
661 solution_cumul_values[i] > current_route_max_cumuls_[i]) {
663 }
664 solver->SetVariableBounds(current_route_cumul_variables_[i],
665 /*lower_bound=*/solution_cumul_values[i],
666 /*upper_bound=*/solution_cumul_values[i]);
667 }
668
669 // Constrains the breaks.
670 DCHECK_EQ(solution_break_values.size(),
671 current_route_break_variables_.size());
672 std::vector<int64_t> current_route_min_breaks(
673 current_route_break_variables_.size());
674 std::vector<int64_t> current_route_max_breaks(
675 current_route_break_variables_.size());
676 for (int i = 0; i < current_route_break_variables_.size(); ++i) {
677 current_route_min_breaks[i] =
678 solver->GetVariableLowerBound(current_route_break_variables_[i]);
679 current_route_max_breaks[i] =
680 solver->GetVariableUpperBound(current_route_break_variables_[i]);
681 solver->SetVariableBounds(current_route_break_variables_[i],
682 /*lower_bound=*/solution_break_values[i],
683 /*upper_bound=*/solution_break_values[i]);
684 }
685
686 const DimensionSchedulingStatus status = solver->Solve(solve_duration_value);
688 solver->Clear();
689 return status;
690 }
691
692 if (cost_without_transits != nullptr) {
693 *cost_without_transits =
694 CapAdd(cost_offset_value, solver->GetObjectiveValue());
695 }
696
697 if (clear_lp) {
698 solver->Clear();
699 } else if (clear_solution_constraints) {
700 for (int i = 0; i < current_route_cumul_variables_.size(); ++i) {
701 solver->SetVariableBounds(current_route_cumul_variables_[i],
702 /*lower_bound=*/current_route_min_cumuls_[i],
703 /*upper_bound=*/current_route_max_cumuls_[i]);
704 }
705 for (int i = 0; i < current_route_break_variables_.size(); ++i) {
706 solver->SetVariableBounds(current_route_break_variables_[i],
707 /*lower_bound=*/current_route_min_breaks[i],
708 /*upper_bound=*/current_route_max_breaks[i]);
709 }
710 }
711 return status;
712}
713
714namespace {
715template <typename T>
716void ClearIfNonNull(std::vector<T>* v) {
717 if (v != nullptr) {
718 v->clear();
719 }
720}
721} // namespace
722
725 int vehicle, double solve_duration_ratio,
726 const std::function<int64_t(int64_t)>& next_accessor,
727 const RouteDimensionTravelInfo* dimension_travel_info,
728 const RoutingModel::ResourceGroup::Resource* resource,
729 bool optimize_vehicle_costs, RoutingLinearSolverWrapper* solver,
730 std::vector<int64_t>* cumul_values, std::vector<int64_t>* break_values,
731 int64_t* cost_without_transits, int64_t* transit_cost, bool clear_lp) {
732 if (cost_without_transits != nullptr) *cost_without_transits = -1;
733 ClearIfNonNull(cumul_values);
734 ClearIfNonNull(break_values);
735
736 const std::vector<Resource> resources =
737 resource == nullptr ? std::vector<Resource>()
738 : std::vector<Resource>({*resource});
739 const std::vector<int> resource_indices =
740 resource == nullptr ? std::vector<int>() : std::vector<int>({0});
741 std::vector<int64_t> costs_without_transits;
742 std::vector<std::vector<int64_t>> cumul_values_vec;
743 std::vector<std::vector<int64_t>> break_values_vec;
744 const std::vector<DimensionSchedulingStatus> statuses =
746 vehicle, solve_duration_ratio, next_accessor,
747 dimension_->transit_evaluator(vehicle), dimension_travel_info,
748 resources, resource_indices, optimize_vehicle_costs, solver,
749 cumul_values != nullptr ? &cumul_values_vec : nullptr,
750 break_values != nullptr ? &break_values_vec : nullptr,
751 cost_without_transits != nullptr ? &costs_without_transits : nullptr,
752 transit_cost, clear_lp);
753
754 if (dimension()->model()->CheckLimit()) {
756 }
757 DCHECK_EQ(statuses.size(), 1);
758 const DimensionSchedulingStatus status = statuses[0];
759
760 if (status == DimensionSchedulingStatus::INFEASIBLE) return status;
761
762 if (cost_without_transits != nullptr) {
763 *cost_without_transits = costs_without_transits[0];
764 }
765 if (cumul_values != nullptr) *cumul_values = std::move(cumul_values_vec[0]);
766 if (break_values != nullptr) *break_values = std::move(break_values_vec[0]);
767
768 return status;
769}
770
771namespace {
772
773using ResourceGroup = RoutingModel::ResourceGroup;
774
775bool GetDomainOffsetBounds(const Domain& domain, int64_t offset,
776 ClosedInterval* interval) {
777 const int64_t lower_bound =
778 std::max<int64_t>(CapSub(domain.Min(), offset), 0);
779 const int64_t upper_bound =
780 domain.Max() == std::numeric_limits<int64_t>::max()
781 ? std::numeric_limits<int64_t>::max()
782 : CapSub(domain.Max(), offset);
783 if (lower_bound > upper_bound) return false;
784
785 *interval = ClosedInterval(lower_bound, upper_bound);
786 return true;
787}
788
789bool GetIntervalIntersectionWithOffsetDomain(const ClosedInterval& interval,
790 const Domain& domain,
791 int64_t offset,
792 ClosedInterval* intersection) {
793 if (domain == Domain::AllValues()) {
794 *intersection = interval;
795 return true;
796 }
797 ClosedInterval offset_domain;
798 if (!GetDomainOffsetBounds(domain, offset, &offset_domain)) {
799 return false;
800 }
801 const int64_t intersection_lb = std::max(interval.start, offset_domain.start);
802 const int64_t intersection_ub = std::min(interval.end, offset_domain.end);
803 if (intersection_lb > intersection_ub) return false;
804
805 *intersection = ClosedInterval(intersection_lb, intersection_ub);
806 return true;
807}
808
809ClosedInterval GetVariableBounds(int index,
810 const RoutingLinearSolverWrapper& solver) {
811 return ClosedInterval(solver.GetVariableLowerBound(index),
812 solver.GetVariableUpperBound(index));
813}
814
815bool TightenStartEndVariableBoundsWithResource(
816 const RoutingDimension& dimension, const ResourceGroup::Resource& resource,
817 const ClosedInterval& start_bounds, int start_index,
818 const ClosedInterval& end_bounds, int end_index, int64_t offset,
820 const ResourceGroup::Attributes& attributes =
821 resource.GetDimensionAttributes(&dimension);
822 ClosedInterval new_start_bounds;
823 ClosedInterval new_end_bounds;
824 return GetIntervalIntersectionWithOffsetDomain(start_bounds,
825 attributes.start_domain(),
826 offset, &new_start_bounds) &&
827 solver->SetVariableBounds(start_index, new_start_bounds.start,
828 new_start_bounds.end) &&
829 GetIntervalIntersectionWithOffsetDomain(
830 end_bounds, attributes.end_domain(), offset, &new_end_bounds) &&
831 solver->SetVariableBounds(end_index, new_end_bounds.start,
832 new_end_bounds.end);
833}
834
835bool TightenStartEndVariableBoundsWithAssignedResources(
836 const RoutingDimension& dimension, int v, int start_index, int end_index,
837 int64_t offset, RoutingLinearSolverWrapper* solver) {
838 const RoutingModel* model = dimension.model();
839 for (int rg_index : model->GetDimensionResourceGroupIndices(&dimension)) {
840 const IntVar* res_var = model->ResourceVars(rg_index)[v];
841 DCHECK(res_var->Bound());
842 const int res_index = res_var->Value();
843 if (res_index < 0) {
844 // No resource from this group assigned to the vehicle.
845 DCHECK(!model->GetResourceGroup(rg_index)->VehicleRequiresAResource(v));
846 continue;
847 }
848 const ResourceGroup::Resource& resource =
849 model->GetResourceGroup(rg_index)->GetResource(res_index);
850 if (!TightenStartEndVariableBoundsWithResource(
851 dimension, resource, GetVariableBounds(start_index, *solver),
852 start_index, GetVariableBounds(end_index, *solver), end_index,
853 offset, solver)) {
854 return false;
855 }
856 }
857 return true;
858}
859
860} // namespace
861
862std::vector<DimensionSchedulingStatus>
864 int vehicle, double solve_duration_ratio,
865 const std::function<int64_t(int64_t)>& next_accessor,
866 const std::function<int64_t(int64_t, int64_t)>& transit_accessor,
867 const RouteDimensionTravelInfo* dimension_travel_info,
868 absl::Span<const RoutingModel::ResourceGroup::Resource> resources,
869 absl::Span<const int> resource_indices, bool optimize_vehicle_costs,
871 std::vector<std::vector<int64_t>>* cumul_values,
872 std::vector<std::vector<int64_t>>* break_values,
873 std::vector<int64_t>* costs_without_transits, int64_t* transit_cost,
874 bool clear_lp) {
875 ClearIfNonNull(costs_without_transits);
876 const bool optimize_with_resources = !resource_indices.empty();
877 if (!optimize_with_resources && !resources.empty()) return {};
878
879 InitOptimizer(solver);
880 // Make sure SetRouteCumulConstraints will properly set the cumul bounds by
881 // looking at this route only.
882 DCHECK_EQ(propagator_.get(), nullptr);
883
884 RoutingModel* const model = dimension()->model();
885 if (model->IsEnd(next_accessor(model->Start(vehicle))) &&
886 !model->IsVehicleUsedWhenEmpty(vehicle)) {
887 // An unused empty vehicle doesn't require resources.
888 DCHECK(!optimize_with_resources);
889 optimize_vehicle_costs = false;
890 }
891
892 const int64_t cumul_offset =
893 dimension_->GetLocalOptimizerOffsetForVehicle(vehicle);
894 int64_t cost_offset = 0;
895 if (!SetRouteCumulConstraints(vehicle, next_accessor, transit_accessor,
896 dimension_travel_info, cumul_offset,
897 optimize_vehicle_costs, solver, transit_cost,
898 &cost_offset)) {
899 if (costs_without_transits != nullptr) {
900 costs_without_transits->assign(1, -1);
901 }
903 }
904 DCHECK_GE(current_route_cumul_variables_.size(), 2);
905
906 // NOTE: When there are no resources to optimize for, we still solve the
907 // optimization problem for the route (without any added resource constraint).
908 // TODO(user): Consider taking 'num_solves' into account to re-adjust the
909 // solve_duration_ratio to make sure all sub-problems are given enough time.
910 const int num_solves = resource_indices.empty() ? 1 : resource_indices.size();
911 if (costs_without_transits != nullptr) {
912 costs_without_transits->assign(num_solves, -1);
913 }
914 if (cumul_values != nullptr) cumul_values->assign(num_solves, {});
915 if (break_values != nullptr) break_values->assign(num_solves, {});
916
917 const int start_cumul = current_route_cumul_variables_[0];
918 const ClosedInterval start_bounds = GetVariableBounds(start_cumul, *solver);
919 const int end_cumul = current_route_cumul_variables_.back();
920 const ClosedInterval end_bounds = GetVariableBounds(end_cumul, *solver);
921 std::vector<DimensionSchedulingStatus> statuses;
922 statuses.reserve(num_solves);
923 for (int i = 0; i < num_solves; i++) {
924 if (model->CheckLimit()) {
925 // The model's deadline has been reached, stop.
926 ClearIfNonNull(costs_without_transits);
927 ClearIfNonNull(cumul_values);
928 ClearIfNonNull(break_values);
929 solver->Clear();
930 return {};
931 }
932 if (optimize_with_resources &&
933 !TightenStartEndVariableBoundsWithResource(
934 *dimension_, resources[resource_indices[i]], start_bounds,
935 start_cumul, end_bounds, end_cumul, cumul_offset, solver)) {
936 // The resource attributes don't match this vehicle.
937 statuses.push_back(DimensionSchedulingStatus::INFEASIBLE);
938 continue;
939 }
940
941 statuses.push_back(
942 solver->Solve(model->RemainingTime() * solve_duration_ratio));
943 if (statuses.back() == DimensionSchedulingStatus::INFEASIBLE) {
944 continue;
945 }
946 if (costs_without_transits != nullptr) {
947 costs_without_transits->at(i) =
948 optimize_vehicle_costs
949 ? CapAdd(cost_offset, solver->GetObjectiveValue())
950 : 0;
951 }
952
953 if (cumul_values != nullptr) {
954 SetValuesFromLP(current_route_cumul_variables_, cumul_offset, solver,
955 &cumul_values->at(i));
956 }
957 if (break_values != nullptr) {
958 SetValuesFromLP(current_route_break_variables_, cumul_offset, solver,
959 &break_values->at(i));
960 }
961 }
962
963 if (clear_lp) {
964 solver->Clear();
965 }
966 return statuses;
967}
968
970 const std::function<int64_t(int64_t)>& next_accessor,
971 const std::vector<RouteDimensionTravelInfo>&
972 dimension_travel_info_per_route,
973 RoutingLinearSolverWrapper* solver, std::vector<int64_t>* cumul_values,
974 std::vector<int64_t>* break_values,
975 std::vector<std::vector<int>>* resource_indices_per_group,
976 int64_t* cost_without_transits, int64_t* transit_cost, bool clear_lp,
977 bool optimize_resource_assignment) {
978 InitOptimizer(solver);
979 if (!optimize_resource_assignment) {
980 DCHECK_EQ(resource_indices_per_group, nullptr);
981 }
982
983 // If both "cumul_values" and "costs_without_transits" parameters are null,
984 // we don't try to optimize the cost and stop at the first feasible solution.
985 const bool optimize_costs =
986 (cumul_values != nullptr) || (cost_without_transits != nullptr);
987 bool has_vehicles_being_optimized = false;
988
989 const int64_t cumul_offset = dimension_->GetGlobalOptimizerOffset();
990
991 if (propagator_ != nullptr &&
992 !propagator_->PropagateCumulBounds(next_accessor, cumul_offset,
993 &dimension_travel_info_per_route)) {
995 }
996
997 int64_t total_transit_cost = 0;
998 int64_t total_cost_offset = 0;
999 const RoutingModel* model = dimension_->model();
1000 for (int vehicle = 0; vehicle < model->vehicles(); vehicle++) {
1001 int64_t route_transit_cost = 0;
1002 int64_t route_cost_offset = 0;
1003 const bool vehicle_is_used =
1004 !model->IsEnd(next_accessor(model->Start(vehicle))) ||
1005 model->IsVehicleUsedWhenEmpty(vehicle);
1006 const bool optimize_vehicle_costs = optimize_costs && vehicle_is_used;
1007 const RouteDimensionTravelInfo* const dimension_travel_info =
1008 dimension_travel_info_per_route.empty()
1009 ? nullptr
1010 : &dimension_travel_info_per_route[vehicle];
1011 if (!SetRouteCumulConstraints(
1012 vehicle, next_accessor, dimension_->transit_evaluator(vehicle),
1013 dimension_travel_info, cumul_offset, optimize_vehicle_costs, solver,
1014 &route_transit_cost, &route_cost_offset)) {
1016 }
1017 DCHECK_GE(current_route_cumul_variables_.size(), 2);
1018 if (vehicle_is_used && !optimize_resource_assignment) {
1019 // Tighten the route start/end cumul wrt. resources assigned to it.
1020 if (!TightenStartEndVariableBoundsWithAssignedResources(
1021 *dimension_, vehicle, current_route_cumul_variables_[0],
1022 current_route_cumul_variables_.back(), cumul_offset, solver)) {
1024 }
1025 }
1026 total_transit_cost = CapAdd(total_transit_cost, route_transit_cost);
1027 total_cost_offset = CapAdd(total_cost_offset, route_cost_offset);
1028 has_vehicles_being_optimized |= optimize_vehicle_costs;
1029 }
1030 if (transit_cost != nullptr) {
1031 *transit_cost = total_transit_cost;
1032 }
1033
1034 if (!SetGlobalConstraints(next_accessor, cumul_offset,
1035 has_vehicles_being_optimized,
1036 optimize_resource_assignment, solver)) {
1038 }
1039
1040 const DimensionSchedulingStatus status =
1041 solver->Solve(model->RemainingTime());
1043 solver->Clear();
1044 return status;
1045 }
1046
1047 // TODO(user): In case the status is RELAXED_OPTIMAL_ONLY, check we can
1048 // safely avoid filling variable and cost values.
1049 SetValuesFromLP(index_to_cumul_variable_, cumul_offset, solver, cumul_values);
1050 SetValuesFromLP(all_break_variables_, cumul_offset, solver, break_values);
1051 SetResourceIndices(solver, resource_indices_per_group);
1052
1053 if (cost_without_transits != nullptr) {
1054 *cost_without_transits =
1055 CapAdd(solver->GetObjectiveValue(), total_cost_offset);
1056 }
1057
1058 if (clear_lp) {
1059 solver->Clear();
1060 }
1061 return status;
1062}
1063
1065 const std::function<int64_t(int64_t)>& next_accessor,
1066 const std::vector<RouteDimensionTravelInfo>&
1067 dimension_travel_info_per_route,
1068 RoutingLinearSolverWrapper* solver, std::vector<int64_t>* cumul_values,
1069 std::vector<int64_t>* break_values) {
1070 // Note: We pass a non-nullptr cost to the Optimize() method so the costs
1071 // are optimized by the solver.
1072 int64_t cost = 0;
1073 const glop::GlopParameters original_params = GetGlopParametersForGlobalLP();
1074 glop::GlopParameters packing_parameters;
1075 if (!solver->IsCPSATSolver()) {
1076 packing_parameters = original_params;
1077 packing_parameters.set_use_dual_simplex(false);
1078 packing_parameters.set_use_preprocessing(true);
1079 solver->SetParameters(packing_parameters.SerializeAsString());
1080 }
1082 Optimize(next_accessor, dimension_travel_info_per_route, solver,
1083 /*cumul_values=*/nullptr, /*break_values=*/nullptr,
1084 /*resource_indices_per_group=*/nullptr, &cost,
1085 /*transit_cost=*/nullptr,
1086 /*clear_lp=*/false, /*optimize_resource_assignment=*/false);
1088 std::vector<int> vehicles(dimension()->model()->vehicles());
1089 std::iota(vehicles.begin(), vehicles.end(), 0);
1090 // Subtle: Even if the status was RELAXED_OPTIMAL_ONLY we try to pack just
1091 // in case packing manages to make the solution completely feasible.
1092 status = PackRoutes(std::move(vehicles), /*solve_duration_ratio=*/1.0,
1093 solver, packing_parameters);
1094 }
1095 if (!solver->IsCPSATSolver()) {
1096 solver->SetParameters(original_params.SerializeAsString());
1097 }
1099 return status;
1100 }
1101 // TODO(user): In case the status is RELAXED_OPTIMAL_ONLY, check we can
1102 // safely avoid filling variable values.
1103 const int64_t global_offset = dimension_->GetGlobalOptimizerOffset();
1104 SetValuesFromLP(index_to_cumul_variable_, global_offset, solver,
1105 cumul_values);
1106 SetValuesFromLP(all_break_variables_, global_offset, solver, break_values);
1107 solver->Clear();
1108 return status;
1109}
1110
1113 int vehicle, double solve_duration_ratio,
1114 const std::function<int64_t(int64_t)>& next_accessor,
1115 const RouteDimensionTravelInfo* dimension_travel_info,
1116 const RoutingModel::ResourceGroup::Resource* resource,
1117 RoutingLinearSolverWrapper* solver, std::vector<int64_t>* cumul_values,
1118 std::vector<int64_t>* break_values) {
1119 const glop::GlopParameters original_params = GetGlopParametersForLocalLP();
1120 glop::GlopParameters packing_parameters;
1121 if (!solver->IsCPSATSolver()) {
1122 packing_parameters = original_params;
1123 packing_parameters.set_use_dual_simplex(false);
1124 packing_parameters.set_use_preprocessing(true);
1125 solver->SetParameters(packing_parameters.SerializeAsString());
1126 }
1127 // TODO(user): Since there are 3 separate solves for packing, divide the
1128 // input solve_duration_ratio by 3 before passing to the below functions? Or
1129 // maybe divide by 2 and let PackRoutes() divide by 2 itself (since the 2
1130 // solves in PackRoutes() should start with a very good first solution hint).
1132 vehicle, solve_duration_ratio, next_accessor, dimension_travel_info,
1133 resource, /*optimize_vehicle_costs=*/true, solver,
1134 /*cumul_values=*/nullptr, /*break_values=*/nullptr,
1135 /*cost_without_transit=*/nullptr, /*transit_cost=*/nullptr,
1136 /*clear_lp=*/false);
1137
1139 status =
1140 PackRoutes({vehicle}, solve_duration_ratio, solver, packing_parameters);
1141 }
1142 if (!solver->IsCPSATSolver()) {
1143 solver->SetParameters(original_params.SerializeAsString());
1144 }
1145
1148 }
1149 const int64_t local_offset =
1150 dimension_->GetLocalOptimizerOffsetForVehicle(vehicle);
1151 SetValuesFromLP(current_route_cumul_variables_, local_offset, solver,
1152 cumul_values);
1153 SetValuesFromLP(current_route_break_variables_, local_offset, solver,
1154 break_values);
1155 solver->Clear();
1156 return status;
1157}
1158
1159DimensionSchedulingStatus DimensionCumulOptimizerCore::PackRoutes(
1160 std::vector<int> vehicles, double solve_duration_ratio,
1162 const glop::GlopParameters& packing_parameters) {
1163 const RoutingModel* model = dimension_->model();
1164
1165 // NOTE(user): Given our constraint matrix, our problem *should* always
1166 // have an integer optimal solution, in which case we can round to the nearest
1167 // integer both for the objective constraint bound (returned by
1168 // GetObjectiveValue()) and the end cumul variable bound after minimizing
1169 // (see b/154381899 showcasing an example where std::ceil leads to an
1170 // "imperfect" packing due to rounding precision errors).
1171 // If this DCHECK ever fails, it can be removed but the code below should be
1172 // adapted to have a 2-phase approach, solving once with the rounded value as
1173 // bound and if this fails, solve again using std::ceil.
1174 DCHECK(solver->SolutionIsInteger());
1175
1176 // Minimize the route end times without increasing the cost.
1177 solver->AddObjectiveConstraint();
1178 solver->ClearObjective();
1179 for (int vehicle : vehicles) {
1181 index_to_cumul_variable_[model->End(vehicle)], 1);
1182 }
1183
1184 glop::GlopParameters current_params;
1185 const auto retry_solving = [&current_params, model, solver,
1186 solve_duration_ratio]() {
1187 // NOTE: To bypass some cases of false negatives due to imprecisions, we try
1188 // running Glop with a different use_dual_simplex parameter when running
1189 // into an infeasible status.
1190 current_params.set_use_dual_simplex(!current_params.use_dual_simplex());
1191 solver->SetParameters(current_params.SerializeAsString());
1192 return solver->Solve(model->RemainingTime() * solve_duration_ratio);
1193 };
1194 if (solver->Solve(model->RemainingTime() * solve_duration_ratio) ==
1196 if (solver->IsCPSATSolver()) {
1198 }
1199
1200 current_params = packing_parameters;
1201 if (retry_solving() == DimensionSchedulingStatus::INFEASIBLE) {
1203 }
1204 }
1205
1206 // Maximize the route start times without increasing the cost or the route end
1207 // times.
1208 solver->ClearObjective();
1209 for (int vehicle : vehicles) {
1210 const int end_cumul_var = index_to_cumul_variable_[model->End(vehicle)];
1211 // end_cumul_var <= solver.GetValue(end_cumul_var)
1212 solver->SetVariableBounds(
1213 end_cumul_var, solver->GetVariableLowerBound(end_cumul_var),
1214 MathUtil::FastInt64Round(solver->GetValue(end_cumul_var)));
1215
1216 // Maximize the starts of the routes.
1217 solver->SetObjectiveCoefficient(
1218 index_to_cumul_variable_[model->Start(vehicle)], -1);
1219 }
1220
1222 solver->Solve(model->RemainingTime() * solve_duration_ratio);
1223 if (!solver->IsCPSATSolver() &&
1225 status = retry_solving();
1226 }
1227 return status;
1228}
1229
1230#define SET_DEBUG_VARIABLE_NAME(solver, var, name) \
1231 do { \
1232 if (DEBUG_MODE) { \
1233 solver->SetVariableName(var, name); \
1234 } \
1235 } while (false)
1236
1237void DimensionCumulOptimizerCore::InitOptimizer(
1238 RoutingLinearSolverWrapper* solver) {
1239 solver->Clear();
1240 index_to_cumul_variable_.assign(dimension_->cumuls().size(), -1);
1241 max_end_cumul_ = solver->CreateNewPositiveVariable();
1242 SET_DEBUG_VARIABLE_NAME(solver, max_end_cumul_, "max_end_cumul");
1243 min_start_cumul_ = solver->CreateNewPositiveVariable();
1244 SET_DEBUG_VARIABLE_NAME(solver, min_start_cumul_, "min_start_cumul");
1245}
1246
1247bool DimensionCumulOptimizerCore::ExtractRouteCumulBounds(
1248 absl::Span<const int64_t> route, int64_t cumul_offset) {
1249 const int route_size = route.size();
1250 current_route_min_cumuls_.resize(route_size);
1251 current_route_max_cumuls_.resize(route_size);
1252
1253 // Extract cumul min/max and fixed transits from CP.
1254 for (int pos = 0; pos < route_size; ++pos) {
1255 if (!GetCumulBoundsWithOffset(*dimension_, route[pos], cumul_offset,
1256 &current_route_min_cumuls_[pos],
1257 &current_route_max_cumuls_[pos])) {
1258 return false;
1259 }
1260 }
1261 return true;
1262}
1263
1264bool DimensionCumulOptimizerCore::TightenRouteCumulBounds(
1265 absl::Span<const int64_t> route, absl::Span<const int64_t> min_transits,
1266 int64_t cumul_offset) {
1267 const int route_size = route.size();
1268 if (propagator_ != nullptr) {
1269 for (int pos = 0; pos < route_size; pos++) {
1270 const int64_t node = route[pos];
1271 current_route_min_cumuls_[pos] = propagator_->CumulMin(node);
1272 DCHECK_GE(current_route_min_cumuls_[pos], 0);
1273 current_route_max_cumuls_[pos] = propagator_->CumulMax(node);
1274 DCHECK_GE(current_route_max_cumuls_[pos], current_route_min_cumuls_[pos]);
1275 }
1276 return true;
1277 }
1278
1279 // Refine cumul bounds using
1280 // cumul[i+1] >= cumul[i] + fixed_transit[i] + slack[i].
1281 for (int pos = 1; pos < route_size; ++pos) {
1282 const int64_t slack_min = dimension_->SlackVar(route[pos - 1])->Min();
1283 current_route_min_cumuls_[pos] = std::max(
1284 current_route_min_cumuls_[pos],
1285 CapAdd(
1286 CapAdd(current_route_min_cumuls_[pos - 1], min_transits[pos - 1]),
1287 slack_min));
1288 current_route_min_cumuls_[pos] = GetFirstPossibleValueForCumulWithOffset(
1289 *dimension_, route[pos], current_route_min_cumuls_[pos], cumul_offset);
1290 if (current_route_min_cumuls_[pos] > current_route_max_cumuls_[pos]) {
1291 return false;
1292 }
1293 }
1294
1295 for (int pos = route_size - 2; pos >= 0; --pos) {
1296 // If cumul_max[pos+1] is kint64max, it will be translated to
1297 // double +infinity, so it must not constrain cumul_max[pos].
1298 if (current_route_max_cumuls_[pos + 1] <
1299 std::numeric_limits<int64_t>::max()) {
1300 const int64_t slack_min = dimension_->SlackVar(route[pos])->Min();
1301 current_route_max_cumuls_[pos] = std::min(
1302 current_route_max_cumuls_[pos],
1303 CapSub(CapSub(current_route_max_cumuls_[pos + 1], min_transits[pos]),
1304 slack_min));
1305 current_route_max_cumuls_[pos] = GetLastPossibleValueForCumulWithOffset(
1306 *dimension_, route[pos], current_route_max_cumuls_[pos],
1307 cumul_offset);
1308 if (current_route_max_cumuls_[pos] < current_route_min_cumuls_[pos]) {
1309 return false;
1310 }
1311 }
1312 }
1313 return true;
1314}
1315
1316std::vector<SlopeAndYIntercept> PiecewiseLinearFunctionToSlopeAndYIntercept(
1317 const FloatSlopePiecewiseLinearFunction& pwl_function, int index_start,
1318 int index_end) {
1319 const auto& x_anchors = pwl_function.x_anchors();
1320 const auto& y_anchors = pwl_function.y_anchors();
1321 if (index_end < 0) index_end = x_anchors.size() - 1;
1322 const int num_segments = index_end - index_start;
1323 DCHECK_GE(num_segments, 1);
1324 std::vector<SlopeAndYIntercept> slope_and_y_intercept(num_segments);
1325 for (int seg = index_start; seg < index_end; ++seg) {
1326 auto& [slope, y_intercept] = slope_and_y_intercept[seg - index_start];
1327 slope = (y_anchors[seg + 1] - y_anchors[seg]) /
1328 static_cast<double>(x_anchors[seg + 1] - x_anchors[seg]);
1329 y_intercept = y_anchors[seg] - slope * x_anchors[seg];
1330 }
1331 return slope_and_y_intercept;
1332}
1333
1335 absl::Span<const SlopeAndYIntercept> slope_and_y_intercept) {
1336 CHECK(!slope_and_y_intercept.empty());
1337 std::vector<bool> convex(slope_and_y_intercept.size(), false);
1338 double previous_slope = std::numeric_limits<double>::max();
1339 for (int i = 0; i < slope_and_y_intercept.size(); ++i) {
1340 const auto& pair = slope_and_y_intercept[i];
1341 if (pair.slope < previous_slope) {
1342 convex[i] = true;
1343 }
1344 previous_slope = pair.slope;
1345 }
1346 return convex;
1347}
1348
1349namespace {
1350// Find a "good" scaling factor for constraints with non-integers coefficients.
1351// See sat::FindBestScalingAndComputeErrors() for more infos.
1352double FindBestScaling(absl::Span<const double> coefficients,
1353 absl::Span<const double> lower_bounds,
1354 absl::Span<const double> upper_bounds,
1355 int64_t max_absolute_activity,
1356 double wanted_absolute_activity_precision) {
1357 double unused_relative_coeff_error = 0;
1358 double unused_scaled_sum_error = 0;
1360 coefficients, lower_bounds, upper_bounds, max_absolute_activity,
1361 wanted_absolute_activity_precision, &unused_relative_coeff_error,
1362 &unused_scaled_sum_error);
1363}
1364} // namespace
1365
1366bool DimensionCumulOptimizerCore::SetRouteTravelConstraints(
1367 const RouteDimensionTravelInfo* dimension_travel_info,
1368 absl::Span<const int> lp_slacks, absl::Span<const int64_t> fixed_transit,
1370 const std::vector<int>& lp_cumuls = current_route_cumul_variables_;
1371 const int path_size = lp_cumuls.size();
1372
1373 if (dimension_travel_info == nullptr ||
1374 dimension_travel_info->transition_info.empty()) {
1375 // Travel is not travel-start dependent.
1376 // Add all path constraints to LP:
1377 // cumul[i] + fixed_transit[i] + slack[i] == cumul[i+1]
1378 // <=> fixed_transit[i] == cumul[i+1] - cumul[i] - slack[i].
1379 for (int pos = 0; pos < path_size - 1; ++pos) {
1380 const int ct =
1381 solver->CreateNewConstraint(fixed_transit[pos], fixed_transit[pos]);
1382 solver->SetCoefficient(ct, lp_cumuls[pos + 1], 1);
1383 solver->SetCoefficient(ct, lp_cumuls[pos], -1);
1384 solver->SetCoefficient(ct, lp_slacks[pos], -1);
1385 }
1386 return true;
1387 }
1388
1389 // See:
1390 // https://docs.google.com/document/d/1niFfbCNjh70VepvVk4Ft8QgQAcrA5hrye-Vu_k0VgWw/edit?usp=sharing&resourcekey=0-rUJUHuWPjn1wWZaA0uDdtg
1391 for (int pos = 0; pos < path_size - 1; ++pos) {
1392 // Add a traffic-aware compression cost, for every path.
1393 // compression_cost represents the cost of the ABSOLUTE compression of the
1394 // travel.
1395 const int compression_cost = solver->CreateNewPositiveVariable();
1396 SET_DEBUG_VARIABLE_NAME(solver, compression_cost,
1397 absl::StrFormat("compression_cost(%ld)", pos));
1398 // relative_compression_cost represents the cost of the RELATIVE compression
1399 // of the travel. This is the real cost used. In theory,
1400 // relative_compression_cost = compression_cost / Tᵣ, where Tᵣ is the travel
1401 // value (computed with the PWL). In practice, this requires a
1402 // multiplication which is slow, so several approximations are implemented
1403 // below.
1404 const int relative_compression_cost = solver->CreateNewPositiveVariable();
1406 solver, relative_compression_cost,
1407 absl::StrFormat("relative_compression_cost(%ld)", pos));
1408
1409 const RoutingModel::RouteDimensionTravelInfo::TransitionInfo&
1410 transition_info = dimension_travel_info->transition_info[pos];
1411 const FloatSlopePiecewiseLinearFunction& travel_function =
1412 transition_info.travel_start_dependent_travel;
1413 const auto& travel_x_anchors = travel_function.x_anchors();
1414
1415 // 1. Create the travel value variable and set its constraints.
1416 // 1.a. Create Variables for the start and value of a travel
1417 const int64_t pre_travel_transit = transition_info.pre_travel_transit_value;
1418 const int64_t post_travel_transit =
1419 transition_info.post_travel_transit_value;
1420 const int64_t compressed_travel_value_lower_bound =
1421 transition_info.compressed_travel_value_lower_bound;
1422 const int64_t travel_value_upper_bound =
1423 transition_info.travel_value_upper_bound;
1424 // The lower bound of travel_value is already implemented by constraints as
1425 // travel_value >= compressed_travel_value (defined below) and
1426 // compressed_travel_value has compressed_travel_value_lower_bound as a
1427 // lower bound. The bound is added for the sake of clarity and being
1428 // explicit.
1429 const int travel_value = solver->AddVariable(
1430 compressed_travel_value_lower_bound, travel_value_upper_bound);
1431 SET_DEBUG_VARIABLE_NAME(solver, travel_value,
1432 absl::StrFormat("travel_value(%ld)", pos));
1433 const int travel_start = solver->AddVariable(
1434 current_route_min_cumuls_[pos] + pre_travel_transit,
1435 current_route_max_cumuls_[pos + 1] - post_travel_transit -
1436 compressed_travel_value_lower_bound);
1437 SET_DEBUG_VARIABLE_NAME(solver, travel_start,
1438 absl::StrFormat("travel_start(%ld)", pos));
1439 // travel_start = cumul[pos] + pre_travel[pos]
1440 // This becomes: pre_travel[pos] = travel_start - cumul[pos]
1441 solver->AddLinearConstraint(pre_travel_transit, pre_travel_transit,
1442 {{travel_start, 1}, {lp_cumuls[pos], -1}});
1443
1444 // Find segments that are in bounds
1445 // Only the segments in [index_anchor_start, index_anchor_end[ are in
1446 // bounds, the others can therefore be discarded.
1447 const int num_pwl_anchors = travel_x_anchors.size();
1448 int index_anchor_start = 0;
1449 while (index_anchor_start < num_pwl_anchors - 1 &&
1450 travel_x_anchors[index_anchor_start + 1] <=
1451 current_route_min_cumuls_[pos] + pre_travel_transit) {
1452 ++index_anchor_start;
1453 }
1454 int index_anchor_end = num_pwl_anchors - 1;
1455 while (index_anchor_end > 0 &&
1456 travel_x_anchors[index_anchor_end - 1] >=
1457 current_route_max_cumuls_[pos] + pre_travel_transit) {
1458 --index_anchor_end;
1459 }
1460 // Check that there is at least one segment.
1461 if (index_anchor_start >= index_anchor_end) return false;
1462
1463 // Precompute the slopes and y-intercept as they will be used to detect
1464 // convexities and in the constraints.
1465 // This has size index_anchor_end - index_anchor_start.
1466 const std::vector<SlopeAndYIntercept> slope_and_y_intercept =
1468 travel_function, index_anchor_start, index_anchor_end);
1469
1470 // Optimize binary variables by detecting convexities
1471 const std::vector<bool> convexities =
1472 SlopeAndYInterceptToConvexityRegions(slope_and_y_intercept);
1473
1474 int nb_bin_variables = 0;
1475 for (const bool convexity : convexities) {
1476 if (convexity) {
1477 ++nb_bin_variables;
1478 if (nb_bin_variables >= 2) break;
1479 }
1480 }
1481 const bool need_bins = (nb_bin_variables > 1);
1482 // 1.b. Create a constraint so that the start belongs to only one segment
1483 const int travel_start_in_one_segment_ct =
1484 need_bins ? solver->CreateNewConstraint(1, 1)
1485 : -1; // -1 is a placeholder value here
1486
1487 int belongs_to_this_segment_var;
1488 for (int seg = 0; seg < convexities.size(); ++seg) {
1489 if (need_bins && convexities[seg]) {
1490 belongs_to_this_segment_var = solver->AddVariable(0, 1);
1492 solver, belongs_to_this_segment_var,
1493 absl::StrFormat("travel_start(%ld)belongs_to_seg(%ld)", pos, seg));
1494 solver->SetCoefficient(travel_start_in_one_segment_ct,
1495 belongs_to_this_segment_var, 1);
1496
1497 // 1.c. Link the binary variable to the departure time
1498 // If the travel_start value is outside the PWL, the closest segment
1499 // will be used. This is why some bounds are infinite.
1500 const int64_t lower_bound_interval =
1501 seg > 0 ? travel_x_anchors[index_anchor_start + seg]
1502 : current_route_min_cumuls_[pos] + pre_travel_transit;
1503 int64_t end_of_seg = seg + 1;
1504 const int num_convexities = convexities.size();
1505 while (end_of_seg < num_convexities && !convexities[end_of_seg]) {
1506 ++end_of_seg;
1507 }
1508 const int64_t higher_bound_interval =
1509 end_of_seg < num_pwl_anchors - 1
1510 ? travel_x_anchors[index_anchor_start + end_of_seg]
1511 : current_route_max_cumuls_[pos] + pre_travel_transit;
1512 const int travel_start_in_segment_ct = solver->AddLinearConstraint(
1513 lower_bound_interval, higher_bound_interval, {{travel_start, 1}});
1514 solver->SetEnforcementLiteral(travel_start_in_segment_ct,
1515 belongs_to_this_segment_var);
1516 }
1517
1518 // 1.d. Compute the slope and y_intercept, the
1519 // coefficient used in the constraint, for each segment.
1520 const auto [slope, y_intercept] = slope_and_y_intercept[seg];
1521 // Starting later should always mean arriving later
1522 DCHECK_GE(slope, -1.0) << "Travel value PWL should have a slope >= -1";
1523
1524 // 1.e. Define the linearization of travel_value
1525 // travel_value - slope * travel_start[pos] = y_intercept, for each
1526 // segment. In order to have a softer constraint, we only impose:
1527 // travel_value - slope * travel_start[pos] >= y_intercept
1528 // and since the cost is increasing in the travel_value, it will
1529 // Minimize it. In addition, since we are working with integers, we
1530 // add a relaxation of 0.5 which gives:
1531 // travel_value - slope * travel_start[pos] >= y_intercept - 0.5.
1532 const double upper_bound = current_route_max_cumuls_[pos];
1533 const double factor = FindBestScaling(
1534 {1.0, -slope, y_intercept - 0.5}, /*lower_bounds=*/
1535 {static_cast<double>(compressed_travel_value_lower_bound), 0, 1},
1536 /*upper_bounds=*/
1537 {static_cast<double>(travel_value_upper_bound), upper_bound, 1},
1538 /*max_absolute_activity=*/(int64_t{1} << 62),
1539 /*wanted_absolute_activity_precision=*/1e-3);
1540 // If no correct scaling is found, factor can be equal to 0. This will be
1541 // translated as an unfeasible model as, it will not constraint the
1542 // travel_value with a factor of 0.
1543 if (factor <= 0) return false;
1544
1545 const int linearization_ct = solver->AddLinearConstraint(
1546 MathUtil::FastInt64Round(factor * (y_intercept - 0.5)),
1547 std::numeric_limits<int64_t>::max(),
1548 {{travel_value, MathUtil::FastInt64Round(factor)},
1549 {travel_start, MathUtil::FastInt64Round(-factor * slope)}});
1550 if (need_bins) {
1551 solver->SetEnforcementLiteral(linearization_ct,
1552 belongs_to_this_segment_var);
1553 }
1554
1555 // ====== UNCOMMENT TO USE ORDER0 ERROR APPROXIMATION ===== //
1556 // Normally cost_scaled = C₂×(Tᵣ - T)²/Tᵣ
1557 // but here we approximate it as cost_scaled = C₂×(Tᵣ - T)²/Tm
1558 // with Tm the average travel value of this segment.
1559 // Therefore, we compute cost_scaled = cost / Tm
1560 // So the cost_function must be defined as cost = C₂×(Tᵣ - T)²
1561 // const int64_t Tm = (transit_function.y_anchors[seg] +
1562 // transit_function.y_anchors[seg + 1]) / 2; The constraint is
1563 // implemented as: cost_scaled * Tm >= cost const int cost_ct =
1564 // solver->AddLinearConstraint(0, std::numeric_limits<int64_t>::max(),
1565 // {{cost_scaled, Tm}, {cost, -1}});
1566 // solver->SetEnforcementLiteral(cost_ct, belongs_to_this_segment_var);
1567 }
1568
1569 // 2. Create a variable for the compressed_travel_value.
1570 // cumul[pos + 1] = cumul[pos] + slack[pos] + pre_travel_transit[pos] +
1571 // compressed_travel_value[pos] + post_travel_transit[pos] This becomes:
1572 // post_travel_transit[pos] + pre_travel_transit[pos] = cumul[pos + 1] -
1573 // cumul[pos] - slack[pos] - compressed_travel_value[pos] The higher bound
1574 // of compressed_travel_value is already implemented by constraints as
1575 // travel_compression_absolute = travel_value - compressed_travel_value > 0
1576 // (see below) and travel_value has travel_value_upper_bound as an upper
1577 // bound. The bound is added for the sake of clarity and being explicit.
1578 const int compressed_travel_value = solver->AddVariable(
1579 compressed_travel_value_lower_bound, travel_value_upper_bound);
1581 solver, compressed_travel_value,
1582 absl::StrFormat("compressed_travel_value(%ld)", pos));
1583 solver->AddLinearConstraint(post_travel_transit + pre_travel_transit,
1584 post_travel_transit + pre_travel_transit,
1585 {{compressed_travel_value, -1},
1586 {lp_cumuls[pos + 1], 1},
1587 {lp_cumuls[pos], -1},
1588 {lp_slacks[pos], -1}});
1589
1590 // 2. Create the travel value compression variable
1591 // travel_compression_absolute == travel_value - compressed_travel_value
1592 // This becomes: 0 = travel_compression_absolute - travel_value +
1593 // compressed_travel_value travel_compression_absolute must be positive or
1594 // equal to 0.
1595 const int travel_compression_absolute = solver->AddVariable(
1596 0, travel_value_upper_bound - compressed_travel_value_lower_bound);
1598 solver, travel_compression_absolute,
1599 absl::StrFormat("travel_compression_absolute(%ld)", pos));
1600
1601 solver->AddLinearConstraint(0, 0,
1602 {{travel_compression_absolute, 1},
1603 {travel_value, -1},
1604 {compressed_travel_value, 1}});
1605
1606 // 3. Add a cost per unit of travel
1607 // The travel_cost_coefficient is set with the travel_value and not the
1608 // compressed_travel_value to not give the incentive to compress a little
1609 // bit in order to same some cost per travel.
1610 solver->SetObjectiveCoefficient(
1611 travel_value, dimension_travel_info->travel_cost_coefficient);
1612
1613 // 4. Adds a convex cost in epsilon
1614 // Here we DCHECK that the cost function is indeed convex
1615 const FloatSlopePiecewiseLinearFunction& cost_function =
1616 transition_info.travel_compression_cost;
1617 const auto& cost_x_anchors = cost_function.x_anchors();
1618
1619 const std::vector<SlopeAndYIntercept> cost_slope_and_y_intercept =
1621 const double cost_max = cost_function.ComputeConvexValue(
1622 travel_value_upper_bound - compressed_travel_value_lower_bound);
1623 double previous_slope = 0;
1624 for (int seg = 0; seg < cost_x_anchors.size() - 1; ++seg) {
1625 const auto [slope, y_intercept] = cost_slope_and_y_intercept[seg];
1626 // Check convexity
1627 DCHECK_GE(slope, previous_slope)
1628 << "Compression error is not convex. Segment " << (1 + seg)
1629 << " out of " << (cost_x_anchors.size() - 1);
1630 previous_slope = slope;
1631 const double factor = FindBestScaling(
1632 {1.0, -slope, y_intercept}, /*lower_bounds=*/
1633 {0, static_cast<double>(compressed_travel_value_lower_bound), 1},
1634 /*upper_bounds=*/
1635 {cost_max, static_cast<double>(travel_value_upper_bound), 1},
1636 /*max_absolute_activity=*/(static_cast<int64_t>(1) << 62),
1637 /*wanted_absolute_activity_precision=*/1e-3);
1638 // If no correct scaling is found, factor can be equal to 0. This will be
1639 // translated as an unfeasible model as, it will not constraint the
1640 // compression_cost with a factor of 0.
1641 if (factor <= 0) return false;
1642
1643 solver->AddLinearConstraint(
1644 MathUtil::FastInt64Round(factor * y_intercept),
1645 std::numeric_limits<int64_t>::max(),
1646 {{compression_cost, std::round(factor)},
1647 {travel_compression_absolute,
1648 MathUtil::FastInt64Round(-factor * slope)}});
1649 }
1650 // ====== UNCOMMENT TO USE PRODUCT TO COMPUTE THE EXACT ERROR ===== //
1651 // Normally cost_scaled = C₂×(Tᵣ - T)²/Tᵣ
1652 // and here we compute cost_scaled = cost / Tᵣ
1653 // So the cost_function must be defined as cost = C₂×(Tᵣ - T)²
1654 // const int prod = solver->CreateNewPositiveVariable();
1655 // solver->AddProductConstraint(prod, {cost_scaled, travel_value});
1656 // The constraint is implemented as: cost_scaled * Tᵣ >= cost
1657 // solver->AddLinearConstraint(0, std::numeric_limits<int64_t>::max(),
1658 // {{prod, 1}, {cost, -1}});
1659
1660 // ====== UNCOMMENT TO USE AVERAGE ERROR APPROXIMATION ===== //
1661 // Normally cost_scaled = C₂×(Tᵣ - T)²/Tᵣ
1662 // but here we approximate it as cost_scaled = C₂×(Tᵣ - T)²/Tₐ
1663 // with Tₐ the average travel value (on all the segments).
1664 // Since we do not have access to Tₐ here, we define cost_scaled as
1665 // cost_scaled = cost. So the cost_function must be defined as cost =
1666 // C₂×(Tᵣ - T)²/Tₐ The constraint is implemented as: cost_scaled >= cost
1667 solver->AddLinearConstraint(
1668 0, std::numeric_limits<int64_t>::max(),
1669 {{relative_compression_cost, 1}, {compression_cost, -1}});
1670
1671 solver->SetObjectiveCoefficient(relative_compression_cost, 1.0);
1672 }
1673 return true;
1674}
1675
1676namespace {
1677bool RouteIsValid(const RoutingModel& model, int vehicle,
1678 const std::function<int64_t(int64_t)>& next_accessor) {
1679 absl::flat_hash_set<int64_t> visited;
1680 int node = model.Start(vehicle);
1681 visited.insert(node);
1682 while (!model.IsEnd(node)) {
1683 node = next_accessor(node);
1684 if (visited.contains(node)) return false;
1685 visited.insert(node);
1686 }
1687 return visited.size() >= 2;
1688}
1689} // namespace
1690
1691bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
1692 int vehicle, const std::function<int64_t(int64_t)>& next_accessor,
1693 const std::function<int64_t(int64_t, int64_t)>& transit_accessor,
1694 const RouteDimensionTravelInfo* dimension_travel_info, int64_t cumul_offset,
1695 bool optimize_costs, RoutingLinearSolverWrapper* solver,
1696 int64_t* route_transit_cost, int64_t* route_cost_offset) {
1697 RoutingModel* const model = dimension_->model();
1698 // Extract the vehicle's path from next_accessor.
1699 std::vector<int64_t> path;
1700 {
1701 DCHECK(RouteIsValid(*model, vehicle, next_accessor));
1702 int node = model->Start(vehicle);
1703 path.push_back(node);
1704 while (!model->IsEnd(node)) {
1705 node = next_accessor(node);
1706 path.push_back(node);
1707 }
1708 DCHECK_GE(path.size(), 2);
1709 }
1710 const int path_size = path.size();
1711
1712 std::vector<int64_t> fixed_transit(path_size - 1);
1713 int64_t total_fixed_transit = 0;
1714 {
1715 for (int pos = 1; pos < path_size; ++pos) {
1716 int64_t& transit = fixed_transit[pos - 1];
1717 transit = transit_accessor(path[pos - 1], path[pos]);
1718 total_fixed_transit = CapAdd(total_fixed_transit, transit);
1719 }
1720 }
1721 if (!ExtractRouteCumulBounds(path, cumul_offset)) {
1722 return false;
1723 }
1724 if (dimension_travel_info == nullptr ||
1725 dimension_travel_info->transition_info.empty()) {
1726 if (!TightenRouteCumulBounds(path, fixed_transit, cumul_offset)) {
1727 return false;
1728 }
1729 } else {
1730 // Tighten the bounds with the lower bound of the transit value
1731 std::vector<int64_t> min_transit(path_size - 1);
1732 for (int pos = 0; pos < path_size - 1; ++pos) {
1733 const RouteDimensionTravelInfo::TransitionInfo& transition =
1734 dimension_travel_info->transition_info[pos];
1735 min_transit[pos] = transition.pre_travel_transit_value +
1736 transition.compressed_travel_value_lower_bound +
1737 transition.post_travel_transit_value;
1738 }
1739 if (!TightenRouteCumulBounds(path, min_transit, cumul_offset)) {
1740 return false;
1741 }
1742 }
1743
1744 // LP Model variables, current_route_cumul_variables_ and lp_slacks.
1745 // Create LP variables for cumuls.
1746 std::vector<int>& lp_cumuls = current_route_cumul_variables_;
1747 lp_cumuls.assign(path_size, -1);
1748 for (int pos = 0; pos < path_size; ++pos) {
1749 const int lp_cumul = solver->CreateNewPositiveVariable();
1750 SET_DEBUG_VARIABLE_NAME(solver, lp_cumul,
1751 absl::StrFormat("lp_cumul(%ld)", pos));
1752 index_to_cumul_variable_[path[pos]] = lp_cumul;
1753 lp_cumuls[pos] = lp_cumul;
1754 if (!solver->SetVariableBounds(lp_cumul, current_route_min_cumuls_[pos],
1755 current_route_max_cumuls_[pos])) {
1756 return false;
1757 }
1758 const SortedDisjointIntervalList& forbidden =
1759 dimension_->forbidden_intervals()[path[pos]];
1760 if (forbidden.NumIntervals() > 0) {
1761 std::vector<int64_t> starts;
1762 std::vector<int64_t> ends;
1763 for (const ClosedInterval interval :
1764 dimension_->GetAllowedIntervalsInRange(
1765 path[pos], CapAdd(current_route_min_cumuls_[pos], cumul_offset),
1766 CapAdd(current_route_max_cumuls_[pos], cumul_offset))) {
1767 starts.push_back(CapSub(interval.start, cumul_offset));
1768 ends.push_back(CapSub(interval.end, cumul_offset));
1769 }
1770 solver->SetVariableDisjointBounds(lp_cumul, starts, ends);
1771 }
1772 }
1773 solver->AddRoute(path, lp_cumuls);
1774 // Create LP variables for slacks.
1775 std::vector<int> lp_slacks(path_size - 1, -1);
1776 for (int pos = 0; pos < path_size - 1; ++pos) {
1777 const IntVar* cp_slack = dimension_->SlackVar(path[pos]);
1778 lp_slacks[pos] = solver->CreateNewPositiveVariable();
1779 SET_DEBUG_VARIABLE_NAME(solver, lp_slacks[pos],
1780 absl::StrFormat("lp_slacks(%ld)", pos));
1781 if (!solver->SetVariableBounds(lp_slacks[pos], cp_slack->Min(),
1782 cp_slack->Max())) {
1783 return false;
1784 }
1785 }
1786
1787 if (!SetRouteTravelConstraints(dimension_travel_info, lp_slacks,
1788 fixed_transit, solver)) {
1789 return false;
1790 }
1791
1792 if (route_cost_offset != nullptr) *route_cost_offset = 0;
1793 if (optimize_costs) {
1794 // Add soft upper bounds.
1795 for (int pos = 0; pos < path_size; ++pos) {
1796 if (!dimension_->HasCumulVarSoftUpperBound(path[pos])) continue;
1797 const int64_t coef =
1798 dimension_->GetCumulVarSoftUpperBoundCoefficient(path[pos]);
1799 if (coef == 0) continue;
1800 int64_t bound = dimension_->GetCumulVarSoftUpperBound(path[pos]);
1801 if (bound < cumul_offset && route_cost_offset != nullptr) {
1802 // Add coef * (cumul_offset - bound) to the cost offset.
1803 *route_cost_offset = CapAdd(*route_cost_offset,
1804 CapProd(CapSub(cumul_offset, bound), coef));
1805 }
1806 bound = std::max<int64_t>(0, CapSub(bound, cumul_offset));
1807 if (current_route_max_cumuls_[pos] <= bound) {
1808 // constraint is never violated.
1809 continue;
1810 }
1811 const int soft_ub_diff = solver->CreateNewPositiveVariable();
1812 SET_DEBUG_VARIABLE_NAME(solver, soft_ub_diff,
1813 absl::StrFormat("soft_ub_diff(%ld)", pos));
1814 solver->SetObjectiveCoefficient(soft_ub_diff, coef);
1815 // cumul - soft_ub_diff <= bound.
1816 const int ct = solver->CreateNewConstraint(
1817 std::numeric_limits<int64_t>::min(), bound);
1818 solver->SetCoefficient(ct, lp_cumuls[pos], 1);
1819 solver->SetCoefficient(ct, soft_ub_diff, -1);
1820 }
1821 // Add soft lower bounds.
1822 for (int pos = 0; pos < path_size; ++pos) {
1823 if (!dimension_->HasCumulVarSoftLowerBound(path[pos])) continue;
1824 const int64_t coef =
1825 dimension_->GetCumulVarSoftLowerBoundCoefficient(path[pos]);
1826 if (coef == 0) continue;
1827 const int64_t bound = std::max<int64_t>(
1828 0, CapSub(dimension_->GetCumulVarSoftLowerBound(path[pos]),
1829 cumul_offset));
1830 if (current_route_min_cumuls_[pos] >= bound) {
1831 // constraint is never violated.
1832 continue;
1833 }
1834 const int soft_lb_diff = solver->CreateNewPositiveVariable();
1835 SET_DEBUG_VARIABLE_NAME(solver, soft_lb_diff,
1836 absl::StrFormat("soft_lb_diff(%ld)", pos));
1837 solver->SetObjectiveCoefficient(soft_lb_diff, coef);
1838 // bound - cumul <= soft_lb_diff
1839 const int ct = solver->CreateNewConstraint(
1840 bound, std::numeric_limits<int64_t>::max());
1841 solver->SetCoefficient(ct, lp_cumuls[pos], 1);
1842 solver->SetCoefficient(ct, soft_lb_diff, 1);
1843 }
1844 }
1845 // Add pickup and delivery limits.
1846 std::vector<int> visited_pairs;
1847 StoreVisitedPickupDeliveryPairsOnRoute(
1848 *dimension_, vehicle, next_accessor, &visited_pairs,
1849 &visited_pickup_delivery_indices_for_pair_);
1850 for (int pair_index : visited_pairs) {
1851 const int64_t pickup_index =
1852 visited_pickup_delivery_indices_for_pair_[pair_index].first;
1853 const int64_t delivery_index =
1854 visited_pickup_delivery_indices_for_pair_[pair_index].second;
1855 visited_pickup_delivery_indices_for_pair_[pair_index] = {-1, -1};
1856
1857 DCHECK_GE(pickup_index, 0);
1858 if (delivery_index < 0) {
1859 // We didn't encounter a delivery for this pickup.
1860 continue;
1861 }
1862
1863 const int64_t limit = dimension_->GetPickupToDeliveryLimitForPair(
1864 pair_index, model->GetPickupPosition(pickup_index)->alternative_index,
1865 model->GetDeliveryPosition(delivery_index)->alternative_index);
1866 if (limit < std::numeric_limits<int64_t>::max()) {
1867 // delivery_cumul - pickup_cumul <= limit.
1868 const int ct = solver->CreateNewConstraint(
1869 std::numeric_limits<int64_t>::min(), limit);
1870 solver->SetCoefficient(ct, index_to_cumul_variable_[delivery_index], 1);
1871 solver->SetCoefficient(ct, index_to_cumul_variable_[pickup_index], -1);
1872 }
1873 }
1874
1875 // Add span bound constraint.
1876 const int64_t span_bound = dimension_->GetSpanUpperBoundForVehicle(vehicle);
1877 if (span_bound < std::numeric_limits<int64_t>::max()) {
1878 // end_cumul - start_cumul <= bound
1879 const int ct = solver->CreateNewConstraint(
1880 std::numeric_limits<int64_t>::min(), span_bound);
1881 solver->SetCoefficient(ct, lp_cumuls.back(), 1);
1882 solver->SetCoefficient(ct, lp_cumuls.front(), -1);
1883 }
1884 // Add span and slack costs.
1885 // NOTE: The fixed transit is removed from the span cost since it doesn't
1886 // affect the optimization of the scheduling of the route.
1887 const int64_t span_cost_coef =
1888 dimension_->GetSpanCostCoefficientForVehicle(vehicle);
1889 const int64_t slack_cost_coef = CapAdd(
1890 span_cost_coef, dimension_->GetSlackCostCoefficientForVehicle(vehicle));
1891 if (optimize_costs && slack_cost_coef > 0) {
1892 // span_without_fixed_transit_var =
1893 // end_cumul - start_cumul - total_fixed_transit
1894 const int span_without_fixed_transit_var =
1895 solver->CreateNewPositiveVariable();
1896 SET_DEBUG_VARIABLE_NAME(solver, span_without_fixed_transit_var,
1897 "span_without_fixed_transit_var");
1898 solver->AddLinearConstraint(total_fixed_transit, total_fixed_transit,
1899 {{lp_cumuls.back(), 1},
1900 {lp_cumuls.front(), -1},
1901 {span_without_fixed_transit_var, -1}});
1902 solver->SetObjectiveCoefficient(span_without_fixed_transit_var,
1903 slack_cost_coef);
1904 }
1905 // Add soft span cost.
1906 if (optimize_costs && dimension_->HasSoftSpanUpperBounds()) {
1907 const BoundCost bound_cost =
1908 dimension_->GetSoftSpanUpperBoundForVehicle(vehicle);
1909 if (bound_cost.bound < std::numeric_limits<int64_t>::max() &&
1910 bound_cost.cost > 0) {
1911 const int span_violation = solver->CreateNewPositiveVariable();
1912 SET_DEBUG_VARIABLE_NAME(solver, span_violation, "span_violation");
1913 // end - start <= bound + span_violation
1914 const int violation = solver->CreateNewConstraint(
1915 std::numeric_limits<int64_t>::min(), bound_cost.bound);
1916 solver->SetCoefficient(violation, lp_cumuls.back(), 1.0);
1917 solver->SetCoefficient(violation, lp_cumuls.front(), -1.0);
1918 solver->SetCoefficient(violation, span_violation, -1.0);
1919 // Add span_violation * cost to objective.
1920 solver->SetObjectiveCoefficient(span_violation, bound_cost.cost);
1921 }
1922 }
1923 if (optimize_costs && solver->IsCPSATSolver() &&
1924 dimension_->HasQuadraticCostSoftSpanUpperBounds()) {
1925 // NOTE: the quadratic soft bound might be different from the one above.
1926 const BoundCost bound_cost =
1927 dimension_->GetQuadraticCostSoftSpanUpperBoundForVehicle(vehicle);
1928 if (bound_cost.bound < std::numeric_limits<int64_t>::max() &&
1929 bound_cost.cost > 0) {
1930 const int span_violation = solver->CreateNewPositiveVariable();
1932 solver, span_violation,
1933 absl::StrFormat("quadratic_span_violation(%ld)", vehicle));
1934 // end - start <= bound + span_violation
1935 const int violation = solver->CreateNewConstraint(
1936 std::numeric_limits<int64_t>::min(), bound_cost.bound);
1937 solver->SetCoefficient(violation, lp_cumuls.back(), 1.0);
1938 solver->SetCoefficient(violation, lp_cumuls.front(), -1.0);
1939 solver->SetCoefficient(violation, span_violation, -1.0);
1940 // Add variable squared_span_violation, equal to span_violation².
1941 const int squared_span_violation = solver->CreateNewPositiveVariable();
1943 solver, squared_span_violation,
1944 absl::StrFormat("squared_span_violation(%ld)", vehicle));
1945 solver->AddProductConstraint(squared_span_violation,
1946 {span_violation, span_violation});
1947 // Add squared_span_violation * cost to objective.
1948 solver->SetObjectiveCoefficient(squared_span_violation, bound_cost.cost);
1949 }
1950 }
1951 // Add global span constraint.
1952 if (optimize_costs && dimension_->global_span_cost_coefficient() > 0) {
1953 // min_start_cumul_ <= cumuls[start]
1954 int ct =
1955 solver->CreateNewConstraint(std::numeric_limits<int64_t>::min(), 0);
1956 solver->SetCoefficient(ct, min_start_cumul_, 1);
1957 solver->SetCoefficient(ct, lp_cumuls.front(), -1);
1958 // max_end_cumul_ >= cumuls[end]
1959 ct = solver->CreateNewConstraint(0, std::numeric_limits<int64_t>::max());
1960 solver->SetCoefficient(ct, max_end_cumul_, 1);
1961 solver->SetCoefficient(ct, lp_cumuls.back(), -1);
1962 }
1963 // Fill transit cost if specified.
1964 if (route_transit_cost != nullptr) {
1965 if (optimize_costs && span_cost_coef > 0) {
1966 *route_transit_cost = CapProd(total_fixed_transit, span_cost_coef);
1967 } else {
1968 *route_transit_cost = 0;
1969 }
1970 }
1971 // For every break that must be inside the route, the duration of that break
1972 // must be flowed in the slacks of arcs that can intersect the break.
1973 // This LP modelization is correct but not complete:
1974 // can miss some cases where the breaks cannot fit.
1975 // TODO(user): remove the need for returns in the code below.
1976 current_route_break_variables_.clear();
1977 if (!dimension_->HasBreakConstraints()) return true;
1978 const std::vector<IntervalVar*>& breaks =
1979 dimension_->GetBreakIntervalsOfVehicle(vehicle);
1980 const int num_breaks = breaks.size();
1981 // When there are no breaks, only break distance needs to be modeled,
1982 // and it reduces to a span maximum.
1983 // TODO(user): Also add the case where no breaks can intersect the route.
1984 if (num_breaks == 0) {
1985 int64_t maximum_route_span = std::numeric_limits<int64_t>::max();
1986 for (const auto& distance_duration :
1987 dimension_->GetBreakDistanceDurationOfVehicle(vehicle)) {
1988 maximum_route_span =
1989 std::min(maximum_route_span, distance_duration.first);
1990 }
1991 if (maximum_route_span < std::numeric_limits<int64_t>::max()) {
1992 const int ct = solver->CreateNewConstraint(
1993 std::numeric_limits<int64_t>::min(), maximum_route_span);
1994 solver->SetCoefficient(ct, lp_cumuls.back(), 1);
1995 solver->SetCoefficient(ct, lp_cumuls.front(), -1);
1996 }
1997 return true;
1998 }
1999 // Gather visit information: the visit of node i has [start, end) =
2000 // [cumul[i] - post_travel[i-1], cumul[i] + pre_travel[i]).
2001 // Breaks cannot overlap those visit intervals.
2002 std::vector<int64_t> pre_travel(path_size - 1, 0);
2003 std::vector<int64_t> post_travel(path_size - 1, 0);
2004 {
2005 const int pre_travel_index =
2006 dimension_->GetPreTravelEvaluatorOfVehicle(vehicle);
2007 if (pre_travel_index != -1) {
2008 FillPathEvaluation(path, model->TransitCallback(pre_travel_index),
2009 &pre_travel);
2010 }
2011 const int post_travel_index =
2012 dimension_->GetPostTravelEvaluatorOfVehicle(vehicle);
2013 if (post_travel_index != -1) {
2014 FillPathEvaluation(path, model->TransitCallback(post_travel_index),
2015 &post_travel);
2016 }
2017 }
2018 // If the solver is CPSAT, it will need to represent the times at which
2019 // breaks are scheduled, those variables are used both in the pure breaks
2020 // part and in the break distance part of the model.
2021 // Otherwise, it doesn't need the variables and they are not created.
2022 struct LpBreak {
2023 int start;
2024 int end;
2025 int duration;
2026 };
2027 std::vector<LpBreak> lp_breaks;
2028 if (solver->IsCPSATSolver()) {
2029 lp_breaks.resize(num_breaks, {.start = -1, .end = -1, .duration = -1});
2030 }
2031
2032 std::vector<int> slack_exact_lower_bound_ct(path_size - 1, -1);
2033 std::vector<int> slack_linear_lower_bound_ct(path_size - 1, -1);
2034
2035 const int64_t vehicle_start_min = current_route_min_cumuls_.front();
2036 const int64_t vehicle_start_max = current_route_max_cumuls_.front();
2037 const int64_t vehicle_end_min = current_route_min_cumuls_.back();
2038 const int64_t vehicle_end_max = current_route_max_cumuls_.back();
2039 const int all_break_variables_offset =
2040 vehicle_to_all_break_variables_offset_[vehicle];
2041 for (int br = 0; br < num_breaks; ++br) {
2042 const IntervalVar& break_var = *breaks[br];
2043 if (!break_var.MustBePerformed()) continue;
2044 const int64_t break_start_min = CapSub(break_var.StartMin(), cumul_offset);
2045 const int64_t break_start_max = CapSub(break_var.StartMax(), cumul_offset);
2046 const int64_t break_end_min = CapSub(break_var.EndMin(), cumul_offset);
2047 const int64_t break_end_max = CapSub(break_var.EndMax(), cumul_offset);
2048 const int64_t break_duration_min = break_var.DurationMin();
2049 const int64_t break_duration_max = break_var.DurationMax();
2050 // The CPSAT solver encodes all breaks that can intersect the route,
2051 // the LP solver only encodes the breaks that must intersect the route.
2052 if (solver->IsCPSATSolver()) {
2053 if (break_end_max <= vehicle_start_min ||
2054 vehicle_end_max <= break_start_min) {
2055 all_break_variables_[all_break_variables_offset + 2 * br] = -1;
2056 all_break_variables_[all_break_variables_offset + 2 * br + 1] = -1;
2057 current_route_break_variables_.push_back(-1);
2058 current_route_break_variables_.push_back(-1);
2059 continue;
2060 }
2061 lp_breaks[br] = {
2062 .start = solver->AddVariable(break_start_min, break_start_max),
2063 .end = solver->AddVariable(break_end_min, break_end_max),
2064 .duration =
2065 solver->AddVariable(break_duration_min, break_duration_max)};
2066 const auto [break_start, break_end, break_duration] = lp_breaks[br];
2067 SET_DEBUG_VARIABLE_NAME(solver, break_start,
2068 absl::StrFormat("lp_break_start(%ld)", br));
2069 SET_DEBUG_VARIABLE_NAME(solver, break_end,
2070 absl::StrFormat("lp_break_end(%ld)", br));
2071 SET_DEBUG_VARIABLE_NAME(solver, break_duration,
2072 absl::StrFormat("lp_break_duration(%ld)", br));
2073 // start + duration = end.
2074 solver->AddLinearConstraint(
2075 0, 0, {{break_end, 1}, {break_start, -1}, {break_duration, -1}});
2076 // Record index of variables
2077 all_break_variables_[all_break_variables_offset + 2 * br] = break_start;
2078 all_break_variables_[all_break_variables_offset + 2 * br + 1] = break_end;
2079 current_route_break_variables_.push_back(break_start);
2080 current_route_break_variables_.push_back(break_end);
2081 } else {
2082 if (break_end_min <= vehicle_start_max ||
2083 vehicle_end_min <= break_start_max) {
2084 all_break_variables_[all_break_variables_offset + 2 * br] = -1;
2085 all_break_variables_[all_break_variables_offset + 2 * br + 1] = -1;
2086 current_route_break_variables_.push_back(-1);
2087 current_route_break_variables_.push_back(-1);
2088 continue;
2089 }
2090 }
2091
2092 // Create a constraint for every break, that forces it to be scheduled
2093 // in exactly one place, i.e. one slack or before/after the route.
2094 // sum_i break_in_slack_i == 1.
2095 const int break_in_one_slack_ct = solver->CreateNewConstraint(1, 1);
2096
2097 if (solver->IsCPSATSolver()) {
2098 // Break can be before route.
2099 if (break_end_min <= vehicle_start_max) {
2100 const int ct = solver->AddLinearConstraint(
2101 0, std::numeric_limits<int64_t>::max(),
2102 {{lp_cumuls.front(), 1}, {lp_breaks[br].end, -1}});
2103 const int break_is_before_route = solver->AddVariable(0, 1);
2105 solver, break_is_before_route,
2106 absl::StrFormat("break_is_before_route(%ld)", br));
2107 solver->SetEnforcementLiteral(ct, break_is_before_route);
2108 solver->SetCoefficient(break_in_one_slack_ct, break_is_before_route, 1);
2109 }
2110 // Break can be after route.
2111 if (vehicle_end_min <= break_start_max) {
2112 const int ct = solver->AddLinearConstraint(
2113 0, std::numeric_limits<int64_t>::max(),
2114 {{lp_breaks[br].start, 1}, {lp_cumuls.back(), -1}});
2115 const int break_is_after_route = solver->AddVariable(0, 1);
2117 solver, break_is_after_route,
2118 absl::StrFormat("break_is_after_route(%ld)", br));
2119 solver->SetEnforcementLiteral(ct, break_is_after_route);
2120 solver->SetCoefficient(break_in_one_slack_ct, break_is_after_route, 1);
2121 }
2122 }
2123
2124 // Add the possibility of fitting the break during each slack where it can.
2125 for (int pos = 0; pos < path_size - 1; ++pos) {
2126 // Pass on slacks that cannot start before, cannot end after,
2127 // or are not long enough to contain the break.
2128 const int64_t slack_start_min =
2129 CapAdd(current_route_min_cumuls_[pos], pre_travel[pos]);
2130 if (slack_start_min > break_start_max) break;
2131 const int64_t slack_end_max =
2132 CapSub(current_route_max_cumuls_[pos + 1], post_travel[pos]);
2133 if (break_end_min > slack_end_max) continue;
2134 const int64_t slack_duration_max =
2135 std::min(CapSub(CapSub(current_route_max_cumuls_[pos + 1],
2136 current_route_min_cumuls_[pos]),
2137 fixed_transit[pos]),
2138 dimension_->SlackVar(path[pos])->Max());
2139 if (slack_duration_max < break_duration_min) continue;
2140
2141 // Break can fit into slack: make LP variable, add to break and slack
2142 // constraints.
2143 // Make a linearized slack lower bound (lazily), that represents
2144 // sum_br break_duration_min(br) * break_in_slack(br, pos) <=
2145 // lp_slacks(pos).
2146 const int break_in_slack = solver->AddVariable(0, 1);
2148 solver, break_in_slack,
2149 absl::StrFormat("break_in_slack(%ld, %ld)", br, pos));
2150 if (slack_linear_lower_bound_ct[pos] == -1) {
2151 slack_linear_lower_bound_ct[pos] = solver->AddLinearConstraint(
2152 std::numeric_limits<int64_t>::min(), 0, {{lp_slacks[pos], -1}});
2153 }
2154 // To keep the model clean
2155 // (cf. glop::LinearProgram::NotifyThatColumnsAreClean), constraints on
2156 // break_in_slack need to be in ascending order.
2157 if (break_in_one_slack_ct < slack_linear_lower_bound_ct[pos]) {
2158 solver->SetCoefficient(break_in_one_slack_ct, break_in_slack, 1);
2159 solver->SetCoefficient(slack_linear_lower_bound_ct[pos], break_in_slack,
2160 break_duration_min);
2161 } else {
2162 solver->SetCoefficient(slack_linear_lower_bound_ct[pos], break_in_slack,
2163 break_duration_min);
2164 solver->SetCoefficient(break_in_one_slack_ct, break_in_slack, 1);
2165 }
2166
2167 if (solver->IsCPSATSolver()) {
2168 // Exact relation between breaks, slacks and cumul variables.
2169 // Make an exact slack lower bound (lazily), that represents
2170 // sum_br break_duration(br) * break_in_slack(br, pos) <=
2171 // lp_slacks(pos).
2172 const int break_duration_in_slack =
2173 solver->AddVariable(0, slack_duration_max);
2175 solver, break_duration_in_slack,
2176 absl::StrFormat("break_duration_in_slack(%ld, %ld)", br, pos));
2177 solver->AddProductConstraint(break_duration_in_slack,
2178 {break_in_slack, lp_breaks[br].duration});
2179 if (slack_exact_lower_bound_ct[pos] == -1) {
2180 slack_exact_lower_bound_ct[pos] = solver->AddLinearConstraint(
2181 std::numeric_limits<int64_t>::min(), 0, {{lp_slacks[pos], -1}});
2182 }
2183 solver->SetCoefficient(slack_exact_lower_bound_ct[pos],
2184 break_duration_in_slack, 1);
2185 // If break_in_slack_i == 1, then
2186 // 1) break_start >= cumul[pos] + pre_travel[pos]
2187 const int break_start_after_current_ct = solver->AddLinearConstraint(
2188 pre_travel[pos], std::numeric_limits<int64_t>::max(),
2189 {{lp_breaks[br].start, 1}, {lp_cumuls[pos], -1}});
2190 solver->SetEnforcementLiteral(break_start_after_current_ct,
2191 break_in_slack);
2192 // 2) break_end <= cumul[pos+1] - post_travel[pos]
2193 const int break_ends_before_next_ct = solver->AddLinearConstraint(
2194 post_travel[pos], std::numeric_limits<int64_t>::max(),
2195 {{lp_cumuls[pos + 1], 1}, {lp_breaks[br].end, -1}});
2196 solver->SetEnforcementLiteral(break_ends_before_next_ct,
2197 break_in_slack);
2198 }
2199 }
2200 }
2201
2202 for (const auto& distance_duration :
2203 dimension_->GetBreakDistanceDurationOfVehicle(vehicle)) {
2204 const int64_t limit = distance_duration.first;
2205 const int64_t min_break_duration = distance_duration.second;
2206 int64_t min_num_breaks = 0;
2207 if (limit > 0) {
2208 min_num_breaks =
2209 std::max<int64_t>(0, CapSub(total_fixed_transit, 1) / limit);
2210 }
2211 if (CapSub(current_route_min_cumuls_.back(),
2212 current_route_max_cumuls_.front()) > limit) {
2213 min_num_breaks = std::max<int64_t>(min_num_breaks, 1);
2214 }
2215 if (num_breaks < min_num_breaks) return false;
2216 if (min_num_breaks == 0) continue;
2217
2218 // Adds an LP relaxation of interbreak constraints.
2219 // For all 0 <= pl < pr < path_size, for k > 0,
2220 // if sum_{p in [pl, pr)} fixed_transit[p] > k * limit,
2221 // then sum_{p in [pl, pr)} slack[p] >= k * min_break_duration.
2222 //
2223 // Moreover, if end_min[pr] - start_max[pl] > limit,
2224 // the sum_{p in [pl, pr)} slack[p] >= min_break_duration.
2225 //
2226 // We want to apply the constraints above, without the ones that are
2227 // dominated:
2228 // - do not add the same constraint for k' < k, keep the largest k.
2229 // - do not add the constraint for both (pl', pr') and (pl, pr)
2230 // if [pl', pr') is a subset of [pl, pr), keep the smallest interval.
2231 // TODO(user): reduce the number of constraints further;
2232 // for instance if the constraint holds for (k, pl, pr) and (k', pl', pr')
2233 // with pr <= pr', then no need to add the constraint for (k+k', pl, pr').
2234 //
2235 // We need fast access to sum_{p in [pl, pr)} fixed_transit[p].
2236 // This will be sum_transits[pr] - sum_transits[pl]. Note that
2237 // sum_transits[0] = 0, sum_transits[path_size-1] = total_fixed_transit.
2238 std::vector<int64_t> sum_transits(path_size);
2239 {
2240 sum_transits[0] = 0;
2241 for (int pos = 1; pos < path_size; ++pos) {
2242 sum_transits[pos] = sum_transits[pos - 1] + fixed_transit[pos - 1];
2243 }
2244 }
2245 // To add the slack sum constraints, we need slack sum variables.
2246 // Those are created lazily in a sparse vector, then only those useful
2247 // variables are linked to slack variables after slack sum constraints
2248 // have been added.
2249 std::vector<int> slack_sum_vars(path_size, -1);
2250 // Given a number of breaks k, an interval of path positions [pl, pr),
2251 // returns true if the interbreak constraint triggers for k breaks.
2252 // TODO(user): find tighter end_min/start_max conditions.
2253 // Mind that a break may be longer than min_break_duration.
2254 auto trigger = [&](int k, int pl, int pr) -> bool {
2255 const int64_t r_pre_travel = pr < path_size - 1 ? pre_travel[pr] : 0;
2256 const int64_t l_post_travel = pl >= 1 ? post_travel[pl - 1] : 0;
2257 const int64_t extra_travel = CapAdd(r_pre_travel, l_post_travel);
2258 if (k == 1) {
2259 const int64_t span_lb = CapAdd(CapSub(current_route_min_cumuls_[pr],
2260 current_route_max_cumuls_[pl]),
2261 extra_travel);
2262 if (span_lb > limit) return true;
2263 }
2264 return CapAdd(CapSub(sum_transits[pr], sum_transits[pl]), extra_travel) >
2265 CapProd(k, limit);
2266 };
2267 int min_sum_var_index = path_size;
2268 int max_sum_var_index = -1;
2269 for (int k = 1; k <= min_num_breaks; ++k) {
2270 int pr = 0;
2271 for (int pl = 0; pl < path_size - 1; ++pl) {
2272 pr = std::max(pr, pl + 1);
2273 // Increase pr until transit(pl, pr) > k * limit.
2274 while (pr < path_size && !trigger(k, pl, pr)) ++pr;
2275 if (pr == path_size) break;
2276 // Reduce [pl, pr) from the left.
2277 while (pl < pr && trigger(k, pl + 1, pr)) ++pl;
2278 // If pl == pr, then post_travel[pl-l] + pre_travel[pl] > limit.
2279 // This is infeasible, because breaks cannot interrupt visits.
2280 if (pl == pr) return false;
2281 // If k is the largest for this interval, add the constraint.
2282 // The call trigger(k', pl, pr) may hold for both k and k+1 with an
2283 // irreducible interval [pl, pr] when there is a transit > limit at
2284 // the beginning and at the end of the sub-route.
2285 if (k < min_num_breaks && trigger(k + 1, pl, pr)) continue;
2286 // If the solver is CPSAT, experimentation showed that adding slack sum
2287 // constraints made solves worse than doing nothing, and that adding
2288 // these lightweight constraints works better.
2289 if (solver->IsCPSATSolver()) {
2290 // lp_cumuls[pl] + transit(pl, pr) + k * min_break_duration <=
2291 // lp_cumuls[pr].
2292 solver->AddLinearConstraint(
2293 CapAdd(CapSub(sum_transits[pr], sum_transits[pl]),
2294 CapProd(k, min_break_duration)),
2295 kint64max, {{lp_cumuls[pr], 1}, {lp_cumuls[pl], -1}});
2296 } else {
2297 if (slack_sum_vars[pl] == -1) {
2298 slack_sum_vars[pl] = solver->CreateNewPositiveVariable();
2299 SET_DEBUG_VARIABLE_NAME(solver, slack_sum_vars[pl],
2300 absl::StrFormat("slack_sum_vars(%ld)", pl));
2301 min_sum_var_index = std::min(min_sum_var_index, pl);
2302 }
2303 if (slack_sum_vars[pr] == -1) {
2304 slack_sum_vars[pr] = solver->CreateNewPositiveVariable();
2305 SET_DEBUG_VARIABLE_NAME(solver, slack_sum_vars[pr],
2306 absl::StrFormat("slack_sum_vars(%ld)", pr));
2307 max_sum_var_index = std::max(max_sum_var_index, pr);
2308 }
2309 // sum_slacks[pr] - sum_slacks[pl] >= k * min_break_duration.
2310 solver->AddLinearConstraint(
2311 CapProd(k, min_break_duration), kint64max,
2312 {{slack_sum_vars[pr], 1}, {slack_sum_vars[pl], -1}});
2313 }
2314 }
2315 }
2316 if (min_sum_var_index < max_sum_var_index) {
2317 slack_sum_vars[min_sum_var_index] = solver->AddVariable(0, 0);
2318 int prev_index = min_sum_var_index;
2319 for (int pos = min_sum_var_index + 1; pos <= max_sum_var_index; ++pos) {
2320 if (slack_sum_vars[pos] == -1) continue;
2321 // slack_sum_var[pos] =
2322 // slack_sum_var[prev_index] + sum_{p in [prev_index, pos)} slack[p].
2323 const int ct = solver->AddLinearConstraint(
2324 0, 0, {{slack_sum_vars[pos], 1}, {slack_sum_vars[prev_index], -1}});
2325 for (int p = prev_index; p < pos; ++p) {
2326 solver->SetCoefficient(ct, lp_slacks[p], -1);
2327 }
2328 prev_index = pos;
2329 }
2330 }
2331 }
2332 if (!solver->IsCPSATSolver()) return true;
2333 if (!dimension_->GetBreakDistanceDurationOfVehicle(vehicle).empty()) {
2334 // If there is an optional interval, the following model would be wrong.
2335 // TODO(user): support optional intervals.
2336 for (const IntervalVar* interval :
2337 dimension_->GetBreakIntervalsOfVehicle(vehicle)) {
2338 if (!interval->MustBePerformed()) return true;
2339 }
2340 // When this feature is used, breaks are in sorted order.
2341 for (int br = 1; br < num_breaks; ++br) {
2342 if (lp_breaks[br].start == -1 || lp_breaks[br - 1].start == -1) continue;
2343 solver->AddLinearConstraint(
2344 0, std::numeric_limits<int64_t>::max(),
2345 {{lp_breaks[br - 1].end, -1}, {lp_breaks[br].start, 1}});
2346 }
2347 }
2348 for (const auto& distance_duration :
2349 dimension_->GetBreakDistanceDurationOfVehicle(vehicle)) {
2350 const int64_t limit = distance_duration.first;
2351 const int64_t min_break_duration = distance_duration.second;
2352 // Interbreak limit constraint: breaks are interpreted as being in sorted
2353 // order, and the maximum duration between two consecutive
2354 // breaks of duration more than 'min_break_duration' is 'limit'. This
2355 // considers the time until start of route and after end of route to be
2356 // infinite breaks.
2357 // The model for this constraint adds some 'cover_i' variables, such that
2358 // the breaks up to i and the start of route allows to go without a break.
2359 // With s_i the start of break i and e_i its end:
2360 // - the route start covers time from start to start + limit:
2361 // cover_0 = route_start + limit
2362 // - the coverage up to a given break is the largest of the coverage of the
2363 // previous break and if the break is long enough, break end + limit:
2364 // cover_{i+1} = max(cover_i,
2365 // e_i - s_i >= min_break_duration ? e_i + limit : -inf)
2366 // - the coverage of the last break must be at least the route end,
2367 // to ensure the time point route_end-1 is covered:
2368 // cover_{num_breaks} >= route_end
2369 // - similarly, time point s_i-1 must be covered by breaks up to i-1,
2370 // but only if the cover has not reached the route end.
2371 // For instance, a vehicle could have a choice between two days,
2372 // with a potential break on day 1 and a potential break on day 2,
2373 // but the break of day 1 does not have to cover that of day 2!
2374 // cover_{i-1} < route_end => s_i <= cover_{i-1}
2375 // This is sufficient to ensure that the union of the intervals
2376 // (-infinity, route_start], [route_end, +infinity) and all
2377 // [s_i, e_i+limit) where e_i - s_i >= min_break_duration is
2378 // the whole timeline (-infinity, +infinity).
2379 int previous_cover = solver->AddVariable(CapAdd(vehicle_start_min, limit),
2380 CapAdd(vehicle_start_max, limit));
2381 SET_DEBUG_VARIABLE_NAME(solver, previous_cover, "previous_cover");
2382 solver->AddLinearConstraint(limit, limit,
2383 {{previous_cover, 1}, {lp_cumuls.front(), -1}});
2384 for (int br = 0; br < num_breaks; ++br) {
2385 const LpBreak& lp_break = lp_breaks[br];
2386 if (lp_break.start == -1) continue;
2387 const int64_t break_end_min = CapSub(breaks[br]->EndMin(), cumul_offset);
2388 const int64_t break_end_max = CapSub(breaks[br]->EndMax(), cumul_offset);
2389 // break_is_eligible <=>
2390 // break_end - break_start >= break_minimum_duration.
2391 const int break_is_eligible = solver->AddVariable(0, 1);
2392 SET_DEBUG_VARIABLE_NAME(solver, break_is_eligible,
2393 absl::StrFormat("break_is_eligible(%ld)", br));
2394 const int break_is_not_eligible = solver->AddVariable(0, 1);
2396 solver, break_is_not_eligible,
2397 absl::StrFormat("break_is_not_eligible(%ld)", br));
2398 {
2399 solver->AddLinearConstraint(
2400 1, 1, {{break_is_eligible, 1}, {break_is_not_eligible, 1}});
2401 const int positive_ct = solver->AddLinearConstraint(
2402 min_break_duration, std::numeric_limits<int64_t>::max(),
2403 {{lp_break.end, 1}, {lp_break.start, -1}});
2404 solver->SetEnforcementLiteral(positive_ct, break_is_eligible);
2405 const int negative_ct = solver->AddLinearConstraint(
2406 std::numeric_limits<int64_t>::min(), min_break_duration - 1,
2407 {{lp_break.end, 1}, {lp_break.start, -1}});
2408 solver->SetEnforcementLiteral(negative_ct, break_is_not_eligible);
2409 }
2410 // break_is_eligible => break_cover == break_end + limit.
2411 // break_is_not_eligible => break_cover == vehicle_start_min + limit.
2412 // break_cover's initial domain is the smallest interval that contains the
2413 // union of sets {vehicle_start_min+limit} and
2414 // [break_end_min+limit, break_end_max+limit).
2415 const int break_cover = solver->AddVariable(
2416 CapAdd(std::min(vehicle_start_min, break_end_min), limit),
2417 CapAdd(std::max(vehicle_start_min, break_end_max), limit));
2418 SET_DEBUG_VARIABLE_NAME(solver, break_cover,
2419 absl::StrFormat("break_cover(%ld)", br));
2420 const int limit_cover_ct = solver->AddLinearConstraint(
2421 limit, limit, {{break_cover, 1}, {lp_break.end, -1}});
2422 solver->SetEnforcementLiteral(limit_cover_ct, break_is_eligible);
2423 const int empty_cover_ct = solver->AddLinearConstraint(
2424 CapAdd(vehicle_start_min, limit), CapAdd(vehicle_start_min, limit),
2425 {{break_cover, 1}});
2426 solver->SetEnforcementLiteral(empty_cover_ct, break_is_not_eligible);
2427
2428 const int cover =
2429 solver->AddVariable(CapAdd(vehicle_start_min, limit),
2430 std::numeric_limits<int64_t>::max());
2431 SET_DEBUG_VARIABLE_NAME(solver, cover, absl::StrFormat("cover(%ld)", br));
2432 solver->AddMaximumConstraint(cover, {previous_cover, break_cover});
2433 // Cover chaining. If route end is not covered, break start must be:
2434 // cover_{i-1} < route_end => s_i <= cover_{i-1}
2435 const int route_end_is_not_covered = solver->AddReifiedLinearConstraint(
2436 1, std::numeric_limits<int64_t>::max(),
2437 {{lp_cumuls.back(), 1}, {previous_cover, -1}});
2438 const int break_start_cover_ct = solver->AddLinearConstraint(
2439 0, std::numeric_limits<int64_t>::max(),
2440 {{previous_cover, 1}, {lp_break.start, -1}});
2441 solver->SetEnforcementLiteral(break_start_cover_ct,
2442 route_end_is_not_covered);
2443
2444 previous_cover = cover;
2445 }
2446 solver->AddLinearConstraint(0, std::numeric_limits<int64_t>::max(),
2447 {{previous_cover, 1}, {lp_cumuls.back(), -1}});
2448 }
2449
2450 return true;
2451} // NOLINT(readability/fn_size)
2452
2453namespace {
2454bool AllValuesContainedExcept(const IntVar& var, absl::Span<const int> values,
2455 const absl::flat_hash_set<int>& ignored_values) {
2456 for (int val : values) {
2457 if (!ignored_values.contains(val) && !var.Contains(val)) return false;
2458 }
2459 return true;
2460}
2461} // namespace
2462
2463bool DimensionCumulOptimizerCore::SetGlobalConstraints(
2464 const std::function<int64_t(int64_t)>& next_accessor, int64_t cumul_offset,
2465 bool optimize_costs, bool optimize_resource_assignment,
2467 // Global span cost =
2468 // global_span_cost_coefficient * (max_end_cumul - min_start_cumul).
2469 const int64_t global_span_coeff = dimension_->global_span_cost_coefficient();
2470 if (optimize_costs && global_span_coeff > 0) {
2471 // global_span_var = max_end_cumul_ - min_start_cumul_
2472 const int global_span_var = solver->CreateNewPositiveVariable();
2473 SET_DEBUG_VARIABLE_NAME(solver, global_span_var, "global_span_var");
2474 solver->AddLinearConstraint(
2475 0, 0,
2476 {{global_span_var, 1}, {max_end_cumul_, -1}, {min_start_cumul_, 1}});
2477 // NOTE: Adding the global span cost to the objective as
2478 // global_span_coeff * global_span_var increases the precision of the solver
2479 // compared to adding two separate terms global_span_coeff * max_end_cumul
2480 // and -global_span_coeff * min_start_cumul.
2481 solver->SetObjectiveCoefficient(global_span_var, global_span_coeff);
2482 }
2483
2484 // Node precedence constraints, set when both nodes are visited.
2485 for (const auto [first_node, second_node, offset, performed_constraint] :
2486 dimension_->GetNodePrecedences()) {
2487 if (next_accessor(first_node) == -1 || next_accessor(second_node) == -1) {
2488 continue;
2489 }
2490 const int first_cumul_var = index_to_cumul_variable_[first_node];
2491 const int second_cumul_var = index_to_cumul_variable_[second_node];
2492 switch (RoutingDimension::GetPrecedenceStatus(
2493 first_cumul_var < 0, second_cumul_var < 0, performed_constraint)) {
2494 case RoutingDimension::PrecedenceStatus::kActive:
2495 break;
2496 case RoutingDimension::PrecedenceStatus::kInactive:
2497 continue;
2498 case RoutingDimension::PrecedenceStatus::kInvalid:
2499 return false;
2500 }
2501 DCHECK_NE(first_cumul_var, second_cumul_var)
2502 << "Dimension " << dimension_->name()
2503 << " has a self-precedence on node " << first_node << ".";
2504
2505 // cumul[second_node] - cumul[first_node] >= offset.
2506 const int ct = solver->CreateNewConstraint(
2507 offset, std::numeric_limits<int64_t>::max());
2508 solver->SetCoefficient(ct, second_cumul_var, 1);
2509 solver->SetCoefficient(ct, first_cumul_var, -1);
2510 }
2511
2512 if (optimize_resource_assignment &&
2513 !SetGlobalConstraintsForResourceAssignment(next_accessor, cumul_offset,
2514 solver)) {
2515 return false;
2516 }
2517 return true;
2518}
2519
2520bool DimensionCumulOptimizerCore::SetGlobalConstraintsForResourceAssignment(
2521 const std::function<int64_t(int64_t)>& next_accessor, int64_t cumul_offset,
2523 if (!solver->IsCPSATSolver()) {
2524 // The resource attributes conditional constraints can only be added with
2525 // the CP-SAT MIP solver.
2526 return true;
2527 }
2528
2529 using RCIndex = RoutingModel::ResourceClassIndex;
2530 const RoutingModel& model = *dimension_->model();
2531 const int num_vehicles = model.vehicles();
2532 const auto& resource_groups = model.GetResourceGroups();
2533 for (int rg_index : model.GetDimensionResourceGroupIndices(dimension_)) {
2534 // Resource domain constraints:
2535 // Every (used) vehicle requiring a resource from this group must be
2536 // assigned to exactly one resource-class in this group, and each
2537 // resource-class must be assigned to at most #available_resources_in_class
2538 // vehicles requiring a resource from the group.
2539 // For every resource-class rc having a resource r with Attributes
2540 // A = resources[r].attributes(dim) and every vehicle v,
2541 // assign(rc, v) == 1 -->
2542 // A.start_domain.Min() <= cumul[Start(v)] <= A.start_domain.Max()
2543 // and
2544 // A.end_domain.Min() <= cumul[End(v)] <= A.end_domain.Max()
2545 const ResourceGroup& resource_group = *resource_groups[rg_index];
2546 DCHECK(!resource_group.GetVehiclesRequiringAResource().empty());
2547
2548 static const int kNoConstraint = RoutingLinearSolverWrapper::kNoConstraint;
2549 int num_required_resources = 0;
2550 // Assignment constraints for vehicles: each (used) vehicle must have
2551 // exactly one resource assigned to it.
2552 std::vector<int> vehicle_constraints(model.vehicles(), kNoConstraint);
2553 const int num_resource_classes = resource_group.GetResourceClassesCount();
2554 std::vector<absl::flat_hash_set<int>>& ignored_resources_per_class =
2555 resource_class_ignored_resources_per_group_[rg_index];
2556 ignored_resources_per_class.assign(num_resource_classes, {});
2557 for (int v : resource_group.GetVehiclesRequiringAResource()) {
2558 const IntVar& resource_var = *model.ResourceVar(v, rg_index);
2559 if (model.IsEnd(next_accessor(model.Start(v))) &&
2560 !model.IsVehicleUsedWhenEmpty(v)) {
2561 if (resource_var.Bound() && resource_var.Value() >= 0) {
2562 // Resource var forces this vehicle to be used.
2563 return false;
2564 }
2565 // We don't assign a resource to unused vehicles.
2566 continue;
2567 }
2568 // Vehicle is used.
2569 if (resource_var.Bound()) {
2570 const int resource_index = resource_var.Value();
2571 if (resource_index < 0) {
2572 // This vehicle is used but has a negative resource enforced.
2573 return false;
2574 }
2575 ignored_resources_per_class
2576 [resource_group.GetResourceClassIndex(resource_index).value()]
2577 .insert(resource_index);
2578 // We add the corresponding domain constraint on the vehicle.
2579 const int start_index = index_to_cumul_variable_[model.Start(v)];
2580 const int end_index = index_to_cumul_variable_[model.End(v)];
2581 if (!TightenStartEndVariableBoundsWithResource(
2582 *dimension_, resource_group.GetResource(resource_index),
2583 GetVariableBounds(start_index, *solver), start_index,
2584 GetVariableBounds(end_index, *solver), end_index, cumul_offset,
2585 solver)) {
2586 return false;
2587 }
2588 continue;
2589 }
2590 num_required_resources++;
2591 vehicle_constraints[v] = solver->CreateNewConstraint(1, 1);
2592 }
2593 // Assignment constraints for resource classes: each resource-class must be
2594 // assigned to at most #available_resources_in_class (used) vehicles
2595 // requiring it.
2596 std::vector<int> resource_class_cstrs(num_resource_classes, kNoConstraint);
2597 int num_available_resources = 0;
2598 for (RCIndex rc_index(0); rc_index < num_resource_classes; rc_index++) {
2599 const ResourceGroup::Attributes& attributes =
2600 resource_group.GetDimensionAttributesForClass(dimension_, rc_index);
2601 if (attributes.start_domain().Max() < cumul_offset ||
2602 attributes.end_domain().Max() < cumul_offset) {
2603 // This resource's domain has a cumul max lower than the offset, so
2604 // it's not possible to restrict any vehicle start/end to this domain;
2605 // skip it.
2606 continue;
2607 }
2608 const int rc = rc_index.value();
2609 const int num_available_class_resources =
2610 resource_group.GetResourceIndicesInClass(rc_index).size() -
2611 ignored_resources_per_class[rc].size();
2612 DCHECK_GE(num_available_class_resources, 0);
2613 if (num_available_class_resources > 0) {
2614 num_available_resources += num_available_class_resources;
2615 resource_class_cstrs[rc] =
2616 solver->CreateNewConstraint(0, num_available_class_resources);
2617 }
2618 }
2619
2620 if (num_required_resources > num_available_resources) {
2621 // There aren't enough resources in this group for vehicles requiring
2622 // one.
2623 return false;
2624 }
2625
2626 std::vector<int>& resource_class_to_vehicle_assignment_vars =
2627 resource_class_to_vehicle_assignment_variables_per_group_[rg_index];
2628 resource_class_to_vehicle_assignment_vars.assign(
2629 num_resource_classes * num_vehicles, -1);
2630 // Create assignment variables, add them to the corresponding constraints,
2631 // and create the reified constraints assign(rc, v) == 1 -->
2632 // A(r).start_domain.Min() <= cumul[Start(v)] <= A(r).start_domain.Max(),
2633 // and
2634 // A(r).end_domain.Min() <= cumul[End(v)] <= A(r).end_domain.Max().
2635 DCHECK_EQ(resource_group.Index(), rg_index);
2636 for (int v : resource_group.GetVehiclesRequiringAResource()) {
2637 if (vehicle_constraints[v] == kNoConstraint) continue;
2638 IntVar* const resource_var = model.ResourceVar(v, rg_index);
2639 std::unique_ptr<IntVarIterator> it(
2640 resource_var->MakeDomainIterator(false));
2641 std::vector<bool> resource_class_considered(num_resource_classes, false);
2642 for (int r : InitAndGetValues(it.get())) {
2643 if (r < 0) continue;
2644 const RCIndex rc_index = resource_group.GetResourceClassIndex(r);
2645 const int rc = rc_index.value();
2646 if (resource_class_considered[rc]) {
2647 continue;
2648 }
2649 resource_class_considered[rc] = true;
2650 if (resource_class_cstrs[rc] == kNoConstraint) continue;
2651
2652 // NOTE(user): The resource class computation should allow us to
2653 // catch all incompatibility reasons between vehicles and resources. If
2654 // the following DCHECK fails, the resource classes should be adapted
2655 // accordingly.
2656 DCHECK(AllValuesContainedExcept(
2657 *resource_var, resource_group.GetResourceIndicesInClass(rc_index),
2658 ignored_resources_per_class[rc]))
2659 << DUMP_VARS(v, rg_index,
2660 resource_group.GetResourceIndicesInClass(rc_index),
2661 resource_var->Min(), resource_var->Max());
2662
2663 const int assign_rc_to_v = solver->AddVariable(0, 1);
2665 solver, assign_rc_to_v,
2666 absl::StrFormat("assign_rc_to_v(%ld, %ld)", rc, v));
2667 resource_class_to_vehicle_assignment_vars[rc * num_vehicles + v] =
2668 assign_rc_to_v;
2669 // To keep the model clean
2670 // (cf. glop::LinearProgram::NotifyThatColumnsAreClean), constraints on
2671 // assign_rc_to_v need to be in ascending order.
2672 DCHECK_LT(vehicle_constraints[v], resource_class_cstrs[rc]);
2673 solver->SetCoefficient(vehicle_constraints[v], assign_rc_to_v, 1);
2674 solver->SetCoefficient(resource_class_cstrs[rc], assign_rc_to_v, 1);
2675
2676 const auto& add_domain_constraint =
2677 [&solver, cumul_offset, assign_rc_to_v](const Domain& domain,
2678 int cumul_variable) {
2679 if (domain == Domain::AllValues()) {
2680 return;
2681 }
2682 ClosedInterval cumul_bounds;
2683 if (!GetDomainOffsetBounds(domain, cumul_offset, &cumul_bounds)) {
2684 // This domain cannot be assigned to this vehicle.
2685 solver->SetVariableBounds(assign_rc_to_v, 0, 0);
2686 return;
2687 }
2688 const int cumul_constraint = solver->AddLinearConstraint(
2689 cumul_bounds.start, cumul_bounds.end, {{cumul_variable, 1}});
2690 solver->SetEnforcementLiteral(cumul_constraint, assign_rc_to_v);
2691 };
2692 const ResourceGroup::Attributes& attributes =
2693 resource_group.GetDimensionAttributesForClass(dimension_, rc_index);
2694 add_domain_constraint(attributes.start_domain(),
2695 index_to_cumul_variable_[model.Start(v)]);
2696 add_domain_constraint(attributes.end_domain(),
2697 index_to_cumul_variable_[model.End(v)]);
2698 }
2699 }
2700 }
2701 return true;
2702}
2703
2704#undef SET_DEBUG_VARIABLE_NAME
2705
2706void DimensionCumulOptimizerCore::SetValuesFromLP(
2707 absl::Span<const int> lp_variables, int64_t offset,
2708 RoutingLinearSolverWrapper* solver, std::vector<int64_t>* lp_values) const {
2709 if (lp_values == nullptr) return;
2710 lp_values->assign(lp_variables.size(), std::numeric_limits<int64_t>::min());
2711 for (int i = 0; i < lp_variables.size(); i++) {
2712 const int lp_var = lp_variables[i];
2713 if (lp_var < 0) continue; // Keep default value, kint64min.
2714 const double lp_value_double = solver->GetValue(lp_var);
2715 const int64_t lp_value_int64 =
2716 (lp_value_double >= std::numeric_limits<int64_t>::max())
2717 ? std::numeric_limits<int64_t>::max()
2718 : MathUtil::FastInt64Round(lp_value_double);
2719 (*lp_values)[i] = CapAdd(lp_value_int64, offset);
2720 }
2721}
2722
2723void DimensionCumulOptimizerCore::SetResourceIndices(
2724 RoutingLinearSolverWrapper* solver,
2725 std::vector<std::vector<int>>* resource_indices_per_group) const {
2726 if (resource_indices_per_group == nullptr ||
2727 resource_class_to_vehicle_assignment_variables_per_group_.empty()) {
2728 return;
2729 }
2730 using RCIndex = RoutingModel::ResourceClassIndex;
2731 const RoutingModel& model = *dimension_->model();
2732 const int num_vehicles = model.vehicles();
2733 DCHECK(!model.GetDimensionResourceGroupIndices(dimension_).empty());
2734 const auto& resource_groups = model.GetResourceGroups();
2735 resource_indices_per_group->resize(resource_groups.size());
2736 for (int rg_index : model.GetDimensionResourceGroupIndices(dimension_)) {
2737 const ResourceGroup& resource_group = *resource_groups[rg_index];
2738 DCHECK(!resource_group.GetVehiclesRequiringAResource().empty());
2739
2741 resource_indices_per_class =
2742 resource_group.GetResourceIndicesPerClass();
2743 const int num_resource_classes = resource_group.GetResourceClassesCount();
2744 std::vector<int> current_resource_pos_for_class(num_resource_classes, 0);
2745 std::vector<int>& resource_indices =
2746 resource_indices_per_group->at(rg_index);
2747 resource_indices.assign(num_vehicles, -1);
2748 // Find the resource assigned to each vehicle.
2749 const std::vector<int>& resource_class_to_vehicle_assignment_vars =
2750 resource_class_to_vehicle_assignment_variables_per_group_[rg_index];
2751 DCHECK_EQ(resource_class_to_vehicle_assignment_vars.size(),
2752 num_resource_classes * num_vehicles);
2753 const std::vector<absl::flat_hash_set<int>>& ignored_resources_per_class =
2754 resource_class_ignored_resources_per_group_[rg_index];
2755 DCHECK_EQ(ignored_resources_per_class.size(), num_resource_classes);
2756 for (int v : resource_group.GetVehiclesRequiringAResource()) {
2757 const IntVar& resource_var = *model.ResourceVar(v, rg_index);
2758 if (resource_var.Bound()) {
2759 resource_indices[v] = resource_var.Value();
2760 continue;
2761 }
2762 for (int rc = 0; rc < num_resource_classes; rc++) {
2763 const int assignment_var =
2764 resource_class_to_vehicle_assignment_vars[rc * num_vehicles + v];
2765 if (assignment_var >= 0 && solver->GetValue(assignment_var) == 1) {
2766 // This resource class is assigned to this vehicle.
2767 const std::vector<int>& class_resource_indices =
2768 resource_indices_per_class[RCIndex(rc)];
2769 int& pos = current_resource_pos_for_class[rc];
2770 while (ignored_resources_per_class[rc].contains(
2771 class_resource_indices[pos])) {
2772 pos++;
2773 DCHECK_LT(pos, class_resource_indices.size());
2774 }
2775 resource_indices[v] = class_resource_indices[pos++];
2776 break;
2777 }
2778 }
2779 }
2780 }
2781}
2782
2783// GlobalDimensionCumulOptimizer
2784
2786 const RoutingDimension* dimension,
2788 : optimizer_core_(dimension,
2789 /*use_precedence_propagator=*/
2790 !dimension->GetNodePrecedences().empty()) {
2791 switch (solver_type) {
2793 solver_ = std::make_unique<RoutingGlopWrapper>(
2794 /*is_relaxation=*/!dimension->model()
2795 ->GetDimensionResourceGroupIndices(dimension)
2796 .empty(),
2797 GetGlopParametersForGlobalLP());
2798 break;
2799 }
2801 solver_ = std::make_unique<RoutingCPSatWrapper>();
2802 break;
2803 }
2804 default:
2805 LOG(DFATAL) << "Unrecognized solver type: " << solver_type;
2806 }
2807}
2808
2811 const std::function<int64_t(int64_t)>& next_accessor,
2812 int64_t* optimal_cost_without_transits) {
2813 return optimizer_core_.Optimize(next_accessor, {}, solver_.get(), nullptr,
2814 nullptr, nullptr,
2815 optimal_cost_without_transits, nullptr);
2816}
2817
2819 const std::function<int64_t(int64_t)>& next_accessor,
2820 const std::vector<RoutingModel::RouteDimensionTravelInfo>&
2821 dimension_travel_info_per_route,
2822 std::vector<int64_t>* optimal_cumuls, std::vector<int64_t>* optimal_breaks,
2823 std::vector<std::vector<int>>* optimal_resource_indices) {
2824 return optimizer_core_.Optimize(next_accessor,
2825 dimension_travel_info_per_route,
2826 solver_.get(), optimal_cumuls, optimal_breaks,
2827 optimal_resource_indices, nullptr, nullptr);
2828}
2829
2831 const std::function<int64_t(int64_t)>& next_accessor,
2832 const std::vector<RoutingModel::RouteDimensionTravelInfo>&
2833 dimension_travel_info_per_route,
2834 std::vector<int64_t>* packed_cumuls, std::vector<int64_t>* packed_breaks) {
2835 return optimizer_core_.OptimizeAndPack(
2836 next_accessor, dimension_travel_info_per_route, solver_.get(),
2837 packed_cumuls, packed_breaks);
2838}
2839
2840namespace {
2841
2842template <typename T>
2843void MoveValuesToIndicesFrom(std::vector<T>* out_values,
2844 absl::Span<const int> out_indices_to_evaluate,
2845 const std::function<int(int)>& index_evaluator,
2846 std::vector<T>& values_to_copy) {
2847 if (out_values == nullptr) {
2848 DCHECK(values_to_copy.empty());
2849 return;
2850 }
2851 DCHECK_EQ(values_to_copy.size(), out_indices_to_evaluate.size());
2852 for (int i = 0; i < out_indices_to_evaluate.size(); i++) {
2853 const int output_index = index_evaluator(out_indices_to_evaluate[i]);
2854 DCHECK_LT(output_index, out_values->size());
2855 out_values->at(output_index) = std::move(values_to_copy[i]);
2856 }
2857}
2858
2859} // namespace
2860
2862 int v, double solve_duration_ratio,
2863 const RoutingModel::ResourceGroup& resource_group,
2864 const util_intops::StrongVector<RoutingModel::ResourceClassIndex,
2865 absl::flat_hash_set<int>>&
2866 ignored_resources_per_class,
2867 const std::function<int64_t(int64_t)>& next_accessor,
2868 const std::function<int64_t(int64_t, int64_t)>& transit_accessor,
2869 bool optimize_vehicle_costs, LocalDimensionCumulOptimizer* lp_optimizer,
2870 LocalDimensionCumulOptimizer* mp_optimizer,
2871 std::vector<int64_t>* assignment_costs,
2872 std::vector<std::vector<int64_t>>* cumul_values,
2873 std::vector<std::vector<int64_t>>* break_values) {
2874 DCHECK_GT(solve_duration_ratio, 0);
2875 DCHECK_LE(solve_duration_ratio, 1);
2876 DCHECK(lp_optimizer != nullptr);
2877 DCHECK(mp_optimizer != nullptr);
2878 DCHECK_NE(assignment_costs, nullptr);
2879 assignment_costs->clear();
2880 ClearIfNonNull(cumul_values);
2881 ClearIfNonNull(break_values);
2882
2883 const RoutingDimension* dimension = lp_optimizer->dimension();
2884 DCHECK_EQ(dimension, mp_optimizer->dimension());
2885 RoutingModel* const model = dimension->model();
2886 if (!resource_group.VehicleRequiresAResource(v) ||
2887 (!model->IsVehicleUsedWhenEmpty(v) &&
2888 next_accessor(model->Start(v)) == model->End(v))) {
2889 return true;
2890 }
2891 if (model->CheckLimit()) {
2892 // The model's time limit has been reached, stop everything.
2893 return false;
2894 }
2895
2896 IntVar* const resource_var = model->ResourceVar(v, resource_group.Index());
2897 const int num_resource_classes = resource_group.GetResourceClassesCount();
2898 std::vector<int> considered_resource_indices;
2899 considered_resource_indices.reserve(
2900 std::min<int>(resource_var->Size(), num_resource_classes));
2901 std::vector<bool> resource_class_considered(num_resource_classes, false);
2902 std::unique_ptr<IntVarIterator> it(resource_var->MakeDomainIterator(false));
2903 for (const int res : InitAndGetValues(it.get())) {
2904 if (res < 0) {
2905 continue;
2906 }
2907 const RoutingModel::ResourceClassIndex resource_class =
2908 resource_group.GetResourceClassIndex(res);
2909 const int rc_index = resource_class.value();
2910 const absl::flat_hash_set<int>& ignored_resources =
2911 ignored_resources_per_class[resource_class];
2912 if (resource_class_considered[rc_index] ||
2913 ignored_resources.contains(res)) {
2914 continue;
2915 }
2916 resource_class_considered[rc_index] = true;
2917 // NOTE(user): The resource class computation should allow us to catch
2918 // all incompatibility reasons between vehicles and resources. If the
2919 // following DCHECK fails, the resource classes should be adapted
2920 // accordingly.
2921 DCHECK(AllValuesContainedExcept(
2922 *resource_var, resource_group.GetResourceIndicesInClass(resource_class),
2923 ignored_resources));
2924 considered_resource_indices.push_back(res);
2925 }
2926 const bool use_mp_optimizer =
2927 dimension->HasBreakConstraints() &&
2928 !dimension->GetBreakIntervalsOfVehicle(v).empty();
2929 LocalDimensionCumulOptimizer* optimizer =
2930 use_mp_optimizer ? mp_optimizer : lp_optimizer;
2931
2932 const std::vector<ResourceGroup::Resource>& resources =
2933 resource_group.GetResources();
2934 std::vector<int64_t> considered_assignment_costs;
2935 std::vector<std::vector<int64_t>> considered_cumul_values;
2936 std::vector<std::vector<int64_t>> considered_break_values;
2937 std::vector<DimensionSchedulingStatus> statuses =
2939 v, solve_duration_ratio, next_accessor, transit_accessor, resources,
2940 considered_resource_indices, optimize_vehicle_costs,
2941 &considered_assignment_costs,
2942 cumul_values == nullptr ? nullptr : &considered_cumul_values,
2943 break_values == nullptr ? nullptr : &considered_break_values);
2944
2945 if (statuses.empty() ||
2946 (statuses.size() == 1 &&
2947 statuses[0] == DimensionSchedulingStatus::INFEASIBLE)) {
2948 // Couldn't assign any resource to this vehicle.
2949 return false;
2950 }
2951
2952 assignment_costs->resize(num_resource_classes, -1);
2953 if (cumul_values != nullptr) {
2954 cumul_values->resize(num_resource_classes, {});
2955 }
2956 if (break_values != nullptr) {
2957 break_values->resize(num_resource_classes, {});
2958 }
2959
2960 const auto resource_to_class_index = [&resource_group](int resource_index) {
2961 return resource_group.GetResourceClassIndex(resource_index).value();
2962 };
2963 MoveValuesToIndicesFrom(assignment_costs, considered_resource_indices,
2964 resource_to_class_index, considered_assignment_costs);
2965 MoveValuesToIndicesFrom(cumul_values, considered_resource_indices,
2966 resource_to_class_index, considered_cumul_values);
2967 MoveValuesToIndicesFrom(break_values, considered_resource_indices,
2968 resource_to_class_index, considered_break_values);
2969
2970 if (use_mp_optimizer) {
2971 // We already used the mp optimizer, so we don't need to recompute anything.
2972 // If all assignment costs are negative, it means no resource is feasible
2973 // for this vehicle.
2974 return absl::c_any_of(*assignment_costs,
2975 [](int64_t cost) { return cost >= 0; });
2976 }
2977
2978 std::vector<int> mp_resource_indices;
2979 DCHECK_EQ(statuses.size(), considered_resource_indices.size());
2980 for (int i = 0; i < considered_resource_indices.size(); i++) {
2982 mp_resource_indices.push_back(considered_resource_indices[i]);
2983 }
2984 }
2985
2986 std::vector<int64_t> mp_assignment_costs;
2987 std::vector<std::vector<int64_t>> mp_cumul_values;
2988 std::vector<std::vector<int64_t>> mp_break_values;
2990 v, solve_duration_ratio, next_accessor, transit_accessor, resources,
2991 mp_resource_indices, optimize_vehicle_costs, &mp_assignment_costs,
2992 cumul_values == nullptr ? nullptr : &mp_cumul_values,
2993 break_values == nullptr ? nullptr : &mp_break_values);
2994 if (!mp_resource_indices.empty() && mp_assignment_costs.empty()) {
2995 // A timeout was reached during optimization.
2996 return false;
2997 }
2998
2999 MoveValuesToIndicesFrom(assignment_costs, mp_resource_indices,
3000 resource_to_class_index, mp_assignment_costs);
3001 MoveValuesToIndicesFrom(cumul_values, mp_resource_indices,
3002 resource_to_class_index, mp_cumul_values);
3003 MoveValuesToIndicesFrom(break_values, mp_resource_indices,
3004 resource_to_class_index, mp_break_values);
3005
3006 return absl::c_any_of(*assignment_costs,
3007 [](int64_t cost) { return cost >= 0; });
3008}
3009
3011 absl::Span<const int> vehicles,
3012 const util_intops::StrongVector<RoutingModel::ResourceClassIndex,
3013 std::vector<int>>&
3014 resource_indices_per_class,
3015 const util_intops::StrongVector<RoutingModel::ResourceClassIndex,
3016 absl::flat_hash_set<int>>&
3017 ignored_resources_per_class,
3018 std::function<const std::vector<int64_t>*(int)>
3019 vehicle_to_resource_class_assignment_costs,
3020 std::vector<int>* resource_indices) {
3021 const int total_num_resources = absl::c_accumulate(
3022 resource_indices_per_class, 0,
3023 [](int acc, absl::Span<const int> res) { return acc + res.size(); });
3024 DCHECK_GE(total_num_resources, 1);
3025 const int num_ignored_resources =
3026 absl::c_accumulate(ignored_resources_per_class, 0,
3027 [](int acc, const absl::flat_hash_set<int>& res) {
3028 return acc + res.size();
3029 });
3030 const int num_resources = total_num_resources - num_ignored_resources;
3031 const int num_vehicles = vehicles.size();
3032 int num_total_vehicles = -1;
3033 if (resource_indices != nullptr) {
3034 num_total_vehicles = resource_indices->size();
3035 // When returning infeasible, 'resource_indices' must be cleared, so we do
3036 // it here preemptively.
3037 resource_indices->clear();
3038 DCHECK_GE(num_total_vehicles, num_vehicles);
3039 for (int v : vehicles) {
3040 DCHECK_GE(v, 0);
3041 DCHECK_LT(v, num_total_vehicles);
3042 }
3043 }
3044
3045 // Collect vehicle_to_resource_class_assignment_costs(v) for all v ∈ vehicles.
3046 // Then detect trivial infeasibility cases, before doing the min-cost-flow:
3047 // - There are not enough resources overall.
3048 // - There is no resource assignable to a vehicle that needs one.
3049 const int num_resource_classes = resource_indices_per_class.size();
3050 std::vector<const std::vector<int64_t>*> vi_to_rc_cost(num_vehicles);
3051 int num_vehicles_to_assign = 0;
3052 for (int i = 0; i < num_vehicles; ++i) {
3053 vi_to_rc_cost[i] = vehicle_to_resource_class_assignment_costs(vehicles[i]);
3054 if (!vi_to_rc_cost[i]->empty()) {
3055 DCHECK_EQ(vi_to_rc_cost[i]->size(), num_resource_classes);
3056 ++num_vehicles_to_assign;
3057 }
3058 }
3059 if (num_vehicles_to_assign > num_resources) {
3060 VLOG(3) << "Less resources (" << num_resources << ") than the vehicles"
3061 << " requiring one (" << num_vehicles_to_assign << ")";
3062 return -1; // Infeasible.
3063 }
3064 // Catch infeasibility cases where
3065 // ComputeVehicleToResourceClassAssignmentCosts() hasn't "properly"
3066 // initialized the vehicle to resource class assignment costs (this can happen
3067 // for instance in the ResourceGroupAssignmentFilter when routes are
3068 // synchronized with an impossible first solution).
3069 for (int i = 0; i < num_vehicles; ++i) {
3070 if (!vi_to_rc_cost[i]->empty() &&
3071 *absl::c_max_element(*vi_to_rc_cost[i]) < 0) {
3072 VLOG(3) << "Vehicle #" << vehicles[i] << " has no feasible resource";
3073 return -1;
3074 }
3075 }
3076
3077 // We may need to apply some cost scaling when using SimpleMinCostFlow.
3078 // With our graph it seems having 4 * max_arc_cost * num_nodes ≤ kint64max is
3079 // sufficient. To do that, we first find the maximum arc cost.
3080 int64_t max_arc_cost = 0;
3081 for (const std::vector<int64_t>* costs : vi_to_rc_cost) {
3082 if (costs->empty()) continue;
3083 max_arc_cost = std::max(max_arc_cost, *absl::c_max_element(*costs));
3084 }
3085 // To avoid potential int64_t overflows, we slightly tweak the above formula.
3086 // NOTE(user): SimpleMinCostFlow always adds a sink and source node (we
3087 // probably shouldn't add a sink/source node ourselves in the graph).
3088 const int real_num_nodes = 4 + num_vehicles + num_resource_classes;
3089 const int64_t max_acceptable_arc_cost = kint64max / (4 * real_num_nodes) - 1;
3090 // We use a power of 2 for the cost scaling factor, to have clean (in)accuracy
3091 // properties. Note also that we must round *down* the costs.
3092 int cost_right_shift = 0;
3093 while ((max_arc_cost >> cost_right_shift) > max_acceptable_arc_cost) {
3094 ++cost_right_shift;
3095 }
3096
3097 // Then, we create the SimpleMinCostFlow and run the assignment algorithm.
3098 // NOTE(user): We often don't create as many arcs as outlined below,
3099 // especially when num_vehicles_to_assign < vehicles.size(). But since we
3100 // want to eventually make this whole function incremental, we prefer sticking
3101 // with the whole 'vehicles' set.
3102 SimpleMinCostFlow flow(
3103 /*reserve_num_nodes*/ 2 + num_vehicles + num_resource_classes,
3104 /*reserve_num_arcs*/ num_vehicles + num_vehicles * num_resource_classes +
3105 num_resource_classes);
3106 using ArcIndex = SimpleMinCostFlow::ArcIndex;
3107 const int source_index = num_vehicles + num_resource_classes;
3108 const int sink_index = source_index + 1;
3109 const auto flow_rc_index = [num_vehicles](int rc) {
3110 return num_vehicles + rc;
3111 };
3112
3113 // Used to store the arc indices, if we need to later recover the solution.
3114 FlatMatrix<ArcIndex> vehicle_to_rc_arc_index;
3115 if (resource_indices != nullptr) {
3116 vehicle_to_rc_arc_index =
3117 FlatMatrix<ArcIndex>(num_vehicles, num_resource_classes, -1);
3118 }
3119 for (int vi = 0; vi < num_vehicles; ++vi) {
3120 const std::vector<int64_t>& assignment_costs = *vi_to_rc_cost[vi];
3121 if (assignment_costs.empty()) continue; // Doesn't need resources.
3122
3123 // Add a source → vehicle arc to the min-cost-flow graph.
3124 flow.AddArcWithCapacityAndUnitCost(source_index, vi, 1, 0);
3125
3126 // Add vehicle → resource-class arcs to the min-cost-flow graph.
3127 for (int rc = 0; rc < num_resource_classes; rc++) {
3128 const int64_t assignment_cost = assignment_costs[rc];
3129 if (assignment_cost < 0) continue;
3130 const ArcIndex arc = flow.AddArcWithCapacityAndUnitCost(
3131 vi, flow_rc_index(rc), 1, assignment_cost >> cost_right_shift);
3132 if (resource_indices != nullptr) {
3133 vehicle_to_rc_arc_index[vi][rc] = arc;
3134 }
3135 }
3136 }
3137
3138 // Add resource-class->sink arcs to the flow. The capacity on these arcs is
3139 // the number of available resources for the corresponding class.
3140 using RCIndex = RoutingModel::ResourceClassIndex;
3141 for (int rc = 0; rc < num_resource_classes; rc++) {
3142 const RCIndex rci(rc);
3143 const int num_available_res = resource_indices_per_class[rci].size() -
3144 ignored_resources_per_class[rci].size();
3145 DCHECK_GE(num_available_res, 0);
3146 flow.AddArcWithCapacityAndUnitCost(flow_rc_index(rc), sink_index,
3147 num_available_res, 0);
3148 }
3149
3150 // Set the flow supply.
3151 flow.SetNodeSupply(source_index, num_vehicles_to_assign);
3152 flow.SetNodeSupply(sink_index, -num_vehicles_to_assign);
3153
3154 // Solve the min-cost flow and return its cost.
3155 if (flow.Solve() != SimpleMinCostFlow::OPTIMAL) {
3156 VLOG(3) << "Non-OPTIMAL flow result";
3157 return -1;
3158 }
3159
3160 if (resource_indices != nullptr) {
3161 // Fill the resource indices corresponding to the min-cost assignment.
3162 resource_indices->assign(num_total_vehicles, -1);
3163 std::vector<int> current_resource_pos_for_class(num_resource_classes, 0);
3164 for (int vi = 0; vi < num_vehicles; ++vi) {
3165 if (vi_to_rc_cost[vi]->empty()) {
3166 // No resource needed for this vehicle.
3167 continue;
3168 }
3169 for (int rc = 0; rc < num_resource_classes; rc++) {
3170 const ArcIndex arc = vehicle_to_rc_arc_index[vi][rc];
3171 if (arc >= 0 && flow.Flow(arc) > 0) {
3172 const RCIndex rci(rc);
3173 const std::vector<int>& class_resource_indices =
3174 resource_indices_per_class[rci];
3175 const absl::flat_hash_set<int>& ignored_resources =
3176 ignored_resources_per_class[rci];
3177 int& pos = current_resource_pos_for_class[rc];
3178 DCHECK_LT(pos, class_resource_indices.size());
3179 while (ignored_resources.contains(class_resource_indices[pos])) {
3180 pos++;
3181 DCHECK_LT(pos, class_resource_indices.size());
3182 }
3183 resource_indices->at(vehicles[vi]) = class_resource_indices[pos++];
3184 break;
3185 }
3186 }
3187 }
3188 }
3189
3190 const int64_t cost = flow.OptimalCost();
3191 DCHECK_LE(cost, kint64max >> cost_right_shift);
3192 return cost << cost_right_shift;
3193}
3194
3195std::string Int64ToStr(int64_t number) {
3196 if (number == kint64min) return "-infty";
3197 if (number == kint64max) return "+infty";
3198 return std::to_string(number);
3199}
3200
3201std::string DomainToString(
3202 const ::google::protobuf::RepeatedField<int64_t>* domain) {
3203 if (domain->size() > 2 && domain->size() % 2 == 0) {
3204 std::string s = "∈ ";
3205 for (int i = 0; i < domain->size(); i += 2) {
3206 s += absl::StrFormat("[%s, %s]", Int64ToStr(domain->Get(i)),
3207 Int64ToStr(domain->Get(i + 1)));
3208 if (i < domain->size() - 2) s += " ∪ ";
3209 }
3210 return s;
3211 } else if (domain->size() == 2) {
3212 if (domain->Get(0) == domain->Get(1)) {
3213 return absl::StrFormat("= %s", Int64ToStr(domain->Get(0)));
3214 } else if (domain->Get(0) == 0 && domain->Get(1) == 1) {
3215 return "∈ Binary";
3216 } else if (domain->Get(0) == std::numeric_limits<int64_t>::min() &&
3217 domain->Get(1) == std::numeric_limits<int64_t>::max()) {
3218 return "∈ ℝ";
3219 } else if (domain->Get(0) == std::numeric_limits<int64_t>::min()) {
3220 return absl::StrFormat("≤ %s", Int64ToStr(domain->Get(1)));
3221 } else if (domain->Get(1) == std::numeric_limits<int64_t>::max()) {
3222 return absl::StrFormat("≥ %s", Int64ToStr(domain->Get(0)));
3223 }
3224 return absl::StrFormat("∈ [%ls, %s]", Int64ToStr(domain->Get(0)),
3225 Int64ToStr(domain->Get(1)));
3226 } else if (domain->size() == 1) {
3227 return absl::StrFormat("= %s", Int64ToStr(domain->Get(0)));
3228 } else {
3229 return absl::StrFormat("∈ Unknown domain (size=%ld)", domain->size());
3230 }
3231}
3232
3234 std::pair<sat::IntegerVariableProto, int>& variable_pair,
3235 const sat::CpSolverResponse& response_) {
3236 std::string s = "";
3237 sat::IntegerVariableProto& variable = variable_pair.first;
3238 const int index = variable_pair.second;
3239 if (response_.IsInitialized() && variable.IsInitialized() &&
3240 (response_.status() == sat::CpSolverStatus::OPTIMAL ||
3241 response_.status() == sat::CpSolverStatus::FEASIBLE)) {
3242 const double lp_value_double = response_.solution(index);
3243 const int64_t lp_value_int64 =
3244 (lp_value_double >= std::numeric_limits<int64_t>::max())
3245 ? std::numeric_limits<int64_t>::max()
3246 : MathUtil::FastInt64Round(lp_value_double);
3247 s += Int64ToStr(lp_value_int64) + " ";
3248 } else {
3249 s += "? ";
3250 }
3251 s += DomainToString(variable.mutable_domain());
3252 return s;
3253}
3254
3255std::string ConstraintToString(const sat::ConstraintProto& constraint,
3256 const sat::CpModelProto& model_,
3257 bool show_enforcement = true) {
3258 std::string s = "";
3259 if (constraint.has_linear()) {
3260 const auto& linear = constraint.linear();
3261 for (int j = 0; j < linear.vars().size(); ++j) {
3262 const std::string sign = linear.coeffs(j) > 0 ? "+" : "-";
3263 const std::string mult =
3264 std::abs(linear.coeffs(j)) != 1
3265 ? std::to_string(std::abs(linear.coeffs(j))) + " * "
3266 : "";
3267 if (j > 0 || sign != "+") s += sign + " ";
3268 s += mult + model_.variables(linear.vars(j)).name() + " ";
3269 }
3270 s += DomainToString(&linear.domain());
3271
3272 // Enforcement literal.
3273 if (show_enforcement) {
3274 for (int j = 0; j < constraint.enforcement_literal_size(); ++j) {
3275 s += (j == 0) ? "\t if " : " and ";
3276 s += model_.variables(constraint.enforcement_literal(j)).name();
3277 }
3278 }
3279 } else {
3280 s += ProtobufShortDebugString(constraint);
3281 }
3282 return s;
3283}
3284
3286 absl::flat_hash_map<std::string, std::pair<sat::IntegerVariableProto, int>>&
3287 variables,
3288 absl::flat_hash_map<std::string, std::vector<int>>& variable_instances,
3289 absl::flat_hash_map<std::string, absl::flat_hash_set<std::string>>&
3290 variable_childs,
3291 const sat::CpSolverResponse& response_, absl::string_view variable,
3292 std::string prefix = "") {
3293 if (variable.empty()) {
3294 std::string s = "";
3295 const auto& childs = variable_childs[""];
3296 for (const std::string& child : childs) {
3297 s += prefix +
3298 VariablesToString(variables, variable_instances, variable_childs,
3299 response_, child, prefix) +
3300 prefix + "\n";
3301 }
3302 return s;
3303 }
3304
3305 const auto& instances = variable_instances[variable];
3306 std::string variable_display(variable);
3307 std::size_t bracket_pos = variable.find_last_of(')');
3308 if (bracket_pos != std::string::npos) {
3309 variable_display = variable.substr(bracket_pos + 1);
3310 }
3311 std::string s = variable_display + " | ";
3312 prefix += std::string(variable_display.length(), ' ') + " | ";
3313 for (int i = 0; i < instances.size(); ++i) {
3314 const std::string instance_name =
3315 absl::StrFormat("%s(%ld)", variable, instances[i]);
3316 if (i > 0) s += prefix;
3317 s += absl::StrFormat("%ld: %s", instances[i],
3318 VariableToString(variables[instance_name], response_));
3319
3320 // Childs
3321 const auto& childs = variable_childs[instance_name];
3322 for (const std::string& child : childs) {
3323 s += "\n" + prefix + "| ";
3324 s += VariablesToString(variables, variable_instances, variable_childs,
3325 response_, child, prefix + "| ");
3326 }
3327 if (childs.empty()) s += "\n";
3328 }
3329 return s;
3330}
3331
3333 // Constraints you want to separate.
3334 std::vector<std::vector<std::string>> constraints_apart;
3335 constraints_apart.push_back(
3336 {"compression_cost", "travel_compression_absolute"});
3337
3338 // variable_instances links the lemma of a variable to the different number of
3339 // instantiation. For instance if you have in your model x(0), x(1) and x(4),
3340 // the key "x" will be associated to {0,1,4}.
3341 absl::flat_hash_map<std::string, std::vector<int>> variable_instances;
3342 // variable_children links a variable to its children. That is, if you have in
3343 // you model x(0), then typical childs would be {"x(0)in_segment(0)",
3344 // "x(0)in_segment(1)", "x(0)scaled", ...}
3345 absl::flat_hash_map<std::string, absl::flat_hash_set<std::string>>
3346 variable_children;
3347 // variables link the name of a variable to its Proto.
3348 absl::flat_hash_map<std::string, std::pair<sat::IntegerVariableProto, int>>
3349 variables;
3350 variable_children[""] = {};
3351
3352 const int num_constraints = model_.constraints_size();
3353 const int num_variables = model_.variables_size();
3354 int num_binary_variables = 0;
3355 for (int i = 0; i < num_variables; ++i) {
3356 const auto& variable = model_.variables(i);
3357 const auto& name = variable.name();
3358 const int pos_bracket = name.find_last_of('(');
3359 if (pos_bracket != std::string::npos) {
3360 const std::string lemma = name.substr(0, pos_bracket);
3361 const int pos_closing_bracket = name.find_last_of(')');
3362 CHECK_NE(pos_closing_bracket, std::string::npos);
3363 const int index =
3364 std::stoi(name.substr(pos_bracket + 1, pos_closing_bracket));
3365 std::vector<int>* instances = gtl::FindOrNull(variable_instances, lemma);
3366 if (instances != nullptr) {
3367 instances->push_back(index);
3368 } else {
3369 variable_instances[lemma] = {index};
3370 }
3371 variable_children[name] = {};
3372
3373 std::string parent = "";
3374 const int pos_parent_closing_bracket = lemma.find_last_of(')');
3375 if (pos_parent_closing_bracket != std::string::npos) {
3376 parent = lemma.substr(0, pos_parent_closing_bracket + 1);
3377 }
3378 variable_children[parent].emplace(lemma);
3379 variables[name] = std::make_pair(variable, i);
3380 if (variable.domain(0) == 0 & variable.domain(1) == 1) {
3381 ++num_binary_variables;
3382 }
3383 }
3384 }
3385
3386 // Preparing constraints
3387 // the constraints hashmap associate enforcement to constraints.
3388 // If the ket is "", then the constraint has no enforcement and if the key is
3389 // "multiple", then the constraint has several enforcement. If the constraint
3390 // has a single enforcement, then the key will be the variable name of the
3391 // enforcement.
3392 absl::flat_hash_map<std::string, std::vector<sat::ConstraintProto>>
3393 constraints;
3394 absl::flat_hash_map<std::vector<std::string>,
3395 std::vector<sat::ConstraintProto>>
3396 constraint_groups;
3397 for (int i = 0; i < num_constraints; ++i) {
3398 const auto& constraint = model_.constraints(i);
3399 std::string enforcement = "";
3400 if (constraint.enforcement_literal_size() == 1) {
3401 enforcement = model_.variables(constraint.enforcement_literal(0)).name();
3402 } else if (constraint.enforcement_literal_size() > 1) {
3403 enforcement = "multiple";
3404 } else {
3405 if (constraint.has_linear()) {
3406 const auto& linear = constraint.linear();
3407 std::vector<std::string> key;
3408 for (int j = 0; j < linear.vars().size(); ++j) {
3409 std::string var_name = model_.variables(linear.vars(j)).name();
3410 std::string lemma = var_name.substr(0, var_name.find_last_of('('));
3411 key.push_back(lemma);
3412 }
3413 auto* constraint_group = gtl::FindOrNull(constraint_groups, key);
3414 if (constraint_group != nullptr) {
3415 constraint_group->push_back(constraint);
3416 } else {
3417 constraint_groups[key] = {constraint};
3418 }
3419 }
3420 }
3421 auto* constraints_enforced = gtl::FindOrNull(constraints, enforcement);
3422 if (constraints_enforced != nullptr) {
3423 constraints[enforcement].push_back(constraint);
3424 } else {
3425 constraints[enforcement] = {constraint};
3426 }
3427 }
3428
3429 const std::string prefix_constraint = " • ";
3430 std::string s = "Using RoutingCPSatWrapper.\n";
3431 s += absl::StrFormat("\nObjective = %f\n", this->GetObjectiveValue());
3432
3433 for (int i = 0; i < objective_coefficients_.size(); ++i) {
3434 double coeff = objective_coefficients_[i];
3435 if (coeff != 0) {
3436 s += absl::StrFormat(" | %f * %s\n", coeff, model_.variables(i).name());
3437 }
3438 }
3439
3440 s += absl::StrFormat("\nVariables %d (%d Binary - %d Non Binary)\n",
3441 num_variables, num_binary_variables,
3442 num_variables - num_binary_variables);
3443 s += VariablesToString(variables, variable_instances, variable_children,
3444 response_, "", " | ");
3445 s += absl::StrFormat("\n\nConstraints (%d)\n", num_constraints);
3446
3447 // Constraints NOT enforced
3448 s += "\n- Not enforced\n";
3449 bool at_least_one_not_enforced = false;
3450 for (const auto& pair : constraint_groups) {
3451 if (!std::count(constraints_apart.begin(), constraints_apart.end(),
3452 pair.first)) {
3453 for (const auto& constraint : pair.second) {
3454 s += prefix_constraint + ConstraintToString(constraint, model_, true) +
3455 "\n";
3456 at_least_one_not_enforced = true;
3457 }
3458 }
3459 }
3460 if (!at_least_one_not_enforced) {
3461 s += prefix_constraint + "None\n";
3462 }
3463
3464 // Constraints with a SINGLE enforcement
3465 s += "\n- Single enforcement\n";
3466 bool at_least_one_single_enforced = false;
3467 for (const auto& pair : variable_instances) {
3468 const std::string lemma = pair.first;
3469 bool found_one_constraint = false;
3470 std::string prefix = "";
3471 for (int instance : pair.second) {
3472 const std::string enforcement =
3473 absl::StrFormat("%s(%d)", lemma, instance);
3474 auto* constraints_enforced = gtl::FindOrNull(constraints, enforcement);
3475 std::string prefix_instance = "";
3476 if (constraints_enforced != nullptr) {
3477 at_least_one_single_enforced = true;
3478 if (!found_one_constraint) {
3479 found_one_constraint = true;
3480 s += prefix_constraint + "if " + lemma + " | ";
3481 prefix =
3482 std::string(prefix_constraint.size() + 1 + lemma.size(), ' ') +
3483 " | ";
3484 } else {
3485 s += prefix;
3486 }
3487 s += absl::StrFormat("%d: | ", instance);
3488 prefix_instance = prefix + " | ";
3489 bool first = true;
3490 for (const auto& constraint : *constraints_enforced) {
3491 if (!first)
3492 s += prefix_instance;
3493 else
3494 first = false;
3495 s += ConstraintToString(constraint, model_, false) + "\n";
3496 }
3497 }
3498 }
3499 }
3500 if (!at_least_one_single_enforced) {
3501 s += prefix_constraint + "None\n";
3502 }
3503
3504 // Constraints with MULTIPLE enforcement
3505 s += "\n- Multiple enforcement\n";
3506 auto* constraints_multiple_enforced =
3507 gtl::FindOrNull(constraints, "multiple");
3508 if (constraints_multiple_enforced != nullptr) {
3509 for (const auto& constraint : *constraints_multiple_enforced) {
3510 s += prefix_constraint + ConstraintToString(constraint, model_, true) +
3511 "\n";
3512 }
3513 } else {
3514 s += prefix_constraint + "None\n";
3515 }
3516
3517 // Constraints apart
3518 s += "\n- Set apart\n";
3519 bool at_least_one_apart = false;
3520 for (const auto& pair : constraint_groups) {
3521 if (std::count(constraints_apart.begin(), constraints_apart.end(),
3522 pair.first)) {
3523 for (const auto& constraint : pair.second) {
3524 s += prefix_constraint + ConstraintToString(constraint, model_, true) +
3525 "\n";
3526 at_least_one_apart = true;
3527 }
3528 }
3529 }
3530 if (!at_least_one_apart) {
3531 s += prefix_constraint + "None\n";
3532 }
3533
3534 return s;
3535}
3536
3537} // namespace operations_research
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)
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)
NOTE(user): T=bool is not yet supported (the [] operator doesn't work).
Definition flat_matrix.h:32
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.
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.
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)
Definition mathutil.h:260
std::string PrintModel() const override
Prints an understandable view of the model.
virtual int64_t GetObjectiveValue() const =0
virtual DimensionSchedulingStatus Solve(absl::Duration duration_limit)=0
virtual int64_t GetVariableUpperBound(int index) const =0
virtual void SetObjectiveCoefficient(int index, double coefficient)=0
virtual bool SetVariableBounds(int index, int64_t lower_bound, int64_t upper_bound)=0
virtual void SetParameters(const std::string &parameters)=0
This function is meant to override the parameters of the solver.
virtual bool ModelIsEmpty() const
Returns if the model is empty or not.
virtual int64_t GetVariableLowerBound(int index) const =0
static constexpr SchedulingSolver SCHEDULING_GLOP
RoutingSearchParameters_SchedulingSolver SchedulingSolver
static constexpr SchedulingSolver SCHEDULING_CP_SAT
void SetNodeSupply(NodeIndex node, FlowQuantity supply)
FlowQuantity Flow(ArcIndex arc) const
ArcIndex AddArcWithCapacityAndUnitCost(NodeIndex tail, NodeIndex head, FlowQuantity capacity, CostValue unit_cost)
::int32_t enforcement_literal(int index) const
const ::operations_research::sat::LinearConstraintProto & linear() const
bool has_linear() const
.operations_research.sat.LinearConstraintProto linear = 12;
int enforcement_literal_size() const
repeated int32 enforcement_literal = 2;
const ::operations_research::sat::IntegerVariableProto & variables(int index) const
::operations_research::sat::CpSolverStatus status() const
::google::protobuf::RepeatedField<::int64_t > *PROTOBUF_NONNULL mutable_domain()
#define DUMP_VARS(...)
Definition dump_vars.h:78
const Collection::value_type::second_type * FindOrNull(const Collection &collection, const typename Collection::value_type::first_type &key)
Definition map_util.h:65
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)
Definition lp_utils.cc:873
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)
int64_t CapSub(int64_t x, int64_t y)
std::vector< bool > SlopeAndYInterceptToConvexityRegions(absl::Span< const SlopeAndYIntercept > slope_and_y_intercept)
ClosedInterval::Iterator end(ClosedInterval interval)
std::string ProtobufShortDebugString(const P &message)
Definition proto_utils.h:46
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)
Definition routing.cc:6745
std::string VariableToString(std::pair< sat::IntegerVariableProto, int > &variable_pair, const sat::CpSolverResponse &response_)
#define SET_DEBUG_VARIABLE_NAME(solver, var, name)
Definition model.h:51
static const int64_t kint64max
Definition types.h:32
static const int64_t kint64min
Definition types.h:31