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