OR-Tools  9.1
routing_lp_scheduling.cc
Go to the documentation of this file.
1// Copyright 2010-2021 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 <cstdint>
18#include <deque>
19#include <functional>
20#include <limits>
21#include <memory>
22#include <numeric>
23#include <utility>
24#include <vector>
25
26#include "absl/memory/memory.h"
35
36namespace operations_research {
37
38namespace {
39
40// The following sets of parameters give the fastest response time without
41// impacting solutions found negatively.
42glop::GlopParameters GetGlopParametersForLocalLP() {
43 glop::GlopParameters parameters;
44 parameters.set_use_dual_simplex(true);
45 parameters.set_use_preprocessing(false);
46 return parameters;
47}
48
49glop::GlopParameters GetGlopParametersForGlobalLP() {
50 glop::GlopParameters parameters;
51 parameters.set_use_dual_simplex(true);
52 return parameters;
53}
54
55bool GetCumulBoundsWithOffset(const RoutingDimension& dimension,
56 int64_t node_index, int64_t cumul_offset,
57 int64_t* lower_bound, int64_t* upper_bound) {
58 DCHECK(lower_bound != nullptr);
59 DCHECK(upper_bound != nullptr);
60
61 const IntVar& cumul_var = *dimension.CumulVar(node_index);
62 *upper_bound = cumul_var.Max();
63 if (*upper_bound < cumul_offset) {
64 return false;
65 }
66
67 const int64_t first_after_offset =
68 std::max(dimension.GetFirstPossibleGreaterOrEqualValueForNode(
69 node_index, cumul_offset),
70 cumul_var.Min());
71 DCHECK_LT(first_after_offset, std::numeric_limits<int64_t>::max());
72 *lower_bound = CapSub(first_after_offset, cumul_offset);
74
76 return true;
77 }
78 *upper_bound = CapSub(*upper_bound, cumul_offset);
80 return true;
81}
82
83int64_t GetFirstPossibleValueForCumulWithOffset(
84 const RoutingDimension& dimension, int64_t node_index,
85 int64_t lower_bound_without_offset, int64_t cumul_offset) {
86 return CapSub(
87 dimension.GetFirstPossibleGreaterOrEqualValueForNode(
88 node_index, CapAdd(lower_bound_without_offset, cumul_offset)),
89 cumul_offset);
90}
91
92int64_t GetLastPossibleValueForCumulWithOffset(
93 const RoutingDimension& dimension, int64_t node_index,
94 int64_t upper_bound_without_offset, int64_t cumul_offset) {
95 return CapSub(
96 dimension.GetLastPossibleLessOrEqualValueForNode(
97 node_index, CapAdd(upper_bound_without_offset, cumul_offset)),
98 cumul_offset);
99}
100
101// Finds the pickup/delivery pairs of nodes on a given vehicle's route.
102// Returns the vector of visited pair indices, and stores the corresponding
103// pickup/delivery indices in visited_pickup_delivery_indices_for_pair_.
104// NOTE: Supposes that visited_pickup_delivery_indices_for_pair is correctly
105// sized and initialized to {-1, -1} for all pairs.
106void StoreVisitedPickupDeliveryPairsOnRoute(
107 const RoutingDimension& dimension, int vehicle,
108 const std::function<int64_t(int64_t)>& next_accessor,
109 std::vector<int>* visited_pairs,
110 std::vector<std::pair<int64_t, int64_t>>*
111 visited_pickup_delivery_indices_for_pair) {
112 // visited_pickup_delivery_indices_for_pair must be all {-1, -1}.
113 DCHECK_EQ(visited_pickup_delivery_indices_for_pair->size(),
114 dimension.model()->GetPickupAndDeliveryPairs().size());
115 DCHECK(std::all_of(visited_pickup_delivery_indices_for_pair->begin(),
116 visited_pickup_delivery_indices_for_pair->end(),
117 [](std::pair<int64_t, int64_t> p) {
118 return p.first == -1 && p.second == -1;
119 }));
120 visited_pairs->clear();
121 if (!dimension.HasPickupToDeliveryLimits()) {
122 return;
123 }
124 const RoutingModel& model = *dimension.model();
125
126 int64_t node_index = model.Start(vehicle);
127 while (!model.IsEnd(node_index)) {
128 const std::vector<std::pair<int, int>>& pickup_index_pairs =
129 model.GetPickupIndexPairs(node_index);
130 const std::vector<std::pair<int, int>>& delivery_index_pairs =
131 model.GetDeliveryIndexPairs(node_index);
132 if (!pickup_index_pairs.empty()) {
133 // The current node is a pickup. We verify that it belongs to a single
134 // pickup index pair and that it's not a delivery, and store the index.
135 DCHECK(delivery_index_pairs.empty());
136 DCHECK_EQ(pickup_index_pairs.size(), 1);
137 (*visited_pickup_delivery_indices_for_pair)[pickup_index_pairs[0].first]
138 .first = node_index;
139 visited_pairs->push_back(pickup_index_pairs[0].first);
140 } else if (!delivery_index_pairs.empty()) {
141 // The node is a delivery. We verify that it belongs to a single
142 // delivery pair, and set the limit with its pickup if one has been
143 // visited for this pair.
144 DCHECK_EQ(delivery_index_pairs.size(), 1);
145 const int pair_index = delivery_index_pairs[0].first;
146 std::pair<int64_t, int64_t>& pickup_delivery_index =
147 (*visited_pickup_delivery_indices_for_pair)[pair_index];
148 if (pickup_delivery_index.first < 0) {
149 // This case should not happen, as a delivery must have its pickup
150 // on the route, but we ignore it here.
151 node_index = next_accessor(node_index);
152 continue;
153 }
154 pickup_delivery_index.second = node_index;
155 }
156 node_index = next_accessor(node_index);
157 }
158}
159
160} // namespace
161
163 const RoutingDimension* dimension,
165 : optimizer_core_(dimension, /*use_precedence_propagator=*/false) {
166 // Using one solver per vehicle in the hope that if routes don't change this
167 // will be faster.
168 const int vehicles = dimension->model()->vehicles();
169 solver_.resize(vehicles);
170 switch (solver_type) {
172 const glop::GlopParameters parameters = GetGlopParametersForLocalLP();
173 for (int vehicle = 0; vehicle < vehicles; ++vehicle) {
174 // TODO(user): Instead of passing false, detect if the relaxation
175 // will always violate the MIPL constraints.
176 solver_[vehicle] =
177 absl::make_unique<RoutingGlopWrapper>(false, parameters);
178 }
179 break;
180 }
182 for (int vehicle = 0; vehicle < vehicles; ++vehicle) {
183 solver_[vehicle] = absl::make_unique<RoutingCPSatWrapper>();
184 }
185 break;
186 }
187 default:
188 LOG(DFATAL) << "Unrecognized solver type: " << solver_type;
189 }
190}
191
193 int vehicle, const std::function<int64_t(int64_t)>& next_accessor,
194 int64_t* optimal_cost) {
195 return optimizer_core_.OptimizeSingleRoute(vehicle, next_accessor,
196 solver_[vehicle].get(), nullptr,
197 nullptr, optimal_cost, nullptr);
198}
199
202 int vehicle, const std::function<int64_t(int64_t)>& next_accessor,
203 int64_t* optimal_cost_without_transits) {
204 int64_t cost = 0;
205 int64_t transit_cost = 0;
206 const DimensionSchedulingStatus status = optimizer_core_.OptimizeSingleRoute(
207 vehicle, next_accessor, solver_[vehicle].get(), nullptr, nullptr, &cost,
208 &transit_cost);
210 optimal_cost_without_transits != nullptr) {
211 *optimal_cost_without_transits = CapSub(cost, transit_cost);
212 }
213 return status;
214}
215
217 int vehicle, const std::function<int64_t(int64_t)>& next_accessor,
218 std::vector<int64_t>* optimal_cumuls,
219 std::vector<int64_t>* optimal_breaks) {
220 return optimizer_core_.OptimizeSingleRoute(
221 vehicle, next_accessor, solver_[vehicle].get(), optimal_cumuls,
222 optimal_breaks, nullptr, nullptr);
223}
224
227 int vehicle, const std::function<int64_t(int64_t)>& next_accessor,
228 std::vector<int64_t>* packed_cumuls, std::vector<int64_t>* packed_breaks) {
229 return optimizer_core_.OptimizeAndPackSingleRoute(
230 vehicle, next_accessor, solver_[vehicle].get(), packed_cumuls,
231 packed_breaks);
232}
233
234const int CumulBoundsPropagator::kNoParent = -2;
235const int CumulBoundsPropagator::kParentToBePropagated = -1;
236
238 : dimension_(*dimension), num_nodes_(2 * dimension->cumuls().size()) {
239 outgoing_arcs_.resize(num_nodes_);
240 node_in_queue_.resize(num_nodes_, false);
241 tree_parent_node_of_.resize(num_nodes_, kNoParent);
242 propagated_bounds_.resize(num_nodes_);
243 visited_pickup_delivery_indices_for_pair_.resize(
244 dimension->model()->GetPickupAndDeliveryPairs().size(), {-1, -1});
245}
246
247void CumulBoundsPropagator::AddArcs(int first_index, int second_index,
248 int64_t offset) {
249 // Add arc first_index + offset <= second_index
250 outgoing_arcs_[PositiveNode(first_index)].push_back(
251 {PositiveNode(second_index), offset});
252 AddNodeToQueue(PositiveNode(first_index));
253 // Add arc -second_index + transit <= -first_index
254 outgoing_arcs_[NegativeNode(second_index)].push_back(
255 {NegativeNode(first_index), offset});
256 AddNodeToQueue(NegativeNode(second_index));
257}
258
259bool CumulBoundsPropagator::InitializeArcsAndBounds(
260 const std::function<int64_t(int64_t)>& next_accessor,
261 int64_t cumul_offset) {
262 propagated_bounds_.assign(num_nodes_, std::numeric_limits<int64_t>::min());
263
264 for (std::vector<ArcInfo>& arcs : outgoing_arcs_) {
265 arcs.clear();
266 }
267
268 RoutingModel* const model = dimension_.model();
269 std::vector<int64_t>& lower_bounds = propagated_bounds_;
270
271 for (int vehicle = 0; vehicle < model->vehicles(); vehicle++) {
272 const std::function<int64_t(int64_t, int64_t)>& transit_accessor =
273 dimension_.transit_evaluator(vehicle);
274
275 int node = model->Start(vehicle);
276 while (true) {
277 int64_t cumul_lb, cumul_ub;
278 if (!GetCumulBoundsWithOffset(dimension_, node, cumul_offset, &cumul_lb,
279 &cumul_ub)) {
280 return false;
281 }
282 lower_bounds[PositiveNode(node)] = cumul_lb;
283 if (cumul_ub < std::numeric_limits<int64_t>::max()) {
284 lower_bounds[NegativeNode(node)] = -cumul_ub;
285 }
286
287 if (model->IsEnd(node)) {
288 break;
289 }
290
291 const int next = next_accessor(node);
292 const int64_t transit = transit_accessor(node, next);
293 const IntVar& slack_var = *dimension_.SlackVar(node);
294 // node + transit + slack_var == next
295 // Add arcs for node + transit + slack_min <= next
296 AddArcs(node, next, CapAdd(transit, slack_var.Min()));
297 if (slack_var.Max() < std::numeric_limits<int64_t>::max()) {
298 // Add arcs for node + transit + slack_max >= next.
299 AddArcs(next, node, CapSub(-slack_var.Max(), transit));
300 }
301
302 node = next;
303 }
304
305 // Add vehicle span upper bound: end - span_ub <= start.
306 const int64_t span_ub = dimension_.GetSpanUpperBoundForVehicle(vehicle);
307 if (span_ub < std::numeric_limits<int64_t>::max()) {
308 AddArcs(model->End(vehicle), model->Start(vehicle), -span_ub);
309 }
310
311 // Set pickup/delivery limits on route.
312 std::vector<int> visited_pairs;
313 StoreVisitedPickupDeliveryPairsOnRoute(
314 dimension_, vehicle, next_accessor, &visited_pairs,
315 &visited_pickup_delivery_indices_for_pair_);
316 for (int pair_index : visited_pairs) {
317 const int64_t pickup_index =
318 visited_pickup_delivery_indices_for_pair_[pair_index].first;
319 const int64_t delivery_index =
320 visited_pickup_delivery_indices_for_pair_[pair_index].second;
321 visited_pickup_delivery_indices_for_pair_[pair_index] = {-1, -1};
322
323 DCHECK_GE(pickup_index, 0);
324 if (delivery_index < 0) {
325 // We didn't encounter a delivery for this pickup.
326 continue;
327 }
328
329 const int64_t limit = dimension_.GetPickupToDeliveryLimitForPair(
330 pair_index, model->GetPickupIndexPairs(pickup_index)[0].second,
331 model->GetDeliveryIndexPairs(delivery_index)[0].second);
332 if (limit < std::numeric_limits<int64_t>::max()) {
333 // delivery_cumul - limit <= pickup_cumul.
334 AddArcs(delivery_index, pickup_index, -limit);
335 }
336 }
337 }
338
339 for (const RoutingDimension::NodePrecedence& precedence :
340 dimension_.GetNodePrecedences()) {
341 const int first_index = precedence.first_node;
342 const int second_index = precedence.second_node;
343 if (lower_bounds[PositiveNode(first_index)] ==
345 lower_bounds[PositiveNode(second_index)] ==
347 // One of the nodes is unperformed, so the precedence rule doesn't apply.
348 continue;
349 }
350 AddArcs(first_index, second_index, precedence.offset);
351 }
352
353 return true;
354}
355
356bool CumulBoundsPropagator::UpdateCurrentLowerBoundOfNode(int node,
357 int64_t new_lb,
358 int64_t offset) {
359 const int cumul_var_index = node / 2;
360
361 if (node == PositiveNode(cumul_var_index)) {
362 // new_lb is a lower bound of the cumul of variable 'cumul_var_index'.
363 propagated_bounds_[node] = GetFirstPossibleValueForCumulWithOffset(
364 dimension_, cumul_var_index, new_lb, offset);
365 } else {
366 // -new_lb is an upper bound of the cumul of variable 'cumul_var_index'.
367 const int64_t new_ub = CapSub(0, new_lb);
368 propagated_bounds_[node] =
369 CapSub(0, GetLastPossibleValueForCumulWithOffset(
370 dimension_, cumul_var_index, new_ub, offset));
371 }
372
373 // Test that the lower/upper bounds do not cross each other.
374 const int64_t cumul_lower_bound =
375 propagated_bounds_[PositiveNode(cumul_var_index)];
376
377 const int64_t negated_cumul_upper_bound =
378 propagated_bounds_[NegativeNode(cumul_var_index)];
379
380 return CapAdd(negated_cumul_upper_bound, cumul_lower_bound) <= 0;
381}
382
383bool CumulBoundsPropagator::DisassembleSubtree(int source, int target) {
384 tmp_dfs_stack_.clear();
385 tmp_dfs_stack_.push_back(source);
386 while (!tmp_dfs_stack_.empty()) {
387 const int tail = tmp_dfs_stack_.back();
388 tmp_dfs_stack_.pop_back();
389 for (const ArcInfo& arc : outgoing_arcs_[tail]) {
390 const int child_node = arc.head;
391 if (tree_parent_node_of_[child_node] != tail) continue;
392 if (child_node == target) return false;
393 tree_parent_node_of_[child_node] = kParentToBePropagated;
394 tmp_dfs_stack_.push_back(child_node);
395 }
396 }
397 return true;
398}
399
401 const std::function<int64_t(int64_t)>& next_accessor,
402 int64_t cumul_offset) {
403 tree_parent_node_of_.assign(num_nodes_, kNoParent);
404 DCHECK(std::none_of(node_in_queue_.begin(), node_in_queue_.end(),
405 [](bool b) { return b; }));
406 DCHECK(bf_queue_.empty());
407
408 if (!InitializeArcsAndBounds(next_accessor, cumul_offset)) {
409 return CleanupAndReturnFalse();
410 }
411
412 std::vector<int64_t>& current_lb = propagated_bounds_;
413
414 // Bellman-Ford-Tarjan algorithm.
415 while (!bf_queue_.empty()) {
416 const int node = bf_queue_.front();
417 bf_queue_.pop_front();
418 node_in_queue_[node] = false;
419
420 if (tree_parent_node_of_[node] == kParentToBePropagated) {
421 // The parent of this node is still in the queue, so no need to process
422 // node now, since it will be re-enqued when its parent is processed.
423 continue;
424 }
425
426 const int64_t lower_bound = current_lb[node];
427 for (const ArcInfo& arc : outgoing_arcs_[node]) {
428 // NOTE: kint64min as a lower bound means no lower bound at all, so we
429 // don't use this value to propagate.
430 const int64_t induced_lb =
433 : CapAdd(lower_bound, arc.offset);
434
435 const int head_node = arc.head;
436 if (induced_lb <= current_lb[head_node]) {
437 // No update necessary for the head_node, continue to next children of
438 // node.
439 continue;
440 }
441 if (!UpdateCurrentLowerBoundOfNode(head_node, induced_lb, cumul_offset) ||
442 !DisassembleSubtree(head_node, node)) {
443 // The new lower bound is infeasible, or a positive cycle was detected
444 // in the precedence graph by DisassembleSubtree().
445 return CleanupAndReturnFalse();
446 }
447
448 tree_parent_node_of_[head_node] = node;
449 AddNodeToQueue(head_node);
450 }
451 }
452 return true;
453}
454
456 const RoutingDimension* dimension, bool use_precedence_propagator)
457 : dimension_(dimension),
458 visited_pickup_delivery_indices_for_pair_(
459 dimension->model()->GetPickupAndDeliveryPairs().size(), {-1, -1}) {
460 if (use_precedence_propagator) {
461 propagator_ = absl::make_unique<CumulBoundsPropagator>(dimension);
462 }
463 if (dimension_->HasBreakConstraints()) {
464 // Initialize vehicle_to_first_index_ so the variables of the breaks of
465 // vehicle v are stored from vehicle_to_first_index_[v] to
466 // vehicle_to_first_index_[v+1] - 1.
467 const int num_vehicles = dimension_->model()->vehicles();
468 vehicle_to_all_break_variables_offset_.reserve(num_vehicles);
469 int num_break_vars = 0;
470 for (int vehicle = 0; vehicle < num_vehicles; ++vehicle) {
471 vehicle_to_all_break_variables_offset_.push_back(num_break_vars);
472 const auto& intervals = dimension_->GetBreakIntervalsOfVehicle(vehicle);
473 num_break_vars += 2 * intervals.size(); // 2 variables per break.
474 }
475 all_break_variables_.resize(num_break_vars, -1);
476 }
477}
478
480 int vehicle, const std::function<int64_t(int64_t)>& next_accessor,
481 RoutingLinearSolverWrapper* solver, std::vector<int64_t>* cumul_values,
482 std::vector<int64_t>* break_values, int64_t* cost, int64_t* transit_cost,
483 bool clear_lp) {
484 InitOptimizer(solver);
485 // Make sure SetRouteCumulConstraints will properly set the cumul bounds by
486 // looking at this route only.
487 DCHECK(propagator_ == nullptr);
488
489 const RoutingModel* const model = dimension()->model();
490 const bool optimize_vehicle_costs =
491 (cumul_values != nullptr || cost != nullptr) &&
492 (!model->IsEnd(next_accessor(model->Start(vehicle))) ||
493 model->AreEmptyRouteCostsConsideredForVehicle(vehicle));
494 const int64_t cumul_offset =
495 dimension_->GetLocalOptimizerOffsetForVehicle(vehicle);
496 int64_t cost_offset = 0;
497 if (!SetRouteCumulConstraints(vehicle, next_accessor, cumul_offset,
498 optimize_vehicle_costs, solver, transit_cost,
499 &cost_offset)) {
501 }
502 const DimensionSchedulingStatus status =
503 solver->Solve(model->RemainingTime());
505 return status;
506 }
507
508 SetValuesFromLP(current_route_cumul_variables_, cumul_offset, solver,
509 cumul_values);
510 SetValuesFromLP(current_route_break_variables_, cumul_offset, solver,
511 break_values);
512 if (cost != nullptr) {
513 *cost = CapAdd(cost_offset, solver->GetObjectiveValue());
514 }
515
516 if (clear_lp) {
517 solver->Clear();
518 }
519 return status;
520}
521
523 const std::function<int64_t(int64_t)>& next_accessor,
524 RoutingLinearSolverWrapper* solver, std::vector<int64_t>* cumul_values,
525 std::vector<int64_t>* break_values, int64_t* cost, int64_t* transit_cost,
526 bool clear_lp) {
527 InitOptimizer(solver);
528
529 // If both "cumul_values" and "cost" parameters are null, we don't try to
530 // optimize the cost and stop at the first feasible solution.
531 const bool optimize_costs = (cumul_values != nullptr) || (cost != nullptr);
532 bool has_vehicles_being_optimized = false;
533
534 const int64_t cumul_offset = dimension_->GetGlobalOptimizerOffset();
535
536 if (propagator_ != nullptr &&
537 !propagator_->PropagateCumulBounds(next_accessor, cumul_offset)) {
539 }
540
541 int64_t total_transit_cost = 0;
542 int64_t total_cost_offset = 0;
543 const RoutingModel* model = dimension()->model();
544 int num_needed_resources = 0;
545 int num_available_resources = model->vehicles();
546 const auto& resource_groups = model->GetResourceGroups();
547 for (int rg_index : model->GetDimensionResourceGroupIndices(dimension_)) {
548 num_available_resources =
549 std::min(num_available_resources, resource_groups[rg_index]->Size());
550 }
551 DCHECK_GT(num_available_resources, 0);
552 for (int vehicle = 0; vehicle < model->vehicles(); vehicle++) {
553 int64_t route_transit_cost = 0;
554 int64_t route_cost_offset = 0;
555 const bool vehicle_is_used =
556 !model->IsEnd(next_accessor(model->Start(vehicle))) ||
557 model->AreEmptyRouteCostsConsideredForVehicle(vehicle);
558 if (vehicle_is_used && ++num_needed_resources > num_available_resources) {
560 }
561 const bool optimize_vehicle_costs = optimize_costs && vehicle_is_used;
562 if (!SetRouteCumulConstraints(vehicle, next_accessor, cumul_offset,
563 optimize_vehicle_costs, solver,
564 &route_transit_cost, &route_cost_offset)) {
566 }
567 total_transit_cost = CapAdd(total_transit_cost, route_transit_cost);
568 total_cost_offset = CapAdd(total_cost_offset, route_cost_offset);
569 has_vehicles_being_optimized |= optimize_vehicle_costs;
570 }
571 if (transit_cost != nullptr) {
572 *transit_cost = total_transit_cost;
573 }
574
575 SetGlobalConstraints(next_accessor, cumul_offset,
576 has_vehicles_being_optimized, solver);
577
578 const DimensionSchedulingStatus status =
579 solver->Solve(model->RemainingTime());
581 return status;
582 }
583
584 // TODO(user): In case the status is RELAXED_OPTIMAL_ONLY, check we can
585 // safely avoid filling variable and cost values.
586 SetValuesFromLP(index_to_cumul_variable_, cumul_offset, solver, cumul_values);
587 SetValuesFromLP(all_break_variables_, cumul_offset, solver, break_values);
588
589 if (cost != nullptr) {
590 *cost = CapAdd(solver->GetObjectiveValue(), total_cost_offset);
591 }
592
593 if (clear_lp) {
594 solver->Clear();
595 }
596 return status;
597}
598
600 const std::function<int64_t(int64_t)>& next_accessor,
601 RoutingLinearSolverWrapper* solver, std::vector<int64_t>* cumul_values,
602 std::vector<int64_t>* break_values) {
603 // Note: We pass a non-nullptr cost to the Optimize() method so the costs
604 // are optimized by the solver.
605 int64_t cost = 0;
606 if (Optimize(next_accessor, solver,
607 /*cumul_values=*/nullptr, /*break_values=*/nullptr, &cost,
608 /*transit_cost=*/nullptr,
609 /*clear_lp=*/false) == DimensionSchedulingStatus::INFEASIBLE) {
611 }
612 std::vector<int> vehicles(dimension()->model()->vehicles());
613 std::iota(vehicles.begin(), vehicles.end(), 0);
614 // Subtle: Even if the status was RELAXED_OPTIMAL_ONLY we try to pack just in
615 // case packing manages to make the solution completely feasible.
616 DimensionSchedulingStatus status = PackRoutes(vehicles, solver);
618 return status;
619 }
620 // TODO(user): In case the status is RELAXED_OPTIMAL_ONLY, check we can
621 // safely avoid filling variable values.
622 const int64_t global_offset = dimension_->GetGlobalOptimizerOffset();
623 SetValuesFromLP(index_to_cumul_variable_, global_offset, solver,
624 cumul_values);
625 SetValuesFromLP(all_break_variables_, global_offset, solver, break_values);
626 solver->Clear();
627 return status;
628}
629
632 int vehicle, const std::function<int64_t(int64_t)>& next_accessor,
633 RoutingLinearSolverWrapper* solver, std::vector<int64_t>* cumul_values,
634 std::vector<int64_t>* break_values) {
635 // Note: We pass a non-nullptr cost to the OptimizeSingleRoute() method so the
636 // costs are optimized by the LP.
637 int64_t cost = 0;
638 if (OptimizeSingleRoute(vehicle, next_accessor, solver,
639 /*cumul_values=*/nullptr, /*break_values=*/nullptr,
640 &cost, /*transit_cost=*/nullptr,
641 /*clear_lp=*/false) ==
644 }
645 const DimensionSchedulingStatus status = PackRoutes({vehicle}, solver);
648 }
649 const int64_t local_offset =
650 dimension_->GetLocalOptimizerOffsetForVehicle(vehicle);
651 SetValuesFromLP(current_route_cumul_variables_, local_offset, solver,
652 cumul_values);
653 SetValuesFromLP(current_route_break_variables_, local_offset, solver,
654 break_values);
655 solver->Clear();
656 return status;
657}
658
659DimensionSchedulingStatus DimensionCumulOptimizerCore::PackRoutes(
660 std::vector<int> vehicles, RoutingLinearSolverWrapper* solver) {
661 const RoutingModel* model = dimension_->model();
662
663 // NOTE(user): Given our constraint matrix, our problem *should* always
664 // have an integer optimal solution, in which case we can round to the nearest
665 // integer both for the objective constraint bound (returned by
666 // GetObjectiveValue()) and the end cumul variable bound after minimizing
667 // (see b/154381899 showcasing an example where std::ceil leads to an
668 // "imperfect" packing due to rounding precision errors).
669 // If this DCHECK ever fails, it can be removed but the code below should be
670 // adapted to have a 2-phase approach, solving once with the rounded value as
671 // bound and if this fails, solve again using std::ceil.
672 DCHECK(solver->SolutionIsInteger());
673
674 // Minimize the route end times without increasing the cost.
675 solver->AddObjectiveConstraint();
676 solver->ClearObjective();
677 for (int vehicle : vehicles) {
679 index_to_cumul_variable_[model->End(vehicle)], 1);
680 }
681
682 if (solver->Solve(model->RemainingTime()) ==
685 }
686
687 // Maximize the route start times without increasing the cost or the route end
688 // times.
689 solver->ClearObjective();
690 for (int vehicle : vehicles) {
691 const int end_cumul_var = index_to_cumul_variable_[model->End(vehicle)];
692 // end_cumul_var <= solver.GetValue(end_cumul_var)
693 solver->SetVariableBounds(
694 end_cumul_var, solver->GetVariableLowerBound(end_cumul_var),
695 MathUtil::FastInt64Round(solver->GetValue(end_cumul_var)));
696
697 // Maximize the starts of the routes.
699 index_to_cumul_variable_[model->Start(vehicle)], -1);
700 }
701 return solver->Solve(model->RemainingTime());
702}
703
704void DimensionCumulOptimizerCore::InitOptimizer(
705 RoutingLinearSolverWrapper* solver) {
706 solver->Clear();
707 index_to_cumul_variable_.assign(dimension_->cumuls().size(), -1);
708 max_end_cumul_ = solver->CreateNewPositiveVariable();
709 min_start_cumul_ = solver->CreateNewPositiveVariable();
710}
711
712bool DimensionCumulOptimizerCore::ComputeRouteCumulBounds(
713 const std::vector<int64_t>& route,
714 const std::vector<int64_t>& fixed_transits, int64_t cumul_offset) {
715 const int route_size = route.size();
716 current_route_min_cumuls_.resize(route_size);
717 current_route_max_cumuls_.resize(route_size);
718 if (propagator_ != nullptr) {
719 for (int pos = 0; pos < route_size; pos++) {
720 const int64_t node = route[pos];
721 current_route_min_cumuls_[pos] = propagator_->CumulMin(node);
722 DCHECK_GE(current_route_min_cumuls_[pos], 0);
723 current_route_max_cumuls_[pos] = propagator_->CumulMax(node);
724 DCHECK_GE(current_route_max_cumuls_[pos], current_route_min_cumuls_[pos]);
725 }
726 return true;
727 }
728
729 // Extract cumul min/max and fixed transits from CP.
730 for (int pos = 0; pos < route_size; ++pos) {
731 if (!GetCumulBoundsWithOffset(*dimension_, route[pos], cumul_offset,
732 &current_route_min_cumuls_[pos],
733 &current_route_max_cumuls_[pos])) {
734 return false;
735 }
736 }
737
738 // Refine cumul bounds using
739 // cumul[i+1] >= cumul[i] + fixed_transit[i] + slack[i].
740 for (int pos = 1; pos < route_size; ++pos) {
741 const int64_t slack_min = dimension_->SlackVar(route[pos - 1])->Min();
742 current_route_min_cumuls_[pos] = std::max(
743 current_route_min_cumuls_[pos],
744 CapAdd(
745 CapAdd(current_route_min_cumuls_[pos - 1], fixed_transits[pos - 1]),
746 slack_min));
747 current_route_min_cumuls_[pos] = GetFirstPossibleValueForCumulWithOffset(
748 *dimension_, route[pos], current_route_min_cumuls_[pos], cumul_offset);
749 if (current_route_min_cumuls_[pos] > current_route_max_cumuls_[pos]) {
750 return false;
751 }
752 }
753
754 for (int pos = route_size - 2; pos >= 0; --pos) {
755 // If cumul_max[pos+1] is kint64max, it will be translated to
756 // double +infinity, so it must not constrain cumul_max[pos].
757 if (current_route_max_cumuls_[pos + 1] <
759 const int64_t slack_min = dimension_->SlackVar(route[pos])->Min();
760 current_route_max_cumuls_[pos] = std::min(
761 current_route_max_cumuls_[pos],
762 CapSub(
763 CapSub(current_route_max_cumuls_[pos + 1], fixed_transits[pos]),
764 slack_min));
765 current_route_max_cumuls_[pos] = GetLastPossibleValueForCumulWithOffset(
766 *dimension_, route[pos], current_route_max_cumuls_[pos],
767 cumul_offset);
768 if (current_route_max_cumuls_[pos] < current_route_min_cumuls_[pos]) {
769 return false;
770 }
771 }
772 }
773 return true;
774}
775
776bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
777 int vehicle, const std::function<int64_t(int64_t)>& next_accessor,
778 int64_t cumul_offset, bool optimize_costs,
779 RoutingLinearSolverWrapper* solver, int64_t* route_transit_cost,
780 int64_t* route_cost_offset) {
781 RoutingModel* const model = dimension_->model();
782 // Extract the vehicle's path from next_accessor.
783 std::vector<int64_t> path;
784 {
785 int node = model->Start(vehicle);
786 path.push_back(node);
787 while (!model->IsEnd(node)) {
788 node = next_accessor(node);
789 path.push_back(node);
790 }
791 DCHECK_GE(path.size(), 2);
792 }
793 const int path_size = path.size();
794
795 std::vector<int64_t> fixed_transit(path_size - 1);
796 {
797 const std::function<int64_t(int64_t, int64_t)>& transit_accessor =
798 dimension_->transit_evaluator(vehicle);
799 for (int pos = 1; pos < path_size; ++pos) {
800 fixed_transit[pos - 1] = transit_accessor(path[pos - 1], path[pos]);
801 }
802 }
803
804 if (!ComputeRouteCumulBounds(path, fixed_transit, cumul_offset)) {
805 return false;
806 }
807
808 // LP Model variables, current_route_cumul_variables_ and lp_slacks.
809 // Create LP variables for cumuls.
810 std::vector<int>& lp_cumuls = current_route_cumul_variables_;
811 lp_cumuls.assign(path_size, -1);
812 for (int pos = 0; pos < path_size; ++pos) {
813 const int lp_cumul = solver->CreateNewPositiveVariable();
814 index_to_cumul_variable_[path[pos]] = lp_cumul;
815 lp_cumuls[pos] = lp_cumul;
816 if (!solver->SetVariableBounds(lp_cumul, current_route_min_cumuls_[pos],
817 current_route_max_cumuls_[pos])) {
818 return false;
819 }
820 const SortedDisjointIntervalList& forbidden =
821 dimension_->forbidden_intervals()[path[pos]];
822 if (forbidden.NumIntervals() > 0) {
823 std::vector<int64_t> starts;
824 std::vector<int64_t> ends;
825 for (const ClosedInterval interval :
826 dimension_->GetAllowedIntervalsInRange(
827 path[pos], CapAdd(current_route_min_cumuls_[pos], cumul_offset),
828 CapAdd(current_route_max_cumuls_[pos], cumul_offset))) {
829 starts.push_back(CapSub(interval.start, cumul_offset));
830 ends.push_back(CapSub(interval.end, cumul_offset));
831 }
832 solver->SetVariableDisjointBounds(lp_cumul, starts, ends);
833 }
834 }
835 // Create LP variables for slacks.
836 std::vector<int> lp_slacks(path_size - 1, -1);
837 for (int pos = 0; pos < path_size - 1; ++pos) {
838 const IntVar* cp_slack = dimension_->SlackVar(path[pos]);
839 lp_slacks[pos] = solver->CreateNewPositiveVariable();
840 if (!solver->SetVariableBounds(lp_slacks[pos], cp_slack->Min(),
841 cp_slack->Max())) {
842 return false;
843 }
844 }
845
846 // LP Model constraints and costs.
847 // Add all path constraints to LP:
848 // cumul[i] + fixed_transit[i] + slack[i] == cumul[i+1]
849 // <=> fixed_transit[i] == cumul[i+1] - cumul[i] - slack[i].
850 for (int pos = 0; pos < path_size - 1; ++pos) {
851 const int ct =
852 solver->CreateNewConstraint(fixed_transit[pos], fixed_transit[pos]);
853 solver->SetCoefficient(ct, lp_cumuls[pos + 1], 1);
854 solver->SetCoefficient(ct, lp_cumuls[pos], -1);
855 solver->SetCoefficient(ct, lp_slacks[pos], -1);
856 }
857 if (route_cost_offset != nullptr) *route_cost_offset = 0;
858 if (optimize_costs) {
859 // Add soft upper bounds.
860 for (int pos = 0; pos < path_size; ++pos) {
861 if (!dimension_->HasCumulVarSoftUpperBound(path[pos])) continue;
862 const int64_t coef =
863 dimension_->GetCumulVarSoftUpperBoundCoefficient(path[pos]);
864 if (coef == 0) continue;
865 int64_t bound = dimension_->GetCumulVarSoftUpperBound(path[pos]);
866 if (bound < cumul_offset && route_cost_offset != nullptr) {
867 // Add coef * (cumul_offset - bound) to the cost offset.
868 *route_cost_offset = CapAdd(*route_cost_offset,
869 CapProd(CapSub(cumul_offset, bound), coef));
870 }
871 bound = std::max<int64_t>(0, CapSub(bound, cumul_offset));
872 if (current_route_max_cumuls_[pos] <= bound) {
873 // constraint is never violated.
874 continue;
875 }
876 const int soft_ub_diff = solver->CreateNewPositiveVariable();
877 solver->SetObjectiveCoefficient(soft_ub_diff, coef);
878 // cumul - soft_ub_diff <= bound.
879 const int ct = solver->CreateNewConstraint(
881 solver->SetCoefficient(ct, lp_cumuls[pos], 1);
882 solver->SetCoefficient(ct, soft_ub_diff, -1);
883 }
884 // Add soft lower bounds.
885 for (int pos = 0; pos < path_size; ++pos) {
886 if (!dimension_->HasCumulVarSoftLowerBound(path[pos])) continue;
887 const int64_t coef =
888 dimension_->GetCumulVarSoftLowerBoundCoefficient(path[pos]);
889 if (coef == 0) continue;
890 const int64_t bound = std::max<int64_t>(
891 0, CapSub(dimension_->GetCumulVarSoftLowerBound(path[pos]),
892 cumul_offset));
893 if (current_route_min_cumuls_[pos] >= bound) {
894 // constraint is never violated.
895 continue;
896 }
897 const int soft_lb_diff = solver->CreateNewPositiveVariable();
898 solver->SetObjectiveCoefficient(soft_lb_diff, coef);
899 // bound - cumul <= soft_lb_diff
900 const int ct = solver->CreateNewConstraint(
902 solver->SetCoefficient(ct, lp_cumuls[pos], 1);
903 solver->SetCoefficient(ct, soft_lb_diff, 1);
904 }
905 }
906 // Add pickup and delivery limits.
907 std::vector<int> visited_pairs;
908 StoreVisitedPickupDeliveryPairsOnRoute(
909 *dimension_, vehicle, next_accessor, &visited_pairs,
910 &visited_pickup_delivery_indices_for_pair_);
911 for (int pair_index : visited_pairs) {
912 const int64_t pickup_index =
913 visited_pickup_delivery_indices_for_pair_[pair_index].first;
914 const int64_t delivery_index =
915 visited_pickup_delivery_indices_for_pair_[pair_index].second;
916 visited_pickup_delivery_indices_for_pair_[pair_index] = {-1, -1};
917
918 DCHECK_GE(pickup_index, 0);
919 if (delivery_index < 0) {
920 // We didn't encounter a delivery for this pickup.
921 continue;
922 }
923
924 const int64_t limit = dimension_->GetPickupToDeliveryLimitForPair(
925 pair_index, model->GetPickupIndexPairs(pickup_index)[0].second,
926 model->GetDeliveryIndexPairs(delivery_index)[0].second);
927 if (limit < std::numeric_limits<int64_t>::max()) {
928 // delivery_cumul - pickup_cumul <= limit.
929 const int ct = solver->CreateNewConstraint(
931 solver->SetCoefficient(ct, index_to_cumul_variable_[delivery_index], 1);
932 solver->SetCoefficient(ct, index_to_cumul_variable_[pickup_index], -1);
933 }
934 }
935
936 // Add span bound constraint.
937 const int64_t span_bound = dimension_->GetSpanUpperBoundForVehicle(vehicle);
938 if (span_bound < std::numeric_limits<int64_t>::max()) {
939 // end_cumul - start_cumul <= bound
940 const int ct = solver->CreateNewConstraint(
942 solver->SetCoefficient(ct, lp_cumuls.back(), 1);
943 solver->SetCoefficient(ct, lp_cumuls.front(), -1);
944 }
945 // Add span cost.
946 const int64_t span_cost_coef =
947 dimension_->GetSpanCostCoefficientForVehicle(vehicle);
948 if (optimize_costs && span_cost_coef > 0) {
949 solver->SetObjectiveCoefficient(lp_cumuls.back(), span_cost_coef);
950 solver->SetObjectiveCoefficient(lp_cumuls.front(), -span_cost_coef);
951 }
952 // Add soft span cost.
953 if (optimize_costs && dimension_->HasSoftSpanUpperBounds()) {
954 SimpleBoundCosts::BoundCost bound_cost =
955 dimension_->GetSoftSpanUpperBoundForVehicle(vehicle);
956 if (bound_cost.bound < std::numeric_limits<int64_t>::max() &&
957 bound_cost.cost > 0) {
958 const int span_violation = solver->CreateNewPositiveVariable();
959 // end - start <= bound + span_violation
960 const int violation = solver->CreateNewConstraint(
961 std::numeric_limits<int64_t>::min(), bound_cost.bound);
962 solver->SetCoefficient(violation, lp_cumuls.back(), 1.0);
963 solver->SetCoefficient(violation, lp_cumuls.front(), -1.0);
964 solver->SetCoefficient(violation, span_violation, -1.0);
965 // Add span_violation * cost to objective.
966 solver->SetObjectiveCoefficient(span_violation, bound_cost.cost);
967 }
968 }
969 // Add global span constraint.
970 if (optimize_costs && dimension_->global_span_cost_coefficient() > 0) {
971 // min_start_cumul_ <= cumuls[start]
972 int ct =
973 solver->CreateNewConstraint(std::numeric_limits<int64_t>::min(), 0);
974 solver->SetCoefficient(ct, min_start_cumul_, 1);
975 solver->SetCoefficient(ct, lp_cumuls.front(), -1);
976 // max_end_cumul_ >= cumuls[end]
977 ct = solver->CreateNewConstraint(0, std::numeric_limits<int64_t>::max());
978 solver->SetCoefficient(ct, max_end_cumul_, 1);
979 solver->SetCoefficient(ct, lp_cumuls.back(), -1);
980 }
981 // Fill transit cost if specified.
982 if (route_transit_cost != nullptr) {
983 if (optimize_costs && span_cost_coef > 0) {
984 const int64_t total_fixed_transit = std::accumulate(
985 fixed_transit.begin(), fixed_transit.end(), 0, CapAdd);
986 *route_transit_cost = CapProd(total_fixed_transit, span_cost_coef);
987 } else {
988 *route_transit_cost = 0;
989 }
990 }
991 // For every break that must be inside the route, the duration of that break
992 // must be flowed in the slacks of arcs that can intersect the break.
993 // This LP modelization is correct but not complete:
994 // can miss some cases where the breaks cannot fit.
995 // TODO(user): remove the need for returns in the code below.
996 current_route_break_variables_.clear();
997 if (!dimension_->HasBreakConstraints()) return true;
998 const std::vector<IntervalVar*>& breaks =
999 dimension_->GetBreakIntervalsOfVehicle(vehicle);
1000 const int num_breaks = breaks.size();
1001 // When there are no breaks, only break distance needs to be modeled,
1002 // and it reduces to a span maximum.
1003 // TODO(user): Also add the case where no breaks can intersect the route.
1004 if (num_breaks == 0) {
1005 int64_t maximum_route_span = std::numeric_limits<int64_t>::max();
1006 for (const auto& distance_duration :
1007 dimension_->GetBreakDistanceDurationOfVehicle(vehicle)) {
1008 maximum_route_span =
1009 std::min(maximum_route_span, distance_duration.first);
1010 }
1011 if (maximum_route_span < std::numeric_limits<int64_t>::max()) {
1012 const int ct = solver->CreateNewConstraint(
1013 std::numeric_limits<int64_t>::min(), maximum_route_span);
1014 solver->SetCoefficient(ct, lp_cumuls.back(), 1);
1015 solver->SetCoefficient(ct, lp_cumuls.front(), -1);
1016 }
1017 return true;
1018 }
1019 // Gather visit information: the visit of node i has [start, end) =
1020 // [cumul[i] - post_travel[i-1], cumul[i] + pre_travel[i]).
1021 // Breaks cannot overlap those visit intervals.
1022 std::vector<int64_t> pre_travel(path_size - 1, 0);
1023 std::vector<int64_t> post_travel(path_size - 1, 0);
1024 {
1025 const int pre_travel_index =
1026 dimension_->GetPreTravelEvaluatorOfVehicle(vehicle);
1027 if (pre_travel_index != -1) {
1028 FillPathEvaluation(path, model->TransitCallback(pre_travel_index),
1029 &pre_travel);
1030 }
1031 const int post_travel_index =
1032 dimension_->GetPostTravelEvaluatorOfVehicle(vehicle);
1033 if (post_travel_index != -1) {
1034 FillPathEvaluation(path, model->TransitCallback(post_travel_index),
1035 &post_travel);
1036 }
1037 }
1038 // If the solver is CPSAT, it will need to represent the times at which
1039 // breaks are scheduled, those variables are used both in the pure breaks
1040 // part and in the break distance part of the model.
1041 // Otherwise, it doesn't need the variables and they are not created.
1042 std::vector<int> lp_break_start;
1043 std::vector<int> lp_break_duration;
1044 std::vector<int> lp_break_end;
1045 if (solver->IsCPSATSolver()) {
1046 lp_break_start.resize(num_breaks, -1);
1047 lp_break_duration.resize(num_breaks, -1);
1048 lp_break_end.resize(num_breaks, -1);
1049 }
1050
1051 std::vector<int> slack_exact_lower_bound_ct(path_size - 1, -1);
1052 std::vector<int> slack_linear_lower_bound_ct(path_size - 1, -1);
1053
1054 const int64_t vehicle_start_min = current_route_min_cumuls_.front();
1055 const int64_t vehicle_start_max = current_route_max_cumuls_.front();
1056 const int64_t vehicle_end_min = current_route_min_cumuls_.back();
1057 const int64_t vehicle_end_max = current_route_max_cumuls_.back();
1058 const int all_break_variables_offset =
1059 vehicle_to_all_break_variables_offset_[vehicle];
1060 for (int br = 0; br < num_breaks; ++br) {
1061 const IntervalVar& break_var = *breaks[br];
1062 if (!break_var.MustBePerformed()) continue;
1063 const int64_t break_start_min = CapSub(break_var.StartMin(), cumul_offset);
1064 const int64_t break_start_max = CapSub(break_var.StartMax(), cumul_offset);
1065 const int64_t break_end_min = CapSub(break_var.EndMin(), cumul_offset);
1066 const int64_t break_end_max = CapSub(break_var.EndMax(), cumul_offset);
1067 const int64_t break_duration_min = break_var.DurationMin();
1068 const int64_t break_duration_max = break_var.DurationMax();
1069 // The CPSAT solver encodes all breaks that can intersect the route,
1070 // the LP solver only encodes the breaks that must intersect the route.
1071 if (solver->IsCPSATSolver()) {
1072 if (break_end_max <= vehicle_start_min ||
1073 vehicle_end_max <= break_start_min) {
1074 all_break_variables_[all_break_variables_offset + 2 * br] = -1;
1075 all_break_variables_[all_break_variables_offset + 2 * br + 1] = -1;
1076 current_route_break_variables_.push_back(-1);
1077 current_route_break_variables_.push_back(-1);
1078 continue;
1079 }
1080 lp_break_start[br] =
1081 solver->AddVariable(break_start_min, break_start_max);
1082 lp_break_end[br] = solver->AddVariable(break_end_min, break_end_max);
1083 lp_break_duration[br] =
1084 solver->AddVariable(break_duration_min, break_duration_max);
1085 // start + duration = end.
1086 solver->AddLinearConstraint(0, 0,
1087 {{lp_break_end[br], 1},
1088 {lp_break_start[br], -1},
1089 {lp_break_duration[br], -1}});
1090 // Record index of variables
1091 all_break_variables_[all_break_variables_offset + 2 * br] =
1092 lp_break_start[br];
1093 all_break_variables_[all_break_variables_offset + 2 * br + 1] =
1094 lp_break_end[br];
1095 current_route_break_variables_.push_back(lp_break_start[br]);
1096 current_route_break_variables_.push_back(lp_break_end[br]);
1097 } else {
1098 if (break_end_min <= vehicle_start_max ||
1099 vehicle_end_min <= break_start_max) {
1100 continue;
1101 }
1102 }
1103
1104 // Create a constraint for every break, that forces it to be scheduled
1105 // in exactly one place, i.e. one slack or before/after the route.
1106 // sum_i break_in_slack_i == 1.
1107 const int break_in_one_slack_ct = solver->CreateNewConstraint(1, 1);
1108
1109 if (solver->IsCPSATSolver()) {
1110 // Break can be before route.
1111 if (break_end_min <= vehicle_start_max) {
1112 const int ct = solver->AddLinearConstraint(
1114 {{lp_cumuls.front(), 1}, {lp_break_end[br], -1}});
1115 const int break_is_before_route = solver->AddVariable(0, 1);
1116 solver->SetEnforcementLiteral(ct, break_is_before_route);
1117 solver->SetCoefficient(break_in_one_slack_ct, break_is_before_route, 1);
1118 }
1119 // Break can be after route.
1120 if (vehicle_end_min <= break_start_max) {
1121 const int ct = solver->AddLinearConstraint(
1123 {{lp_break_start[br], 1}, {lp_cumuls.back(), -1}});
1124 const int break_is_after_route = solver->AddVariable(0, 1);
1125 solver->SetEnforcementLiteral(ct, break_is_after_route);
1126 solver->SetCoefficient(break_in_one_slack_ct, break_is_after_route, 1);
1127 }
1128 }
1129
1130 // Add the possibility of fitting the break during each slack where it can.
1131 for (int pos = 0; pos < path_size - 1; ++pos) {
1132 // Pass on slacks that cannot start before, cannot end after,
1133 // or are not long enough to contain the break.
1134 const int64_t slack_start_min =
1135 CapAdd(current_route_min_cumuls_[pos], pre_travel[pos]);
1136 if (slack_start_min > break_start_max) break;
1137 const int64_t slack_end_max =
1138 CapSub(current_route_max_cumuls_[pos + 1], post_travel[pos]);
1139 if (break_end_min > slack_end_max) continue;
1140 const int64_t slack_duration_max =
1141 std::min(CapSub(CapSub(current_route_max_cumuls_[pos + 1],
1142 current_route_min_cumuls_[pos]),
1143 fixed_transit[pos]),
1144 dimension_->SlackVar(path[pos])->Max());
1145 if (slack_duration_max < break_duration_min) continue;
1146
1147 // Break can fit into slack: make LP variable, add to break and slack
1148 // constraints.
1149 // Make a linearized slack lower bound (lazily), that represents
1150 // sum_br break_duration_min(br) * break_in_slack(br, pos) <=
1151 // lp_slacks(pos).
1152 const int break_in_slack = solver->AddVariable(0, 1);
1153 solver->SetCoefficient(break_in_one_slack_ct, break_in_slack, 1);
1154 if (slack_linear_lower_bound_ct[pos] == -1) {
1155 slack_linear_lower_bound_ct[pos] = solver->AddLinearConstraint(
1156 std::numeric_limits<int64_t>::min(), 0, {{lp_slacks[pos], -1}});
1157 }
1158 solver->SetCoefficient(slack_linear_lower_bound_ct[pos], break_in_slack,
1159 break_duration_min);
1160 if (solver->IsCPSATSolver()) {
1161 // Exact relation between breaks, slacks and cumul variables.
1162 // Make an exact slack lower bound (lazily), that represents
1163 // sum_br break_duration(br) * break_in_slack(br, pos) <=
1164 // lp_slacks(pos).
1165 const int break_duration_in_slack =
1166 solver->AddVariable(0, slack_duration_max);
1167 solver->AddProductConstraint(break_duration_in_slack,
1168 {break_in_slack, lp_break_duration[br]});
1169 if (slack_exact_lower_bound_ct[pos] == -1) {
1170 slack_exact_lower_bound_ct[pos] = solver->AddLinearConstraint(
1171 std::numeric_limits<int64_t>::min(), 0, {{lp_slacks[pos], -1}});
1172 }
1173 solver->SetCoefficient(slack_exact_lower_bound_ct[pos],
1174 break_duration_in_slack, 1);
1175 // If break_in_slack_i == 1, then
1176 // 1) break_start >= cumul[pos] + pre_travel[pos]
1177 const int break_start_after_current_ct = solver->AddLinearConstraint(
1178 pre_travel[pos], std::numeric_limits<int64_t>::max(),
1179 {{lp_break_start[br], 1}, {lp_cumuls[pos], -1}});
1180 solver->SetEnforcementLiteral(break_start_after_current_ct,
1181 break_in_slack);
1182 // 2) break_end <= cumul[pos+1] - post_travel[pos]
1183 const int break_ends_before_next_ct = solver->AddLinearConstraint(
1184 post_travel[pos], std::numeric_limits<int64_t>::max(),
1185 {{lp_cumuls[pos + 1], 1}, {lp_break_end[br], -1}});
1186 solver->SetEnforcementLiteral(break_ends_before_next_ct,
1187 break_in_slack);
1188 }
1189 }
1190 }
1191
1192 if (!solver->IsCPSATSolver()) return true;
1193 if (!dimension_->GetBreakDistanceDurationOfVehicle(vehicle).empty()) {
1194 // If there is an optional interval, the following model would be wrong.
1195 // TODO(user): support optional intervals.
1196 for (const IntervalVar* interval :
1197 dimension_->GetBreakIntervalsOfVehicle(vehicle)) {
1198 if (!interval->MustBePerformed()) return true;
1199 }
1200 // When this feature is used, breaks are in sorted order.
1201 for (int br = 1; br < num_breaks; ++br) {
1202 solver->AddLinearConstraint(
1204 {{lp_break_end[br - 1], -1}, {lp_break_start[br], 1}});
1205 }
1206 }
1207 for (const auto& distance_duration :
1208 dimension_->GetBreakDistanceDurationOfVehicle(vehicle)) {
1209 const int64_t limit = distance_duration.first;
1210 const int64_t min_break_duration = distance_duration.second;
1211 // Interbreak limit constraint: breaks are interpreted as being in sorted
1212 // order, and the maximum duration between two consecutive
1213 // breaks of duration more than 'min_break_duration' is 'limit'. This
1214 // considers the time until start of route and after end of route to be
1215 // infinite breaks.
1216 // The model for this constraint adds some 'cover_i' variables, such that
1217 // the breaks up to i and the start of route allows to go without a break.
1218 // With s_i the start of break i and e_i its end:
1219 // - the route start covers time from start to start + limit:
1220 // cover_0 = route_start + limit
1221 // - the coverage up to a given break is the largest of the coverage of the
1222 // previous break and if the break is long enough, break end + limit:
1223 // cover_{i+1} = max(cover_i,
1224 // e_i - s_i >= min_break_duration ? e_i + limit : -inf)
1225 // - the coverage of the last break must be at least the route end,
1226 // to ensure the time point route_end-1 is covered:
1227 // cover_{num_breaks} >= route_end
1228 // - similarly, time point s_i-1 must be covered by breaks up to i-1,
1229 // but only if the cover has not reached the route end.
1230 // For instance, a vehicle could have a choice between two days,
1231 // with a potential break on day 1 and a potential break on day 2,
1232 // but the break of day 1 does not have to cover that of day 2!
1233 // cover_{i-1} < route_end => s_i <= cover_{i-1}
1234 // This is sufficient to ensure that the union of the intervals
1235 // (-infinity, route_start], [route_end, +infinity) and all
1236 // [s_i, e_i+limit) where e_i - s_i >= min_break_duration is
1237 // the whole timeline (-infinity, +infinity).
1238 int previous_cover = solver->AddVariable(CapAdd(vehicle_start_min, limit),
1239 CapAdd(vehicle_start_max, limit));
1240 solver->AddLinearConstraint(limit, limit,
1241 {{previous_cover, 1}, {lp_cumuls.front(), -1}});
1242 for (int br = 0; br < num_breaks; ++br) {
1243 if (lp_break_start[br] == -1) continue;
1244 const int64_t break_end_min = CapSub(breaks[br]->EndMin(), cumul_offset);
1245 const int64_t break_end_max = CapSub(breaks[br]->EndMax(), cumul_offset);
1246 // break_is_eligible <=>
1247 // break_end - break_start >= break_minimum_duration.
1248 const int break_is_eligible = solver->AddVariable(0, 1);
1249 const int break_is_not_eligible = solver->AddVariable(0, 1);
1250 {
1251 solver->AddLinearConstraint(
1252 1, 1, {{break_is_eligible, 1}, {break_is_not_eligible, 1}});
1253 const int positive_ct = solver->AddLinearConstraint(
1254 min_break_duration, std::numeric_limits<int64_t>::max(),
1255 {{lp_break_end[br], 1}, {lp_break_start[br], -1}});
1256 solver->SetEnforcementLiteral(positive_ct, break_is_eligible);
1257 const int negative_ct = solver->AddLinearConstraint(
1258 std::numeric_limits<int64_t>::min(), min_break_duration - 1,
1259 {{lp_break_end[br], 1}, {lp_break_start[br], -1}});
1260 solver->SetEnforcementLiteral(negative_ct, break_is_not_eligible);
1261 }
1262 // break_is_eligible => break_cover == break_end + limit.
1263 // break_is_not_eligible => break_cover == vehicle_start_min + limit.
1264 // break_cover's initial domain is the smallest interval that contains the
1265 // union of sets {vehicle_start_min+limit} and
1266 // [break_end_min+limit, break_end_max+limit).
1267 const int break_cover = solver->AddVariable(
1268 CapAdd(std::min(vehicle_start_min, break_end_min), limit),
1269 CapAdd(std::max(vehicle_start_min, break_end_max), limit));
1270 const int limit_cover_ct = solver->AddLinearConstraint(
1271 limit, limit, {{break_cover, 1}, {lp_break_end[br], -1}});
1272 solver->SetEnforcementLiteral(limit_cover_ct, break_is_eligible);
1273 const int empty_cover_ct = solver->AddLinearConstraint(
1274 CapAdd(vehicle_start_min, limit), CapAdd(vehicle_start_min, limit),
1275 {{break_cover, 1}});
1276 solver->SetEnforcementLiteral(empty_cover_ct, break_is_not_eligible);
1277
1278 const int cover =
1279 solver->AddVariable(CapAdd(vehicle_start_min, limit),
1281 solver->AddMaximumConstraint(cover, {previous_cover, break_cover});
1282 // Cover chaining. If route end is not covered, break start must be:
1283 // cover_{i-1} < route_end => s_i <= cover_{i-1}
1284 const int route_end_is_not_covered = solver->AddReifiedLinearConstraint(
1286 {{lp_cumuls.back(), 1}, {previous_cover, -1}});
1287 const int break_start_cover_ct = solver->AddLinearConstraint(
1289 {{previous_cover, 1}, {lp_break_start[br], -1}});
1290 solver->SetEnforcementLiteral(break_start_cover_ct,
1291 route_end_is_not_covered);
1292
1293 previous_cover = cover;
1294 }
1295 solver->AddLinearConstraint(0, std::numeric_limits<int64_t>::max(),
1296 {{previous_cover, 1}, {lp_cumuls.back(), -1}});
1297 }
1298
1299 return true;
1300}
1301
1302void DimensionCumulOptimizerCore::SetGlobalConstraints(
1303 const std::function<int64_t(int64_t)>& next_accessor, int64_t cumul_offset,
1304 bool optimize_costs, RoutingLinearSolverWrapper* solver) {
1305 // Global span cost =
1306 // global_span_cost_coefficient * (max_end_cumul - min_start_cumul).
1307 const int64_t global_span_coeff = dimension_->global_span_cost_coefficient();
1308 if (optimize_costs && global_span_coeff > 0) {
1309 solver->SetObjectiveCoefficient(max_end_cumul_, global_span_coeff);
1310 solver->SetObjectiveCoefficient(min_start_cumul_, -global_span_coeff);
1311 }
1312
1313 // Node precedence constraints, set when both nodes are visited.
1314 for (const RoutingDimension::NodePrecedence& precedence :
1315 dimension_->GetNodePrecedences()) {
1316 const int first_cumul_var = index_to_cumul_variable_[precedence.first_node];
1317 const int second_cumul_var =
1318 index_to_cumul_variable_[precedence.second_node];
1319 if (first_cumul_var < 0 || second_cumul_var < 0) {
1320 // At least one of the nodes is not on any route, skip this precedence
1321 // constraint.
1322 continue;
1323 }
1324 DCHECK_NE(first_cumul_var, second_cumul_var)
1325 << "Dimension " << dimension_->name()
1326 << " has a self-precedence on node " << precedence.first_node << ".";
1327
1328 // cumul[second_node] - cumul[first_node] >= offset.
1329 const int ct = solver->CreateNewConstraint(
1330 precedence.offset, std::numeric_limits<int64_t>::max());
1331 solver->SetCoefficient(ct, second_cumul_var, 1);
1332 solver->SetCoefficient(ct, first_cumul_var, -1);
1333 }
1334
1335 const RoutingModel& model = *dimension_->model();
1336 if (!solver->IsCPSATSolver()) {
1337 // The resource attributes conditional constraints can only be added with
1338 // the CP-SAT MIP solver.
1339 return;
1340 }
1341
1342 using ResourceGroup = RoutingModel::ResourceGroup;
1343 const auto& resource_groups = model.GetResourceGroups();
1344 for (int rg_index : model.GetDimensionResourceGroupIndices(dimension_)) {
1345 // Resource domain constraints:
1346 // Every (used) vehicle must be assigned to exactly one resource in this
1347 // group, and each resource must be assigned to at most 1 vehicle.
1348 // For every resource r with Attributes A = resources[r].attributes(dim)
1349 // and every vehicle v, assign(r, v) == 1 -->
1350 // A.start_domain.Min() <= cumul[Start(v)] <= A.start_domain.Max(),
1351 // and
1352 // A.end_domain.Min() <= cumul[End(v)] <= A.end_domain.Max().
1353 const std::vector<ResourceGroup::Resource>& resources =
1354 resource_groups[rg_index]->GetResources();
1355
1356 static const int kNoConstraint = -1;
1357 // Assignment constraints for vehicles: each (used) vehicle must have
1358 // exactly one resource assigned to it.
1359 std::vector<int> vehicle_constraints(model.vehicles(), kNoConstraint);
1360 for (int v = 0; v < model.vehicles(); v++) {
1361 if (model.IsEnd(next_accessor(model.Start(v))) &&
1362 !model.AreEmptyRouteCostsConsideredForVehicle(v)) {
1363 // We don't assign a driver to unused vehicles.
1364 continue;
1365 }
1366 vehicle_constraints[v] = solver->CreateNewConstraint(1, 1);
1367 }
1368 // Assignment constraints for resources: each resource must be assigned to
1369 // at most one (used) vehicle.
1370 std::vector<int> resource_constraints(resources.size(), kNoConstraint);
1371 for (int r = 0; r < resources.size(); r++) {
1372 const ResourceGroup::Attributes& attributes =
1373 resources[r].GetDimensionAttributes(dimension_);
1374 if (attributes.start_domain().Max() < cumul_offset ||
1375 attributes.end_domain().Max() < cumul_offset) {
1376 // This resource's domain has a cumul max lower than the offset, so it's
1377 // not possible to restrict any vehicle start/end to this domain; skip
1378 // it.
1379 continue;
1380 }
1381 resource_constraints[r] = solver->CreateNewConstraint(0, 1);
1382 }
1383 // Create assignment variables, add them to the corresponding constraints,
1384 // and create the reified constraints assign(r, v) == 1 -->
1385 // A(r).start_domain.Min() <= cumul[Start(v)] <= A(r).start_domain.Max(),
1386 // and
1387 // A(r).end_domain.Min() <= cumul[End(v)] <= A(r).end_domain.Max().
1388 for (int r = 0; r < resources.size(); r++) {
1389 if (resource_constraints[r] == kNoConstraint) continue;
1390 const ResourceGroup::Attributes& attributes =
1391 resources[r].GetDimensionAttributes(dimension_);
1392 for (int v = 0; v < dimension_->model()->vehicles(); v++) {
1393 if (vehicle_constraints[v] == kNoConstraint) continue;
1394
1395 const int assign_r_to_v = solver->AddVariable(0, 1);
1396 solver->SetCoefficient(vehicle_constraints[v], assign_r_to_v, 1);
1397 solver->SetCoefficient(resource_constraints[r], assign_r_to_v, 1);
1398
1399 const auto& add_domain_constraint =
1400 [&solver, cumul_offset, assign_r_to_v](const Domain& domain,
1401 int cumul_variable) {
1402 if (domain == Domain::AllValues()) {
1403 return;
1404 }
1405 const int64_t cumul_min =
1406 std::max<int64_t>(CapSub(domain.Min(), cumul_offset), 0);
1407 const int64_t cumul_max =
1408 domain.Max() == std::numeric_limits<int64_t>::max()
1410 : CapSub(domain.Max(), cumul_offset);
1411 DCHECK_GE(cumul_max, 0);
1412 const int cumul_constraint = solver->AddLinearConstraint(
1413 cumul_min, cumul_max, {{cumul_variable, 1}});
1414 solver->SetEnforcementLiteral(cumul_constraint, assign_r_to_v);
1415 };
1416 add_domain_constraint(attributes.start_domain(),
1417 index_to_cumul_variable_[model.Start(v)]);
1418 add_domain_constraint(attributes.end_domain(),
1419 index_to_cumul_variable_[model.End(v)]);
1420 }
1421 }
1422 }
1423}
1424
1425void DimensionCumulOptimizerCore::SetValuesFromLP(
1426 const std::vector<int>& lp_variables, int64_t offset,
1427 RoutingLinearSolverWrapper* solver, std::vector<int64_t>* lp_values) {
1428 if (lp_values == nullptr) return;
1429 lp_values->assign(lp_variables.size(), std::numeric_limits<int64_t>::min());
1430 for (int i = 0; i < lp_variables.size(); i++) {
1431 const int cumul_var = lp_variables[i];
1432 if (cumul_var < 0) continue; // Keep default value, kint64min.
1433 const double lp_value_double = solver->GetValue(cumul_var);
1434 const int64_t lp_value_int64 =
1435 (lp_value_double >= std::numeric_limits<int64_t>::max())
1437 : MathUtil::FastInt64Round(lp_value_double);
1438 (*lp_values)[i] = CapAdd(lp_value_int64, offset);
1439 }
1440}
1441
1443 const RoutingDimension* dimension,
1445 : optimizer_core_(dimension,
1446 /*use_precedence_propagator=*/
1447 !dimension->GetNodePrecedences().empty()) {
1448 switch (solver_type) {
1450 solver_ = absl::make_unique<RoutingGlopWrapper>(
1451 /*is_relaxation=*/!dimension->model()
1453 .empty(),
1454 GetGlopParametersForGlobalLP());
1455 break;
1456 }
1458 solver_ = absl::make_unique<RoutingCPSatWrapper>();
1459 break;
1460 }
1461 default:
1462 LOG(DFATAL) << "Unrecognized solver type: " << solver_type;
1463 }
1464}
1465
1468 const std::function<int64_t(int64_t)>& next_accessor,
1469 int64_t* optimal_cost_without_transits) {
1470 int64_t cost = 0;
1471 int64_t transit_cost = 0;
1472 DimensionSchedulingStatus status = optimizer_core_.Optimize(
1473 next_accessor, solver_.get(), nullptr, nullptr, &cost, &transit_cost);
1475 optimal_cost_without_transits != nullptr) {
1476 *optimal_cost_without_transits = CapSub(cost, transit_cost);
1477 }
1478 return status;
1479}
1480
1482 const std::function<int64_t(int64_t)>& next_accessor,
1483 std::vector<int64_t>* optimal_cumuls,
1484 std::vector<int64_t>* optimal_breaks) {
1485 return optimizer_core_.Optimize(next_accessor, solver_.get(), optimal_cumuls,
1486 optimal_breaks, nullptr, nullptr);
1487}
1488
1490 const std::function<int64_t(int64_t)>& next_accessor,
1491 std::vector<int64_t>* packed_cumuls, std::vector<int64_t>* packed_breaks) {
1492 return optimizer_core_.OptimizeAndPack(next_accessor, solver_.get(),
1493 packed_cumuls, packed_breaks);
1494}
1495} // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define DCHECK_NE(val1, val2)
Definition: base/logging.h:887
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:890
#define DCHECK_GT(val1, val2)
Definition: base/logging.h:891
#define DCHECK_LT(val1, val2)
Definition: base/logging.h:889
#define LOG(severity)
Definition: base/logging.h:416
#define DCHECK(condition)
Definition: base/logging.h:885
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:886
const RoutingDimension & dimension() const
bool PropagateCumulBounds(const std::function< int64_t(int64_t)> &next_accessor, int64_t cumul_offset)
CumulBoundsPropagator(const RoutingDimension *dimension)
DimensionCumulOptimizerCore(const RoutingDimension *dimension, bool use_precedence_propagator)
DimensionSchedulingStatus OptimizeAndPackSingleRoute(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, RoutingLinearSolverWrapper *solver, std::vector< int64_t > *cumul_values, std::vector< int64_t > *break_values)
DimensionSchedulingStatus OptimizeAndPack(const std::function< int64_t(int64_t)> &next_accessor, RoutingLinearSolverWrapper *solver, std::vector< int64_t > *cumul_values, std::vector< int64_t > *break_values)
DimensionSchedulingStatus Optimize(const std::function< int64_t(int64_t)> &next_accessor, RoutingLinearSolverWrapper *solver, std::vector< int64_t > *cumul_values, std::vector< int64_t > *break_values, int64_t *cost, int64_t *transit_cost, bool clear_lp=true)
DimensionSchedulingStatus OptimizeSingleRoute(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, RoutingLinearSolverWrapper *solver, std::vector< int64_t > *cumul_values, std::vector< int64_t > *break_values, int64_t *cost, int64_t *transit_cost, bool clear_lp=true)
static Domain AllValues()
Returns the full domain Int64.
GlobalDimensionCumulOptimizer(const RoutingDimension *dimension, RoutingSearchParameters::SchedulingSolver solver_type)
DimensionSchedulingStatus ComputeCumulCostWithoutFixedTransits(const std::function< int64_t(int64_t)> &next_accessor, int64_t *optimal_cost_without_transits)
DimensionSchedulingStatus ComputeCumuls(const std::function< int64_t(int64_t)> &next_accessor, std::vector< int64_t > *optimal_cumuls, std::vector< int64_t > *optimal_breaks)
DimensionSchedulingStatus ComputePackedCumuls(const std::function< int64_t(int64_t)> &next_accessor, std::vector< int64_t > *packed_cumuls, std::vector< int64_t > *packed_breaks)
virtual int64_t Min() const =0
virtual int64_t Max() const =0
DimensionSchedulingStatus ComputeRouteCumulCost(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, int64_t *optimal_cost)
DimensionSchedulingStatus ComputeRouteCumuls(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, std::vector< int64_t > *optimal_cumuls, std::vector< int64_t > *optimal_breaks)
DimensionSchedulingStatus ComputeRouteCumulCostWithoutFixedTransits(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, int64_t *optimal_cost_without_transits)
LocalDimensionCumulOptimizer(const RoutingDimension *dimension, RoutingSearchParameters::SchedulingSolver solver_type)
DimensionSchedulingStatus ComputePackedRouteCumuls(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, std::vector< int64_t > *packed_cumuls, std::vector< int64_t > *packed_breaks)
static int64_t FastInt64Round(double x)
Definition: mathutil.h:138
Dimensions represent quantities accumulated at nodes along the routes.
Definition: routing.h:2494
SimpleBoundCosts::BoundCost GetSoftSpanUpperBoundForVehicle(int vehicle) const
Definition: routing.h:2858
const std::string & name() const
Returns the name of the dimension.
Definition: routing.h:2758
const std::vector< IntVar * > & cumuls() const
Like CumulVar(), TransitVar(), SlackVar() but return the whole variable vectors instead (indexed by i...
Definition: routing.h:2523
int64_t GetCumulVarSoftUpperBoundCoefficient(int64_t index) const
Returns the cost coefficient of the soft upper bound of a cumul variable for a given variable index.
Definition: routing.cc:6292
int64_t GetGlobalOptimizerOffset() const
Definition: routing.h:2833
int64_t GetPickupToDeliveryLimitForPair(int pair_index, int pickup, int delivery) const
Definition: routing.cc:6553
bool HasCumulVarSoftLowerBound(int64_t index) const
Returns true if a soft lower bound has been set for a given variable index.
Definition: routing.cc:6331
IntVar * SlackVar(int64_t index) const
Definition: routing.h:2518
int64_t GetSpanCostCoefficientForVehicle(int vehicle) const
Definition: routing.h:2813
int64_t global_span_cost_coefficient() const
Definition: routing.h:2829
const RoutingModel::TransitCallback2 & transit_evaluator(int vehicle) const
Returns the callback evaluating the transit value between two node indices for a given vehicle.
Definition: routing.h:2577
int64_t GetSpanUpperBoundForVehicle(int vehicle) const
Definition: routing.h:2805
bool HasBreakConstraints() const
Returns true if any break interval or break distance was defined.
Definition: routing.cc:6494
SortedDisjointIntervalList GetAllowedIntervalsInRange(int64_t index, int64_t min_value, int64_t max_value) const
Returns allowed intervals for a given node in a given interval.
Definition: routing.cc:6153
int GetPreTravelEvaluatorOfVehicle(int vehicle) const
!defined(SWIGPYTHON)
Definition: routing.cc:6505
bool HasCumulVarSoftUpperBound(int64_t index) const
Returns true if a soft upper bound has been set for a given variable index.
Definition: routing.cc:6279
RoutingModel * model() const
Returns the model on which the dimension was created.
Definition: routing.h:2498
const std::vector< IntervalVar * > & GetBreakIntervalsOfVehicle(int vehicle) const
Returns the break intervals set by SetBreakIntervalsOfVehicle().
Definition: routing.cc:6498
const std::vector< NodePrecedence > & GetNodePrecedences() const
Definition: routing.h:2795
const std::vector< SortedDisjointIntervalList > & forbidden_intervals() const
Returns forbidden intervals for each node.
Definition: routing.h:2529
int64_t GetLocalOptimizerOffsetForVehicle(int vehicle) const
Definition: routing.h:2837
int64_t GetCumulVarSoftUpperBound(int64_t index) const
Returns the soft upper bound of a cumul variable for a given variable index.
Definition: routing.cc:6284
const std::vector< std::pair< int64_t, int64_t > > & GetBreakDistanceDurationOfVehicle(int vehicle) const
Returns the pairs (distance, duration) specified by break distance constraints.
Definition: routing.cc:6533
int64_t GetCumulVarSoftLowerBoundCoefficient(int64_t index) const
Returns the cost coefficient of the soft lower bound of a cumul variable for a given variable index.
Definition: routing.cc:6344
int GetPostTravelEvaluatorOfVehicle(int vehicle) const
Definition: routing.cc:6511
int64_t GetCumulVarSoftLowerBound(int64_t index) const
Returns the soft lower bound of a cumul variable for a given variable index.
Definition: routing.cc:6336
virtual int64_t GetObjectiveValue() const =0
virtual double GetValue(int index) const =0
virtual DimensionSchedulingStatus Solve(absl::Duration duration_limit)=0
virtual bool SetVariableBounds(int index, int64_t lower_bound, int64_t upper_bound)=0
virtual void SetObjectiveCoefficient(int index, double coefficient)=0
virtual int64_t GetVariableLowerBound(int index) const =0
const std::vector< int > & GetDimensionResourceGroupIndices(const RoutingDimension *dimension) const
Returns the indices of resource groups for this dimension.
Definition: routing.cc:1328
const IndexPairs & GetPickupAndDeliveryPairs() const
Returns pickup and delivery pairs currently in the model.
Definition: routing.h:834
int vehicles() const
Returns the number of vehicle routes in the model.
Definition: routing.h:1458
int64_t b
Block * next
SatParameters parameters
const Constraint * ct
int64_t coef
Definition: expr_array.cc:1875
double upper_bound
double lower_bound
GRBmodel * model
Collection of objects used to extend the Constraint Solver library.
int64_t CapAdd(int64_t x, int64_t y)
int64_t CapSub(int64_t x, int64_t y)
int64_t CapProd(int64_t x, int64_t y)
void FillPathEvaluation(const std::vector< int64_t > &path, const RoutingModel::TransitCallback2 &evaluator, std::vector< int64_t > *values)
Definition: routing.cc:5810
IntervalVar * interval
Definition: resource.cc:100
int64_t bound
int64_t tail
int64_t cost
std::vector< double > lower_bounds