OR-Tools  9.0
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"
27 #include "ortools/base/logging.h"
28 #include "ortools/base/mathutil.h"
35 
36 namespace operations_research {
37 
38 namespace {
39 
40 // The following sets of parameters give the fastest response time without
41 // impacting solutions found negatively.
42 glop::GlopParameters GetGlopParametersForLocalLP() {
43  glop::GlopParameters parameters;
44  parameters.set_use_dual_simplex(true);
45  parameters.set_use_preprocessing(false);
46  return parameters;
47 }
48 
49 glop::GlopParameters GetGlopParametersForGlobalLP() {
50  glop::GlopParameters parameters;
51  parameters.set_use_dual_simplex(true);
52  return parameters;
53 }
54 
55 bool 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 
83 int64_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 
92 int64_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.
106 void 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,
164  RoutingSearchParameters::SchedulingSolver solver_type)
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) {
171  case RoutingSearchParameters::GLOP: {
172  const glop::GlopParameters parameters = GetGlopParametersForLocalLP();
173  for (int vehicle = 0; vehicle < vehicles; ++vehicle) {
174  solver_[vehicle] = absl::make_unique<RoutingGlopWrapper>(parameters);
175  }
176  break;
177  }
178  case RoutingSearchParameters::CP_SAT: {
179  for (int vehicle = 0; vehicle < vehicles; ++vehicle) {
180  solver_[vehicle] = absl::make_unique<RoutingCPSatWrapper>();
181  }
182  break;
183  }
184  default:
185  LOG(DFATAL) << "Unrecognized solver type: " << solver_type;
186  }
187 }
188 
190  int vehicle, const std::function<int64_t(int64_t)>& next_accessor,
191  int64_t* optimal_cost) {
192  return optimizer_core_.OptimizeSingleRoute(vehicle, next_accessor,
193  solver_[vehicle].get(), nullptr,
194  nullptr, optimal_cost, nullptr);
195 }
196 
199  int vehicle, const std::function<int64_t(int64_t)>& next_accessor,
200  int64_t* optimal_cost_without_transits) {
201  int64_t cost = 0;
202  int64_t transit_cost = 0;
203  const DimensionSchedulingStatus status = optimizer_core_.OptimizeSingleRoute(
204  vehicle, next_accessor, solver_[vehicle].get(), nullptr, nullptr, &cost,
205  &transit_cost);
207  optimal_cost_without_transits != nullptr) {
208  *optimal_cost_without_transits = CapSub(cost, transit_cost);
209  }
210  return status;
211 }
212 
214  int vehicle, const std::function<int64_t(int64_t)>& next_accessor,
215  std::vector<int64_t>* optimal_cumuls,
216  std::vector<int64_t>* optimal_breaks) {
217  return optimizer_core_.OptimizeSingleRoute(
218  vehicle, next_accessor, solver_[vehicle].get(), optimal_cumuls,
219  optimal_breaks, nullptr, nullptr);
220 }
221 
224  int vehicle, const std::function<int64_t(int64_t)>& next_accessor,
225  std::vector<int64_t>* packed_cumuls, std::vector<int64_t>* packed_breaks) {
226  return optimizer_core_.OptimizeAndPackSingleRoute(
227  vehicle, next_accessor, solver_[vehicle].get(), packed_cumuls,
228  packed_breaks);
229 }
230 
231 const int CumulBoundsPropagator::kNoParent = -2;
232 const int CumulBoundsPropagator::kParentToBePropagated = -1;
233 
235  : dimension_(*dimension), num_nodes_(2 * dimension->cumuls().size()) {
236  outgoing_arcs_.resize(num_nodes_);
237  node_in_queue_.resize(num_nodes_, false);
238  tree_parent_node_of_.resize(num_nodes_, kNoParent);
239  propagated_bounds_.resize(num_nodes_);
240  visited_pickup_delivery_indices_for_pair_.resize(
241  dimension->model()->GetPickupAndDeliveryPairs().size(), {-1, -1});
242 }
243 
244 void CumulBoundsPropagator::AddArcs(int first_index, int second_index,
245  int64_t offset) {
246  // Add arc first_index + offset <= second_index
247  outgoing_arcs_[PositiveNode(first_index)].push_back(
248  {PositiveNode(second_index), offset});
249  AddNodeToQueue(PositiveNode(first_index));
250  // Add arc -second_index + transit <= -first_index
251  outgoing_arcs_[NegativeNode(second_index)].push_back(
252  {NegativeNode(first_index), offset});
253  AddNodeToQueue(NegativeNode(second_index));
254 }
255 
256 bool CumulBoundsPropagator::InitializeArcsAndBounds(
257  const std::function<int64_t(int64_t)>& next_accessor,
258  int64_t cumul_offset) {
259  propagated_bounds_.assign(num_nodes_, std::numeric_limits<int64_t>::min());
260 
261  for (std::vector<ArcInfo>& arcs : outgoing_arcs_) {
262  arcs.clear();
263  }
264 
265  RoutingModel* const model = dimension_.model();
266  std::vector<int64_t>& lower_bounds = propagated_bounds_;
267 
268  for (int vehicle = 0; vehicle < model->vehicles(); vehicle++) {
269  const std::function<int64_t(int64_t, int64_t)>& transit_accessor =
270  dimension_.transit_evaluator(vehicle);
271 
272  int node = model->Start(vehicle);
273  while (true) {
274  int64_t cumul_lb, cumul_ub;
275  if (!GetCumulBoundsWithOffset(dimension_, node, cumul_offset, &cumul_lb,
276  &cumul_ub)) {
277  return false;
278  }
279  lower_bounds[PositiveNode(node)] = cumul_lb;
280  if (cumul_ub < std::numeric_limits<int64_t>::max()) {
281  lower_bounds[NegativeNode(node)] = -cumul_ub;
282  }
283 
284  if (model->IsEnd(node)) {
285  break;
286  }
287 
288  const int next = next_accessor(node);
289  const int64_t transit = transit_accessor(node, next);
290  const IntVar& slack_var = *dimension_.SlackVar(node);
291  // node + transit + slack_var == next
292  // Add arcs for node + transit + slack_min <= next
293  AddArcs(node, next, CapAdd(transit, slack_var.Min()));
294  if (slack_var.Max() < std::numeric_limits<int64_t>::max()) {
295  // Add arcs for node + transit + slack_max >= next.
296  AddArcs(next, node, CapSub(-slack_var.Max(), transit));
297  }
298 
299  node = next;
300  }
301 
302  // Add vehicle span upper bound: end - span_ub <= start.
303  const int64_t span_ub = dimension_.GetSpanUpperBoundForVehicle(vehicle);
304  if (span_ub < std::numeric_limits<int64_t>::max()) {
305  AddArcs(model->End(vehicle), model->Start(vehicle), -span_ub);
306  }
307 
308  // Set pickup/delivery limits on route.
309  std::vector<int> visited_pairs;
310  StoreVisitedPickupDeliveryPairsOnRoute(
311  dimension_, vehicle, next_accessor, &visited_pairs,
312  &visited_pickup_delivery_indices_for_pair_);
313  for (int pair_index : visited_pairs) {
314  const int64_t pickup_index =
315  visited_pickup_delivery_indices_for_pair_[pair_index].first;
316  const int64_t delivery_index =
317  visited_pickup_delivery_indices_for_pair_[pair_index].second;
318  visited_pickup_delivery_indices_for_pair_[pair_index] = {-1, -1};
319 
320  DCHECK_GE(pickup_index, 0);
321  if (delivery_index < 0) {
322  // We didn't encounter a delivery for this pickup.
323  continue;
324  }
325 
326  const int64_t limit = dimension_.GetPickupToDeliveryLimitForPair(
327  pair_index, model->GetPickupIndexPairs(pickup_index)[0].second,
328  model->GetDeliveryIndexPairs(delivery_index)[0].second);
329  if (limit < std::numeric_limits<int64_t>::max()) {
330  // delivery_cumul - limit <= pickup_cumul.
331  AddArcs(delivery_index, pickup_index, -limit);
332  }
333  }
334  }
335 
336  for (const RoutingDimension::NodePrecedence& precedence :
337  dimension_.GetNodePrecedences()) {
338  const int first_index = precedence.first_node;
339  const int second_index = precedence.second_node;
340  if (lower_bounds[PositiveNode(first_index)] ==
342  lower_bounds[PositiveNode(second_index)] ==
344  // One of the nodes is unperformed, so the precedence rule doesn't apply.
345  continue;
346  }
347  AddArcs(first_index, second_index, precedence.offset);
348  }
349 
350  return true;
351 }
352 
353 bool CumulBoundsPropagator::UpdateCurrentLowerBoundOfNode(int node,
354  int64_t new_lb,
355  int64_t offset) {
356  const int cumul_var_index = node / 2;
357 
358  if (node == PositiveNode(cumul_var_index)) {
359  // new_lb is a lower bound of the cumul of variable 'cumul_var_index'.
360  propagated_bounds_[node] = GetFirstPossibleValueForCumulWithOffset(
361  dimension_, cumul_var_index, new_lb, offset);
362  } else {
363  // -new_lb is an upper bound of the cumul of variable 'cumul_var_index'.
364  const int64_t new_ub = CapSub(0, new_lb);
365  propagated_bounds_[node] =
366  CapSub(0, GetLastPossibleValueForCumulWithOffset(
367  dimension_, cumul_var_index, new_ub, offset));
368  }
369 
370  // Test that the lower/upper bounds do not cross each other.
371  const int64_t cumul_lower_bound =
372  propagated_bounds_[PositiveNode(cumul_var_index)];
373 
374  const int64_t negated_cumul_upper_bound =
375  propagated_bounds_[NegativeNode(cumul_var_index)];
376 
377  return CapAdd(negated_cumul_upper_bound, cumul_lower_bound) <= 0;
378 }
379 
380 bool CumulBoundsPropagator::DisassembleSubtree(int source, int target) {
381  tmp_dfs_stack_.clear();
382  tmp_dfs_stack_.push_back(source);
383  while (!tmp_dfs_stack_.empty()) {
384  const int tail = tmp_dfs_stack_.back();
385  tmp_dfs_stack_.pop_back();
386  for (const ArcInfo& arc : outgoing_arcs_[tail]) {
387  const int child_node = arc.head;
388  if (tree_parent_node_of_[child_node] != tail) continue;
389  if (child_node == target) return false;
390  tree_parent_node_of_[child_node] = kParentToBePropagated;
391  tmp_dfs_stack_.push_back(child_node);
392  }
393  }
394  return true;
395 }
396 
398  const std::function<int64_t(int64_t)>& next_accessor,
399  int64_t cumul_offset) {
400  tree_parent_node_of_.assign(num_nodes_, kNoParent);
401  DCHECK(std::none_of(node_in_queue_.begin(), node_in_queue_.end(),
402  [](bool b) { return b; }));
403  DCHECK(bf_queue_.empty());
404 
405  if (!InitializeArcsAndBounds(next_accessor, cumul_offset)) {
406  return CleanupAndReturnFalse();
407  }
408 
409  std::vector<int64_t>& current_lb = propagated_bounds_;
410 
411  // Bellman-Ford-Tarjan algorithm.
412  while (!bf_queue_.empty()) {
413  const int node = bf_queue_.front();
414  bf_queue_.pop_front();
415  node_in_queue_[node] = false;
416 
417  if (tree_parent_node_of_[node] == kParentToBePropagated) {
418  // The parent of this node is still in the queue, so no need to process
419  // node now, since it will be re-enqued when its parent is processed.
420  continue;
421  }
422 
423  const int64_t lower_bound = current_lb[node];
424  for (const ArcInfo& arc : outgoing_arcs_[node]) {
425  // NOTE: kint64min as a lower bound means no lower bound at all, so we
426  // don't use this value to propagate.
427  const int64_t induced_lb =
430  : CapAdd(lower_bound, arc.offset);
431 
432  const int head_node = arc.head;
433  if (induced_lb <= current_lb[head_node]) {
434  // No update necessary for the head_node, continue to next children of
435  // node.
436  continue;
437  }
438  if (!UpdateCurrentLowerBoundOfNode(head_node, induced_lb, cumul_offset) ||
439  !DisassembleSubtree(head_node, node)) {
440  // The new lower bound is infeasible, or a positive cycle was detected
441  // in the precedence graph by DisassembleSubtree().
442  return CleanupAndReturnFalse();
443  }
444 
445  tree_parent_node_of_[head_node] = node;
446  AddNodeToQueue(head_node);
447  }
448  }
449  return true;
450 }
451 
453  const RoutingDimension* dimension, bool use_precedence_propagator)
454  : dimension_(dimension),
455  visited_pickup_delivery_indices_for_pair_(
456  dimension->model()->GetPickupAndDeliveryPairs().size(), {-1, -1}) {
457  if (use_precedence_propagator) {
458  propagator_ = absl::make_unique<CumulBoundsPropagator>(dimension);
459  }
460  if (dimension_->HasBreakConstraints()) {
461  // Initialize vehicle_to_first_index_ so the variables of the breaks of
462  // vehicle v are stored from vehicle_to_first_index_[v] to
463  // vehicle_to_first_index_[v+1] - 1.
464  const int num_vehicles = dimension_->model()->vehicles();
465  vehicle_to_all_break_variables_offset_.reserve(num_vehicles);
466  int num_break_vars = 0;
467  for (int vehicle = 0; vehicle < num_vehicles; ++vehicle) {
468  vehicle_to_all_break_variables_offset_.push_back(num_break_vars);
469  const auto& intervals = dimension_->GetBreakIntervalsOfVehicle(vehicle);
470  num_break_vars += 2 * intervals.size(); // 2 variables per break.
471  }
472  all_break_variables_.resize(num_break_vars, -1);
473  }
474 }
475 
477  int vehicle, const std::function<int64_t(int64_t)>& next_accessor,
478  RoutingLinearSolverWrapper* solver, std::vector<int64_t>* cumul_values,
479  std::vector<int64_t>* break_values, int64_t* cost, int64_t* transit_cost,
480  bool clear_lp) {
481  InitOptimizer(solver);
482  // Make sure SetRouteCumulConstraints will properly set the cumul bounds by
483  // looking at this route only.
484  DCHECK(propagator_ == nullptr);
485 
486  const RoutingModel* const model = dimension()->model();
487  const bool optimize_vehicle_costs =
488  (cumul_values != nullptr || cost != nullptr) &&
489  (!model->IsEnd(next_accessor(model->Start(vehicle))) ||
490  model->AreEmptyRouteCostsConsideredForVehicle(vehicle));
491  const int64_t cumul_offset =
492  dimension_->GetLocalOptimizerOffsetForVehicle(vehicle);
493  int64_t cost_offset = 0;
494  if (!SetRouteCumulConstraints(vehicle, next_accessor, cumul_offset,
495  optimize_vehicle_costs, solver, transit_cost,
496  &cost_offset)) {
498  }
499  const DimensionSchedulingStatus status =
500  solver->Solve(model->RemainingTime());
502  return status;
503  }
504 
505  SetValuesFromLP(current_route_cumul_variables_, cumul_offset, solver,
506  cumul_values);
507  SetValuesFromLP(current_route_break_variables_, cumul_offset, solver,
508  break_values);
509  if (cost != nullptr) {
510  *cost = CapAdd(cost_offset, solver->GetObjectiveValue());
511  }
512 
513  if (clear_lp) {
514  solver->Clear();
515  }
516  return status;
517 }
518 
520  const std::function<int64_t(int64_t)>& next_accessor,
521  RoutingLinearSolverWrapper* solver, std::vector<int64_t>* cumul_values,
522  std::vector<int64_t>* break_values, int64_t* cost, int64_t* transit_cost,
523  bool clear_lp) {
524  InitOptimizer(solver);
525 
526  // If both "cumul_values" and "cost" parameters are null, we don't try to
527  // optimize the cost and stop at the first feasible solution.
528  const bool optimize_costs = (cumul_values != nullptr) || (cost != nullptr);
529  bool has_vehicles_being_optimized = false;
530 
531  const int64_t cumul_offset = dimension_->GetGlobalOptimizerOffset();
532 
533  if (propagator_ != nullptr &&
534  !propagator_->PropagateCumulBounds(next_accessor, cumul_offset)) {
535  return false;
536  }
537 
538  int64_t total_transit_cost = 0;
539  int64_t total_cost_offset = 0;
540  const RoutingModel* model = dimension()->model();
541  for (int vehicle = 0; vehicle < model->vehicles(); vehicle++) {
542  int64_t route_transit_cost = 0;
543  int64_t route_cost_offset = 0;
544  const bool optimize_vehicle_costs =
545  optimize_costs &&
546  (!model->IsEnd(next_accessor(model->Start(vehicle))) ||
547  model->AreEmptyRouteCostsConsideredForVehicle(vehicle));
548  if (!SetRouteCumulConstraints(vehicle, next_accessor, cumul_offset,
549  optimize_vehicle_costs, solver,
550  &route_transit_cost, &route_cost_offset)) {
551  return false;
552  }
553  total_transit_cost = CapAdd(total_transit_cost, route_transit_cost);
554  total_cost_offset = CapAdd(total_cost_offset, route_cost_offset);
555  has_vehicles_being_optimized |= optimize_vehicle_costs;
556  }
557  if (transit_cost != nullptr) {
558  *transit_cost = total_transit_cost;
559  }
560 
561  SetGlobalConstraints(has_vehicles_being_optimized, solver);
562 
563  if (solver->Solve(model->RemainingTime()) ==
565  return false;
566  }
567 
568  SetValuesFromLP(index_to_cumul_variable_, cumul_offset, solver, cumul_values);
569  SetValuesFromLP(all_break_variables_, cumul_offset, solver, break_values);
570 
571  if (cost != nullptr) {
572  *cost = CapAdd(solver->GetObjectiveValue(), total_cost_offset);
573  }
574 
575  if (clear_lp) {
576  solver->Clear();
577  }
578  return true;
579 }
580 
582  const std::function<int64_t(int64_t)>& next_accessor,
583  RoutingLinearSolverWrapper* solver, std::vector<int64_t>* cumul_values,
584  std::vector<int64_t>* break_values) {
585  // Note: We pass a non-nullptr cost to the Optimize() method so the costs are
586  // optimized by the LP.
587  int64_t cost = 0;
588  if (!Optimize(next_accessor, solver,
589  /*cumul_values=*/nullptr, /*break_values=*/nullptr, &cost,
590  /*transit_cost=*/nullptr, /*clear_lp=*/false)) {
591  return false;
592  }
593 
594  std::vector<int> vehicles(dimension()->model()->vehicles());
595  std::iota(vehicles.begin(), vehicles.end(), 0);
596  if (PackRoutes(std::move(vehicles), solver) ==
598  return false;
599  }
600  const int64_t global_offset = dimension_->GetGlobalOptimizerOffset();
601  SetValuesFromLP(index_to_cumul_variable_, global_offset, solver,
602  cumul_values);
603  SetValuesFromLP(all_break_variables_, global_offset, solver, break_values);
604  solver->Clear();
605  return true;
606 }
607 
610  int vehicle, const std::function<int64_t(int64_t)>& next_accessor,
611  RoutingLinearSolverWrapper* solver, std::vector<int64_t>* cumul_values,
612  std::vector<int64_t>* break_values) {
613  // Note: We pass a non-nullptr cost to the OptimizeSingleRoute() method so the
614  // costs are optimized by the LP.
615  int64_t cost = 0;
616  if (OptimizeSingleRoute(vehicle, next_accessor, solver,
617  /*cumul_values=*/nullptr, /*break_values=*/nullptr,
618  &cost, /*transit_cost=*/nullptr,
619  /*clear_lp=*/false) ==
622  }
623  const DimensionSchedulingStatus status = PackRoutes({vehicle}, solver);
626  }
627  const int64_t local_offset =
628  dimension_->GetLocalOptimizerOffsetForVehicle(vehicle);
629  SetValuesFromLP(current_route_cumul_variables_, local_offset, solver,
630  cumul_values);
631  SetValuesFromLP(current_route_break_variables_, local_offset, solver,
632  break_values);
633  solver->Clear();
634  return status;
635 }
636 
637 DimensionSchedulingStatus DimensionCumulOptimizerCore::PackRoutes(
638  std::vector<int> vehicles, RoutingLinearSolverWrapper* solver) {
639  const RoutingModel* model = dimension_->model();
640 
641  // NOTE(user): Given our constraint matrix, our problem *should* always
642  // have an integer optimal solution, in which case we can round to the nearest
643  // integer both for the objective constraint bound (returned by
644  // GetObjectiveValue()) and the end cumul variable bound after minimizing
645  // (see b/154381899 showcasing an example where std::ceil leads to an
646  // "imperfect" packing due to rounding precision errors).
647  // If this DCHECK ever fails, it can be removed but the code below should be
648  // adapted to have a 2-phase approach, solving once with the rounded value as
649  // bound and if this fails, solve again using std::ceil.
650  DCHECK(solver->SolutionIsInteger());
651 
652  // Minimize the route end times without increasing the cost.
653  const int objective_ct =
654  solver->CreateNewConstraint(0, solver->GetObjectiveValue());
655 
656  for (int variable = 0; variable < solver->NumVariables(); variable++) {
657  const double coefficient = solver->GetObjectiveCoefficient(variable);
658  if (coefficient != 0) {
659  solver->SetCoefficient(objective_ct, variable, coefficient);
660  }
661  }
662  solver->ClearObjective();
663  for (int vehicle : vehicles) {
664  solver->SetObjectiveCoefficient(
665  index_to_cumul_variable_[model->End(vehicle)], 1);
666  }
667 
668  if (solver->Solve(model->RemainingTime()) ==
671  }
672 
673  // Maximize the route start times without increasing the cost or the route end
674  // times.
675  solver->ClearObjective();
676  for (int vehicle : vehicles) {
677  const int end_cumul_var = index_to_cumul_variable_[model->End(vehicle)];
678  // end_cumul_var <= solver.GetValue(end_cumul_var)
679  solver->SetVariableBounds(
680  end_cumul_var, solver->GetVariableLowerBound(end_cumul_var),
681  MathUtil::FastInt64Round(solver->GetValue(end_cumul_var)));
682 
683  // Maximize the starts of the routes.
684  solver->SetObjectiveCoefficient(
685  index_to_cumul_variable_[model->Start(vehicle)], -1);
686  }
687  return solver->Solve(model->RemainingTime());
688 }
689 
690 void DimensionCumulOptimizerCore::InitOptimizer(
691  RoutingLinearSolverWrapper* solver) {
692  solver->Clear();
693  index_to_cumul_variable_.assign(dimension_->cumuls().size(), -1);
694  max_end_cumul_ = solver->CreateNewPositiveVariable();
695  min_start_cumul_ = solver->CreateNewPositiveVariable();
696 }
697 
698 bool DimensionCumulOptimizerCore::ComputeRouteCumulBounds(
699  const std::vector<int64_t>& route,
700  const std::vector<int64_t>& fixed_transits, int64_t cumul_offset) {
701  const int route_size = route.size();
702  current_route_min_cumuls_.resize(route_size);
703  current_route_max_cumuls_.resize(route_size);
704  if (propagator_ != nullptr) {
705  for (int pos = 0; pos < route_size; pos++) {
706  const int64_t node = route[pos];
707  current_route_min_cumuls_[pos] = propagator_->CumulMin(node);
708  DCHECK_GE(current_route_min_cumuls_[pos], 0);
709  current_route_max_cumuls_[pos] = propagator_->CumulMax(node);
710  DCHECK_GE(current_route_max_cumuls_[pos], current_route_min_cumuls_[pos]);
711  }
712  return true;
713  }
714 
715  // Extract cumul min/max and fixed transits from CP.
716  for (int pos = 0; pos < route_size; ++pos) {
717  if (!GetCumulBoundsWithOffset(*dimension_, route[pos], cumul_offset,
718  &current_route_min_cumuls_[pos],
719  &current_route_max_cumuls_[pos])) {
720  return false;
721  }
722  }
723 
724  // Refine cumul bounds using
725  // cumul[i+1] >= cumul[i] + fixed_transit[i] + slack[i].
726  for (int pos = 1; pos < route_size; ++pos) {
727  const int64_t slack_min = dimension_->SlackVar(route[pos - 1])->Min();
728  current_route_min_cumuls_[pos] = std::max(
729  current_route_min_cumuls_[pos],
730  CapAdd(
731  CapAdd(current_route_min_cumuls_[pos - 1], fixed_transits[pos - 1]),
732  slack_min));
733  current_route_min_cumuls_[pos] = GetFirstPossibleValueForCumulWithOffset(
734  *dimension_, route[pos], current_route_min_cumuls_[pos], cumul_offset);
735  if (current_route_min_cumuls_[pos] > current_route_max_cumuls_[pos]) {
736  return false;
737  }
738  }
739 
740  for (int pos = route_size - 2; pos >= 0; --pos) {
741  // If cumul_max[pos+1] is kint64max, it will be translated to
742  // double +infinity, so it must not constrain cumul_max[pos].
743  if (current_route_max_cumuls_[pos + 1] <
745  const int64_t slack_min = dimension_->SlackVar(route[pos])->Min();
746  current_route_max_cumuls_[pos] = std::min(
747  current_route_max_cumuls_[pos],
748  CapSub(
749  CapSub(current_route_max_cumuls_[pos + 1], fixed_transits[pos]),
750  slack_min));
751  current_route_max_cumuls_[pos] = GetLastPossibleValueForCumulWithOffset(
752  *dimension_, route[pos], current_route_max_cumuls_[pos],
753  cumul_offset);
754  if (current_route_max_cumuls_[pos] < current_route_min_cumuls_[pos]) {
755  return false;
756  }
757  }
758  }
759  return true;
760 }
761 
762 bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
763  int vehicle, const std::function<int64_t(int64_t)>& next_accessor,
764  int64_t cumul_offset, bool optimize_costs,
765  RoutingLinearSolverWrapper* solver, int64_t* route_transit_cost,
766  int64_t* route_cost_offset) {
767  RoutingModel* const model = dimension_->model();
768  // Extract the vehicle's path from next_accessor.
769  std::vector<int64_t> path;
770  {
771  int node = model->Start(vehicle);
772  path.push_back(node);
773  while (!model->IsEnd(node)) {
774  node = next_accessor(node);
775  path.push_back(node);
776  }
777  DCHECK_GE(path.size(), 2);
778  }
779  const int path_size = path.size();
780 
781  std::vector<int64_t> fixed_transit(path_size - 1);
782  {
783  const std::function<int64_t(int64_t, int64_t)>& transit_accessor =
784  dimension_->transit_evaluator(vehicle);
785  for (int pos = 1; pos < path_size; ++pos) {
786  fixed_transit[pos - 1] = transit_accessor(path[pos - 1], path[pos]);
787  }
788  }
789 
790  if (!ComputeRouteCumulBounds(path, fixed_transit, cumul_offset)) {
791  return false;
792  }
793 
794  // LP Model variables, current_route_cumul_variables_ and lp_slacks.
795  // Create LP variables for cumuls.
796  std::vector<int>& lp_cumuls = current_route_cumul_variables_;
797  lp_cumuls.assign(path_size, -1);
798  for (int pos = 0; pos < path_size; ++pos) {
799  const int lp_cumul = solver->CreateNewPositiveVariable();
800  index_to_cumul_variable_[path[pos]] = lp_cumul;
801  lp_cumuls[pos] = lp_cumul;
802  if (!solver->SetVariableBounds(lp_cumul, current_route_min_cumuls_[pos],
803  current_route_max_cumuls_[pos])) {
804  return false;
805  }
806  const SortedDisjointIntervalList& forbidden =
807  dimension_->forbidden_intervals()[path[pos]];
808  if (forbidden.NumIntervals() > 0) {
809  std::vector<int64_t> starts;
810  std::vector<int64_t> ends;
811  for (const ClosedInterval interval :
812  dimension_->GetAllowedIntervalsInRange(
813  path[pos], CapAdd(current_route_min_cumuls_[pos], cumul_offset),
814  CapAdd(current_route_max_cumuls_[pos], cumul_offset))) {
815  starts.push_back(CapSub(interval.start, cumul_offset));
816  ends.push_back(CapSub(interval.end, cumul_offset));
817  }
818  solver->SetVariableDisjointBounds(lp_cumul, starts, ends);
819  }
820  }
821  // Create LP variables for slacks.
822  std::vector<int> lp_slacks(path_size - 1, -1);
823  for (int pos = 0; pos < path_size - 1; ++pos) {
824  const IntVar* cp_slack = dimension_->SlackVar(path[pos]);
825  lp_slacks[pos] = solver->CreateNewPositiveVariable();
826  if (!solver->SetVariableBounds(lp_slacks[pos], cp_slack->Min(),
827  cp_slack->Max())) {
828  return false;
829  }
830  }
831 
832  // LP Model constraints and costs.
833  // Add all path constraints to LP:
834  // cumul[i] + fixed_transit[i] + slack[i] == cumul[i+1]
835  // <=> fixed_transit[i] == cumul[i+1] - cumul[i] - slack[i].
836  for (int pos = 0; pos < path_size - 1; ++pos) {
837  const int ct =
838  solver->CreateNewConstraint(fixed_transit[pos], fixed_transit[pos]);
839  solver->SetCoefficient(ct, lp_cumuls[pos + 1], 1);
840  solver->SetCoefficient(ct, lp_cumuls[pos], -1);
841  solver->SetCoefficient(ct, lp_slacks[pos], -1);
842  }
843  if (route_cost_offset != nullptr) *route_cost_offset = 0;
844  if (optimize_costs) {
845  // Add soft upper bounds.
846  for (int pos = 0; pos < path_size; ++pos) {
847  if (!dimension_->HasCumulVarSoftUpperBound(path[pos])) continue;
848  const int64_t coef =
849  dimension_->GetCumulVarSoftUpperBoundCoefficient(path[pos]);
850  if (coef == 0) continue;
851  int64_t bound = dimension_->GetCumulVarSoftUpperBound(path[pos]);
852  if (bound < cumul_offset && route_cost_offset != nullptr) {
853  // Add coef * (cumul_offset - bound) to the cost offset.
854  *route_cost_offset = CapAdd(*route_cost_offset,
855  CapProd(CapSub(cumul_offset, bound), coef));
856  }
857  bound = std::max<int64_t>(0, CapSub(bound, cumul_offset));
858  if (current_route_max_cumuls_[pos] <= bound) {
859  // constraint is never violated.
860  continue;
861  }
862  const int soft_ub_diff = solver->CreateNewPositiveVariable();
863  solver->SetObjectiveCoefficient(soft_ub_diff, coef);
864  // cumul - soft_ub_diff <= bound.
865  const int ct = solver->CreateNewConstraint(
867  solver->SetCoefficient(ct, lp_cumuls[pos], 1);
868  solver->SetCoefficient(ct, soft_ub_diff, -1);
869  }
870  // Add soft lower bounds.
871  for (int pos = 0; pos < path_size; ++pos) {
872  if (!dimension_->HasCumulVarSoftLowerBound(path[pos])) continue;
873  const int64_t coef =
874  dimension_->GetCumulVarSoftLowerBoundCoefficient(path[pos]);
875  if (coef == 0) continue;
876  const int64_t bound = std::max<int64_t>(
877  0, CapSub(dimension_->GetCumulVarSoftLowerBound(path[pos]),
878  cumul_offset));
879  if (current_route_min_cumuls_[pos] >= bound) {
880  // constraint is never violated.
881  continue;
882  }
883  const int soft_lb_diff = solver->CreateNewPositiveVariable();
884  solver->SetObjectiveCoefficient(soft_lb_diff, coef);
885  // bound - cumul <= soft_lb_diff
886  const int ct = solver->CreateNewConstraint(
888  solver->SetCoefficient(ct, lp_cumuls[pos], 1);
889  solver->SetCoefficient(ct, soft_lb_diff, 1);
890  }
891  }
892  // Add pickup and delivery limits.
893  std::vector<int> visited_pairs;
894  StoreVisitedPickupDeliveryPairsOnRoute(
895  *dimension_, vehicle, next_accessor, &visited_pairs,
896  &visited_pickup_delivery_indices_for_pair_);
897  for (int pair_index : visited_pairs) {
898  const int64_t pickup_index =
899  visited_pickup_delivery_indices_for_pair_[pair_index].first;
900  const int64_t delivery_index =
901  visited_pickup_delivery_indices_for_pair_[pair_index].second;
902  visited_pickup_delivery_indices_for_pair_[pair_index] = {-1, -1};
903 
904  DCHECK_GE(pickup_index, 0);
905  if (delivery_index < 0) {
906  // We didn't encounter a delivery for this pickup.
907  continue;
908  }
909 
910  const int64_t limit = dimension_->GetPickupToDeliveryLimitForPair(
911  pair_index, model->GetPickupIndexPairs(pickup_index)[0].second,
912  model->GetDeliveryIndexPairs(delivery_index)[0].second);
913  if (limit < std::numeric_limits<int64_t>::max()) {
914  // delivery_cumul - pickup_cumul <= limit.
915  const int ct = solver->CreateNewConstraint(
917  solver->SetCoefficient(ct, index_to_cumul_variable_[delivery_index], 1);
918  solver->SetCoefficient(ct, index_to_cumul_variable_[pickup_index], -1);
919  }
920  }
921 
922  // Add span bound constraint.
923  const int64_t span_bound = dimension_->GetSpanUpperBoundForVehicle(vehicle);
924  if (span_bound < std::numeric_limits<int64_t>::max()) {
925  // end_cumul - start_cumul <= bound
926  const int ct = solver->CreateNewConstraint(
927  std::numeric_limits<int64_t>::min(), span_bound);
928  solver->SetCoefficient(ct, lp_cumuls.back(), 1);
929  solver->SetCoefficient(ct, lp_cumuls.front(), -1);
930  }
931  // Add span cost.
932  const int64_t span_cost_coef =
933  dimension_->GetSpanCostCoefficientForVehicle(vehicle);
934  if (optimize_costs && span_cost_coef > 0) {
935  solver->SetObjectiveCoefficient(lp_cumuls.back(), span_cost_coef);
936  solver->SetObjectiveCoefficient(lp_cumuls.front(), -span_cost_coef);
937  }
938  // Add soft span cost.
939  if (optimize_costs && dimension_->HasSoftSpanUpperBounds()) {
940  SimpleBoundCosts::BoundCost bound_cost =
941  dimension_->GetSoftSpanUpperBoundForVehicle(vehicle);
942  if (bound_cost.bound < std::numeric_limits<int64_t>::max() &&
943  bound_cost.cost > 0) {
944  const int span_violation = solver->CreateNewPositiveVariable();
945  // end - start <= bound + span_violation
946  const int violation = solver->CreateNewConstraint(
947  std::numeric_limits<int64_t>::min(), bound_cost.bound);
948  solver->SetCoefficient(violation, lp_cumuls.back(), 1.0);
949  solver->SetCoefficient(violation, lp_cumuls.front(), -1.0);
950  solver->SetCoefficient(violation, span_violation, -1.0);
951  // Add span_violation * cost to objective.
952  solver->SetObjectiveCoefficient(span_violation, bound_cost.cost);
953  }
954  }
955  // Add global span constraint.
956  if (optimize_costs && dimension_->global_span_cost_coefficient() > 0) {
957  // min_start_cumul_ <= cumuls[start]
958  int ct =
959  solver->CreateNewConstraint(std::numeric_limits<int64_t>::min(), 0);
960  solver->SetCoefficient(ct, min_start_cumul_, 1);
961  solver->SetCoefficient(ct, lp_cumuls.front(), -1);
962  // max_end_cumul_ >= cumuls[end]
963  ct = solver->CreateNewConstraint(0, std::numeric_limits<int64_t>::max());
964  solver->SetCoefficient(ct, max_end_cumul_, 1);
965  solver->SetCoefficient(ct, lp_cumuls.back(), -1);
966  }
967  // Fill transit cost if specified.
968  if (route_transit_cost != nullptr) {
969  if (optimize_costs && span_cost_coef > 0) {
970  const int64_t total_fixed_transit = std::accumulate(
971  fixed_transit.begin(), fixed_transit.end(), 0, CapAdd);
972  *route_transit_cost = CapProd(total_fixed_transit, span_cost_coef);
973  } else {
974  *route_transit_cost = 0;
975  }
976  }
977  // For every break that must be inside the route, the duration of that break
978  // must be flowed in the slacks of arcs that can intersect the break.
979  // This LP modelization is correct but not complete:
980  // can miss some cases where the breaks cannot fit.
981  // TODO(user): remove the need for returns in the code below.
982  current_route_break_variables_.clear();
983  if (!dimension_->HasBreakConstraints()) return true;
984  const std::vector<IntervalVar*>& breaks =
985  dimension_->GetBreakIntervalsOfVehicle(vehicle);
986  const int num_breaks = breaks.size();
987  // When there are no breaks, only break distance needs to be modeled,
988  // and it reduces to a span maximum.
989  // TODO(user): Also add the case where no breaks can intersect the route.
990  if (num_breaks == 0) {
991  int64_t maximum_route_span = std::numeric_limits<int64_t>::max();
992  for (const auto& distance_duration :
993  dimension_->GetBreakDistanceDurationOfVehicle(vehicle)) {
994  maximum_route_span =
995  std::min(maximum_route_span, distance_duration.first);
996  }
997  if (maximum_route_span < std::numeric_limits<int64_t>::max()) {
998  const int ct = solver->CreateNewConstraint(
999  std::numeric_limits<int64_t>::min(), maximum_route_span);
1000  solver->SetCoefficient(ct, lp_cumuls.back(), 1);
1001  solver->SetCoefficient(ct, lp_cumuls.front(), -1);
1002  }
1003  return true;
1004  }
1005  // Gather visit information: the visit of node i has [start, end) =
1006  // [cumul[i] - post_travel[i-1], cumul[i] + pre_travel[i]).
1007  // Breaks cannot overlap those visit intervals.
1008  std::vector<int64_t> pre_travel(path_size - 1, 0);
1009  std::vector<int64_t> post_travel(path_size - 1, 0);
1010  {
1011  const int pre_travel_index =
1012  dimension_->GetPreTravelEvaluatorOfVehicle(vehicle);
1013  if (pre_travel_index != -1) {
1014  FillPathEvaluation(path, model->TransitCallback(pre_travel_index),
1015  &pre_travel);
1016  }
1017  const int post_travel_index =
1018  dimension_->GetPostTravelEvaluatorOfVehicle(vehicle);
1019  if (post_travel_index != -1) {
1020  FillPathEvaluation(path, model->TransitCallback(post_travel_index),
1021  &post_travel);
1022  }
1023  }
1024  // If the solver is CPSAT, it will need to represent the times at which
1025  // breaks are scheduled, those variables are used both in the pure breaks
1026  // part and in the break distance part of the model.
1027  // Otherwise, it doesn't need the variables and they are not created.
1028  std::vector<int> lp_break_start;
1029  std::vector<int> lp_break_duration;
1030  std::vector<int> lp_break_end;
1031  if (solver->IsCPSATSolver()) {
1032  lp_break_start.resize(num_breaks, -1);
1033  lp_break_duration.resize(num_breaks, -1);
1034  lp_break_end.resize(num_breaks, -1);
1035  }
1036 
1037  std::vector<int> slack_exact_lower_bound_ct(path_size - 1, -1);
1038  std::vector<int> slack_linear_lower_bound_ct(path_size - 1, -1);
1039 
1040  const int64_t vehicle_start_min = current_route_min_cumuls_.front();
1041  const int64_t vehicle_start_max = current_route_max_cumuls_.front();
1042  const int64_t vehicle_end_min = current_route_min_cumuls_.back();
1043  const int64_t vehicle_end_max = current_route_max_cumuls_.back();
1044  const int all_break_variables_offset =
1045  vehicle_to_all_break_variables_offset_[vehicle];
1046  for (int br = 0; br < num_breaks; ++br) {
1047  const IntervalVar& break_var = *breaks[br];
1048  if (!break_var.MustBePerformed()) continue;
1049  const int64_t break_start_min = CapSub(break_var.StartMin(), cumul_offset);
1050  const int64_t break_start_max = CapSub(break_var.StartMax(), cumul_offset);
1051  const int64_t break_end_min = CapSub(break_var.EndMin(), cumul_offset);
1052  const int64_t break_end_max = CapSub(break_var.EndMax(), cumul_offset);
1053  const int64_t break_duration_min = break_var.DurationMin();
1054  const int64_t break_duration_max = break_var.DurationMax();
1055  // The CPSAT solver encodes all breaks that can intersect the route,
1056  // the LP solver only encodes the breaks that must intersect the route.
1057  if (solver->IsCPSATSolver()) {
1058  if (break_end_max <= vehicle_start_min ||
1059  vehicle_end_max <= break_start_min) {
1060  all_break_variables_[all_break_variables_offset + 2 * br] = -1;
1061  all_break_variables_[all_break_variables_offset + 2 * br + 1] = -1;
1062  current_route_break_variables_.push_back(-1);
1063  current_route_break_variables_.push_back(-1);
1064  continue;
1065  }
1066  lp_break_start[br] =
1067  solver->AddVariable(break_start_min, break_start_max);
1068  lp_break_end[br] = solver->AddVariable(break_end_min, break_end_max);
1069  lp_break_duration[br] =
1070  solver->AddVariable(break_duration_min, break_duration_max);
1071  // start + duration = end.
1072  solver->AddLinearConstraint(0, 0,
1073  {{lp_break_end[br], 1},
1074  {lp_break_start[br], -1},
1075  {lp_break_duration[br], -1}});
1076  // Record index of variables
1077  all_break_variables_[all_break_variables_offset + 2 * br] =
1078  lp_break_start[br];
1079  all_break_variables_[all_break_variables_offset + 2 * br + 1] =
1080  lp_break_end[br];
1081  current_route_break_variables_.push_back(lp_break_start[br]);
1082  current_route_break_variables_.push_back(lp_break_end[br]);
1083  } else {
1084  if (break_end_min <= vehicle_start_max ||
1085  vehicle_end_min <= break_start_max) {
1086  continue;
1087  }
1088  }
1089 
1090  // Create a constraint for every break, that forces it to be scheduled
1091  // in exactly one place, i.e. one slack or before/after the route.
1092  // sum_i break_in_slack_i == 1.
1093  const int break_in_one_slack_ct = solver->CreateNewConstraint(1, 1);
1094 
1095  if (solver->IsCPSATSolver()) {
1096  // Break can be before route.
1097  if (break_end_min <= vehicle_start_max) {
1098  const int ct = solver->AddLinearConstraint(
1100  {{lp_cumuls.front(), 1}, {lp_break_end[br], -1}});
1101  const int break_is_before_route = solver->AddVariable(0, 1);
1102  solver->SetEnforcementLiteral(ct, break_is_before_route);
1103  solver->SetCoefficient(break_in_one_slack_ct, break_is_before_route, 1);
1104  }
1105  // Break can be after route.
1106  if (vehicle_end_min <= break_start_max) {
1107  const int ct = solver->AddLinearConstraint(
1109  {{lp_break_start[br], 1}, {lp_cumuls.back(), -1}});
1110  const int break_is_after_route = solver->AddVariable(0, 1);
1111  solver->SetEnforcementLiteral(ct, break_is_after_route);
1112  solver->SetCoefficient(break_in_one_slack_ct, break_is_after_route, 1);
1113  }
1114  }
1115 
1116  // Add the possibility of fitting the break during each slack where it can.
1117  for (int pos = 0; pos < path_size - 1; ++pos) {
1118  // Pass on slacks that cannot start before, cannot end after,
1119  // or are not long enough to contain the break.
1120  const int64_t slack_start_min =
1121  CapAdd(current_route_min_cumuls_[pos], pre_travel[pos]);
1122  if (slack_start_min > break_start_max) break;
1123  const int64_t slack_end_max =
1124  CapSub(current_route_max_cumuls_[pos + 1], post_travel[pos]);
1125  if (break_end_min > slack_end_max) continue;
1126  const int64_t slack_duration_max =
1127  std::min(CapSub(CapSub(current_route_max_cumuls_[pos + 1],
1128  current_route_min_cumuls_[pos]),
1129  fixed_transit[pos]),
1130  dimension_->SlackVar(path[pos])->Max());
1131  if (slack_duration_max < break_duration_min) continue;
1132 
1133  // Break can fit into slack: make LP variable, add to break and slack
1134  // constraints.
1135  // Make a linearized slack lower bound (lazily), that represents
1136  // sum_br break_duration_min(br) * break_in_slack(br, pos) <=
1137  // lp_slacks(pos).
1138  const int break_in_slack = solver->AddVariable(0, 1);
1139  solver->SetCoefficient(break_in_one_slack_ct, break_in_slack, 1);
1140  if (slack_linear_lower_bound_ct[pos] == -1) {
1141  slack_linear_lower_bound_ct[pos] = solver->AddLinearConstraint(
1142  std::numeric_limits<int64_t>::min(), 0, {{lp_slacks[pos], -1}});
1143  }
1144  solver->SetCoefficient(slack_linear_lower_bound_ct[pos], break_in_slack,
1145  break_duration_min);
1146  if (solver->IsCPSATSolver()) {
1147  // Exact relation between breaks, slacks and cumul variables.
1148  // Make an exact slack lower bound (lazily), that represents
1149  // sum_br break_duration(br) * break_in_slack(br, pos) <=
1150  // lp_slacks(pos).
1151  const int break_duration_in_slack =
1152  solver->AddVariable(0, slack_duration_max);
1153  solver->AddProductConstraint(break_duration_in_slack,
1154  {break_in_slack, lp_break_duration[br]});
1155  if (slack_exact_lower_bound_ct[pos] == -1) {
1156  slack_exact_lower_bound_ct[pos] = solver->AddLinearConstraint(
1157  std::numeric_limits<int64_t>::min(), 0, {{lp_slacks[pos], -1}});
1158  }
1159  solver->SetCoefficient(slack_exact_lower_bound_ct[pos],
1160  break_duration_in_slack, 1);
1161  // If break_in_slack_i == 1, then
1162  // 1) break_start >= cumul[pos] + pre_travel[pos]
1163  const int break_start_after_current_ct = solver->AddLinearConstraint(
1164  pre_travel[pos], std::numeric_limits<int64_t>::max(),
1165  {{lp_break_start[br], 1}, {lp_cumuls[pos], -1}});
1166  solver->SetEnforcementLiteral(break_start_after_current_ct,
1167  break_in_slack);
1168  // 2) break_end <= cumul[pos+1] - post_travel[pos]
1169  const int break_ends_before_next_ct = solver->AddLinearConstraint(
1170  post_travel[pos], std::numeric_limits<int64_t>::max(),
1171  {{lp_cumuls[pos + 1], 1}, {lp_break_end[br], -1}});
1172  solver->SetEnforcementLiteral(break_ends_before_next_ct,
1173  break_in_slack);
1174  }
1175  }
1176  }
1177 
1178  if (!solver->IsCPSATSolver()) return true;
1179  if (!dimension_->GetBreakDistanceDurationOfVehicle(vehicle).empty()) {
1180  // If there is an optional interval, the following model would be wrong.
1181  // TODO(user): support optional intervals.
1182  for (const IntervalVar* interval :
1183  dimension_->GetBreakIntervalsOfVehicle(vehicle)) {
1184  if (!interval->MustBePerformed()) return true;
1185  }
1186  // When this feature is used, breaks are in sorted order.
1187  for (int br = 1; br < num_breaks; ++br) {
1188  solver->AddLinearConstraint(
1190  {{lp_break_end[br - 1], -1}, {lp_break_start[br], 1}});
1191  }
1192  }
1193  for (const auto& distance_duration :
1194  dimension_->GetBreakDistanceDurationOfVehicle(vehicle)) {
1195  const int64_t limit = distance_duration.first;
1196  const int64_t min_break_duration = distance_duration.second;
1197  // Interbreak limit constraint: breaks are interpreted as being in sorted
1198  // order, and the maximum duration between two consecutive
1199  // breaks of duration more than 'min_break_duration' is 'limit'. This
1200  // considers the time until start of route and after end of route to be
1201  // infinite breaks.
1202  // The model for this constraint adds some 'cover_i' variables, such that
1203  // the breaks up to i and the start of route allows to go without a break.
1204  // With s_i the start of break i and e_i its end:
1205  // - the route start covers time from start to start + limit:
1206  // cover_0 = route_start + limit
1207  // - the coverage up to a given break is the largest of the coverage of the
1208  // previous break and if the break is long enough, break end + limit:
1209  // cover_{i+1} = max(cover_i,
1210  // e_i - s_i >= min_break_duration ? e_i + limit : -inf)
1211  // - the coverage of the last break must be at least the route end,
1212  // to ensure the time point route_end-1 is covered:
1213  // cover_{num_breaks} >= route_end
1214  // - similarly, time point s_i-1 must be covered by breaks up to i-1,
1215  // but only if the cover has not reached the route end.
1216  // For instance, a vehicle could have a choice between two days,
1217  // with a potential break on day 1 and a potential break on day 2,
1218  // but the break of day 1 does not have to cover that of day 2!
1219  // cover_{i-1} < route_end => s_i <= cover_{i-1}
1220  // This is sufficient to ensure that the union of the intervals
1221  // (-infinity, route_start], [route_end, +infinity) and all
1222  // [s_i, e_i+limit) where e_i - s_i >= min_break_duration is
1223  // the whole timeline (-infinity, +infinity).
1224  int previous_cover = solver->AddVariable(CapAdd(vehicle_start_min, limit),
1225  CapAdd(vehicle_start_max, limit));
1226  solver->AddLinearConstraint(limit, limit,
1227  {{previous_cover, 1}, {lp_cumuls.front(), -1}});
1228  for (int br = 0; br < num_breaks; ++br) {
1229  if (lp_break_start[br] == -1) continue;
1230  const int64_t break_end_min = CapSub(breaks[br]->EndMin(), cumul_offset);
1231  const int64_t break_end_max = CapSub(breaks[br]->EndMax(), cumul_offset);
1232  // break_is_eligible <=>
1233  // break_end - break_start >= break_minimum_duration.
1234  const int break_is_eligible = solver->AddVariable(0, 1);
1235  const int break_is_not_eligible = solver->AddVariable(0, 1);
1236  {
1237  solver->AddLinearConstraint(
1238  1, 1, {{break_is_eligible, 1}, {break_is_not_eligible, 1}});
1239  const int positive_ct = solver->AddLinearConstraint(
1240  min_break_duration, std::numeric_limits<int64_t>::max(),
1241  {{lp_break_end[br], 1}, {lp_break_start[br], -1}});
1242  solver->SetEnforcementLiteral(positive_ct, break_is_eligible);
1243  const int negative_ct = solver->AddLinearConstraint(
1244  std::numeric_limits<int64_t>::min(), min_break_duration - 1,
1245  {{lp_break_end[br], 1}, {lp_break_start[br], -1}});
1246  solver->SetEnforcementLiteral(negative_ct, break_is_not_eligible);
1247  }
1248  // break_is_eligible => break_cover == break_end + limit.
1249  // break_is_not_eligible => break_cover == vehicle_start_min + limit.
1250  // break_cover's initial domain is the smallest interval that contains the
1251  // union of sets {vehicle_start_min+limit} and
1252  // [break_end_min+limit, break_end_max+limit).
1253  const int break_cover = solver->AddVariable(
1254  CapAdd(std::min(vehicle_start_min, break_end_min), limit),
1255  CapAdd(std::max(vehicle_start_min, break_end_max), limit));
1256  const int limit_cover_ct = solver->AddLinearConstraint(
1257  limit, limit, {{break_cover, 1}, {lp_break_end[br], -1}});
1258  solver->SetEnforcementLiteral(limit_cover_ct, break_is_eligible);
1259  const int empty_cover_ct = solver->AddLinearConstraint(
1260  CapAdd(vehicle_start_min, limit), CapAdd(vehicle_start_min, limit),
1261  {{break_cover, 1}});
1262  solver->SetEnforcementLiteral(empty_cover_ct, break_is_not_eligible);
1263 
1264  const int cover =
1265  solver->AddVariable(CapAdd(vehicle_start_min, limit),
1267  solver->AddMaximumConstraint(cover, {previous_cover, break_cover});
1268  // Cover chaining. If route end is not covered, break start must be:
1269  // cover_{i-1} < route_end => s_i <= cover_{i-1}
1270  const int route_end_is_not_covered = solver->AddReifiedLinearConstraint(
1272  {{lp_cumuls.back(), 1}, {previous_cover, -1}});
1273  const int break_start_cover_ct = solver->AddLinearConstraint(
1275  {{previous_cover, 1}, {lp_break_start[br], -1}});
1276  solver->SetEnforcementLiteral(break_start_cover_ct,
1277  route_end_is_not_covered);
1278 
1279  previous_cover = cover;
1280  }
1281  solver->AddLinearConstraint(0, std::numeric_limits<int64_t>::max(),
1282  {{previous_cover, 1}, {lp_cumuls.back(), -1}});
1283  }
1284 
1285  return true;
1286 }
1287 
1288 void DimensionCumulOptimizerCore::SetGlobalConstraints(
1289  bool optimize_costs, RoutingLinearSolverWrapper* solver) {
1290  // Global span cost =
1291  // global_span_cost_coefficient * (max_end_cumul - min_start_cumul).
1292  const int64_t global_span_coeff = dimension_->global_span_cost_coefficient();
1293  if (optimize_costs && global_span_coeff > 0) {
1294  solver->SetObjectiveCoefficient(max_end_cumul_, global_span_coeff);
1295  solver->SetObjectiveCoefficient(min_start_cumul_, -global_span_coeff);
1296  }
1297 
1298  // Node precedence constraints, set when both nodes are visited.
1299  for (const RoutingDimension::NodePrecedence& precedence :
1300  dimension_->GetNodePrecedences()) {
1301  const int first_cumul_var = index_to_cumul_variable_[precedence.first_node];
1302  const int second_cumul_var =
1303  index_to_cumul_variable_[precedence.second_node];
1304  if (first_cumul_var < 0 || second_cumul_var < 0) {
1305  // At least one of the nodes is not on any route, skip this precedence
1306  // constraint.
1307  continue;
1308  }
1309  DCHECK_NE(first_cumul_var, second_cumul_var)
1310  << "Dimension " << dimension_->name()
1311  << " has a self-precedence on node " << precedence.first_node << ".";
1312 
1313  // cumul[second_node] - cumul[first_node] >= offset.
1314  const int ct = solver->CreateNewConstraint(
1315  precedence.offset, std::numeric_limits<int64_t>::max());
1316  solver->SetCoefficient(ct, second_cumul_var, 1);
1317  solver->SetCoefficient(ct, first_cumul_var, -1);
1318  }
1319 }
1320 
1321 void DimensionCumulOptimizerCore::SetValuesFromLP(
1322  const std::vector<int>& lp_variables, int64_t offset,
1323  RoutingLinearSolverWrapper* solver, std::vector<int64_t>* lp_values) {
1324  if (lp_values == nullptr) return;
1325  lp_values->assign(lp_variables.size(), std::numeric_limits<int64_t>::min());
1326  for (int i = 0; i < lp_variables.size(); i++) {
1327  const int cumul_var = lp_variables[i];
1328  if (cumul_var < 0) continue; // Keep default value, kint64min.
1329  const double lp_value_double = solver->GetValue(cumul_var);
1330  const int64_t lp_value_int64 =
1331  (lp_value_double >= std::numeric_limits<int64_t>::max())
1333  : MathUtil::FastInt64Round(lp_value_double);
1334  (*lp_values)[i] = CapAdd(lp_value_int64, offset);
1335  }
1336 }
1337 
1339  const RoutingDimension* dimension)
1340  : optimizer_core_(dimension,
1341  /*use_precedence_propagator=*/
1342  !dimension->GetNodePrecedences().empty()) {
1343  solver_ =
1344  absl::make_unique<RoutingGlopWrapper>(GetGlopParametersForGlobalLP());
1345 }
1346 
1348  const std::function<int64_t(int64_t)>& next_accessor,
1349  int64_t* optimal_cost_without_transits) {
1350  int64_t cost = 0;
1351  int64_t transit_cost = 0;
1352  if (optimizer_core_.Optimize(next_accessor, solver_.get(), nullptr, nullptr,
1353  &cost, &transit_cost)) {
1354  if (optimal_cost_without_transits != nullptr) {
1355  *optimal_cost_without_transits = CapSub(cost, transit_cost);
1356  }
1357  return true;
1358  }
1359  return false;
1360 }
1361 
1363  const std::function<int64_t(int64_t)>& next_accessor,
1364  std::vector<int64_t>* optimal_cumuls,
1365  std::vector<int64_t>* optimal_breaks) {
1366  return optimizer_core_.Optimize(next_accessor, solver_.get(), optimal_cumuls,
1367  optimal_breaks, nullptr, nullptr);
1368 }
1369 
1371  const std::function<int64_t(int64_t)>& next_accessor) {
1372  return optimizer_core_.Optimize(next_accessor, solver_.get(), nullptr,
1373  nullptr, nullptr, nullptr);
1374 }
1375 
1377  const std::function<int64_t(int64_t)>& next_accessor,
1378  std::vector<int64_t>* packed_cumuls, std::vector<int64_t>* packed_breaks) {
1379  return optimizer_core_.OptimizeAndPack(next_accessor, solver_.get(),
1380  packed_cumuls, packed_breaks);
1381 }
1382 } // 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:894
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:897
#define DCHECK_LT(val1, val2)
Definition: base/logging.h:896
#define LOG(severity)
Definition: base/logging.h:423
#define DCHECK(condition)
Definition: base/logging.h:892
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:893
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)
bool 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 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)
bool 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)
bool ComputeCumulCostWithoutFixedTransits(const std::function< int64_t(int64_t)> &next_accessor, int64_t *optimal_cost_without_transits)
GlobalDimensionCumulOptimizer(const RoutingDimension *dimension)
bool IsFeasible(const std::function< int64_t(int64_t)> &next_accessor)
bool ComputeCumuls(const std::function< int64_t(int64_t)> &next_accessor, std::vector< int64_t > *optimal_cumuls, std::vector< int64_t > *optimal_breaks)
bool 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:2374
SimpleBoundCosts::BoundCost GetSoftSpanUpperBoundForVehicle(int vehicle) const
Definition: routing.h:2720
const std::vector< IntVar * > & cumuls() const
Like CumulVar(), TransitVar(), SlackVar() but return the whole variable vectors instead (indexed by i...
Definition: routing.h:2403
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:6172
RoutingModel * model() const
Returns the model on which the dimension was created.
Definition: routing.h:2378
int64_t GetGlobalOptimizerOffset() const
Definition: routing.h:2695
int64_t GetPickupToDeliveryLimitForPair(int pair_index, int pickup, int delivery) const
Definition: routing.cc:6433
bool HasCumulVarSoftLowerBound(int64_t index) const
Returns true if a soft lower bound has been set for a given variable index.
Definition: routing.cc:6211
int64_t GetSpanCostCoefficientForVehicle(int vehicle) const
Definition: routing.h:2683
int64_t global_span_cost_coefficient() const
Definition: routing.h:2691
int64_t GetSpanUpperBoundForVehicle(int vehicle) const
Definition: routing.h:2675
bool HasBreakConstraints() const
Returns true if any break interval or break distance was defined.
Definition: routing.cc:6374
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:6033
int GetPreTravelEvaluatorOfVehicle(int vehicle) const
!defined(SWIGPYTHON)
Definition: routing.cc:6385
bool HasCumulVarSoftUpperBound(int64_t index) const
Returns true if a soft upper bound has been set for a given variable index.
Definition: routing.cc:6159
const std::vector< IntervalVar * > & GetBreakIntervalsOfVehicle(int vehicle) const
Returns the break intervals set by SetBreakIntervalsOfVehicle().
Definition: routing.cc:6378
IntVar * SlackVar(int64_t index) const
Definition: routing.h:2398
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:2457
int64_t GetLocalOptimizerOffsetForVehicle(int vehicle) const
Definition: routing.h:2699
const std::string & name() const
Returns the name of the dimension.
Definition: routing.h:2628
const std::vector< NodePrecedence > & GetNodePrecedences() const
Definition: routing.h:2665
int64_t GetCumulVarSoftUpperBound(int64_t index) const
Returns the soft upper bound of a cumul variable for a given variable index.
Definition: routing.cc:6164
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:6413
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:6224
int GetPostTravelEvaluatorOfVehicle(int vehicle) const
Definition: routing.cc:6391
int64_t GetCumulVarSoftLowerBound(int64_t index) const
Returns the soft lower bound of a cumul variable for a given variable index.
Definition: routing.cc:6216
const std::vector< SortedDisjointIntervalList > & forbidden_intervals() const
Returns forbidden intervals for each node.
Definition: routing.h:2409
virtual void SetCoefficient(int ct, int index, double coefficient)=0
virtual double GetObjectiveCoefficient(int index) const =0
virtual int64_t GetObjectiveValue() const =0
virtual int CreateNewConstraint(int64_t lower_bound, int64_t upper_bound)=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 IndexPairs & GetPickupAndDeliveryPairs() const
Returns pickup and delivery pairs currently in the model.
Definition: routing.h:741
int vehicles() const
Returns the number of vehicle routes in the model.
Definition: routing.h:1350
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:5690
IntervalVar * interval
Definition: resource.cc:100
int64_t bound
int64_t coefficient
int64_t tail
int64_t cost
std::vector< double > lower_bounds