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"
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,
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 
234 const int CumulBoundsPropagator::kNoParent = -2;
235 const 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 
247 void 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 
259 bool 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 
356 bool 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 
383 bool 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 
659 DimensionSchedulingStatus 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) {
678  solver->SetObjectiveCoefficient(
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.
698  solver->SetObjectiveCoefficient(
699  index_to_cumul_variable_[model->Start(vehicle)], -1);
700  }
701  return solver->Solve(model->RemainingTime());
702 }
703 
704 void 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 
712 bool 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 
776 bool 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(
941  std::numeric_limits<int64_t>::min(), span_bound);
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 
1302 void 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 
1425 void 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);
1474  if (status != DimensionSchedulingStatus::INFEASIBLE &&
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 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
DimensionSchedulingStatus ComputeRouteCumulCostWithoutFixedTransits(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, int64_t *optimal_cost_without_transits)
int64_t bound
DimensionSchedulingStatus ComputePackedCumuls(const std::function< int64_t(int64_t)> &next_accessor, std::vector< int64_t > *packed_cumuls, std::vector< int64_t > *packed_breaks)
int64_t CapSub(int64_t x, int64_t y)
int64_t min
Definition: alldiff_cst.cc:139
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< IntVar * > & cumuls() const
Like CumulVar(), TransitVar(), SlackVar() but return the whole variable vectors instead (indexed by i...
Definition: routing.h:2523
int64_t GetLocalOptimizerOffsetForVehicle(int vehicle) const
Definition: routing.h:2837
bool HasBreakConstraints() const
Returns true if any break interval or break distance was defined.
Definition: routing.cc:6494
int GetPreTravelEvaluatorOfVehicle(int vehicle) const
!defined(SWIGPYTHON)
Definition: routing.cc:6505
virtual double GetValue(int index) const =0
std::vector< double > lower_bounds
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
virtual bool SetVariableBounds(int index, int64_t lower_bound, int64_t upper_bound)=0
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
int64_t global_span_cost_coefficient() const
Definition: routing.h:2829
RoutingModel * model() const
Returns the model on which the dimension was created.
Definition: routing.h:2498
virtual void SetObjectiveCoefficient(int index, double coefficient)=0
#define LOG(severity)
Definition: base/logging.h:416
GRBmodel * model
virtual int64_t Min() const =0
int64_t CapProd(int64_t x, int64_t y)
Dimensions represent quantities accumulated at nodes along the routes.
Definition: routing.h:2494
#define DCHECK_GT(val1, val2)
Definition: base/logging.h:891
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)
int64_t GetSpanUpperBoundForVehicle(int vehicle) const
Definition: routing.h:2805
static int64_t FastInt64Round(double x)
Definition: mathutil.h:138
int64_t tail
const IndexPairs & GetPickupAndDeliveryPairs() const
Returns pickup and delivery pairs currently in the model.
Definition: routing.h:834
static Domain AllValues()
Returns the full domain Int64.
void FillPathEvaluation(const std::vector< int64_t > &path, const RoutingModel::TransitCallback2 &evaluator, std::vector< int64_t > *values)
Definition: routing.cc:5810
int64_t coef
Definition: expr_array.cc:1875
int64_t GetPickupToDeliveryLimitForPair(int pair_index, int pickup, int delivery) const
Definition: routing.cc:6553
int64_t b
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)
const RoutingDimension & dimension() const
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
int64_t max
Definition: alldiff_cst.cc:140
Block * next
DimensionSchedulingStatus ComputeCumulCostWithoutFixedTransits(const std::function< int64_t(int64_t)> &next_accessor, int64_t *optimal_cost_without_transits)
virtual int64_t GetObjectiveValue() const =0
double upper_bound
const std::string & name() const
Returns the name of the dimension.
Definition: routing.h:2758
int64_t CapAdd(int64_t x, int64_t y)
#define DCHECK_NE(val1, val2)
Definition: base/logging.h:887
int64_t GetSpanCostCoefficientForVehicle(int vehicle) const
Definition: routing.h:2813
double lower_bound
DimensionSchedulingStatus ComputeCumuls(const std::function< int64_t(int64_t)> &next_accessor, std::vector< int64_t > *optimal_cumuls, std::vector< int64_t > *optimal_breaks)
const std::vector< SortedDisjointIntervalList > & forbidden_intervals() const
Returns forbidden intervals for each node.
Definition: routing.h:2529
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
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)
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:890
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
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)
SimpleBoundCosts::BoundCost GetSoftSpanUpperBoundForVehicle(int vehicle) const
Definition: routing.h:2858
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
int64_t cost
#define DCHECK(condition)
Definition: base/logging.h:885
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
int GetPostTravelEvaluatorOfVehicle(int vehicle) const
Definition: routing.cc:6511
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)
int vehicles() const
Returns the number of vehicle routes in the model.
Definition: routing.h:1458
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)
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:886
const std::vector< NodePrecedence > & GetNodePrecedences() const
Definition: routing.h:2795
DimensionCumulOptimizerCore(const RoutingDimension *dimension, bool use_precedence_propagator)
LocalDimensionCumulOptimizer(const RoutingDimension *dimension, RoutingSearchParameters::SchedulingSolver solver_type)
IntVar * SlackVar(int64_t index) const
Definition: routing.h:2518
Collection of objects used to extend the Constraint Solver library.
bool PropagateCumulBounds(const std::function< int64_t(int64_t)> &next_accessor, int64_t cumul_offset)
int64_t GetGlobalOptimizerOffset() const
Definition: routing.h:2833
SatParameters parameters
const std::vector< IntervalVar * > & GetBreakIntervalsOfVehicle(int vehicle) const
Returns the break intervals set by SetBreakIntervalsOfVehicle().
Definition: routing.cc:6498
virtual DimensionSchedulingStatus Solve(absl::Duration duration_limit)=0
const std::vector< int > & GetDimensionResourceGroupIndices(const RoutingDimension *dimension) const
Returns the indices of resource groups for this dimension.
Definition: routing.cc:1328
GlobalDimensionCumulOptimizer(const RoutingDimension *dimension, RoutingSearchParameters::SchedulingSolver solver_type)
virtual int64_t GetVariableLowerBound(int index) const =0
IntervalVar * interval
Definition: resource.cc:100
virtual int64_t Max() const =0
const Constraint * ct
CumulBoundsPropagator(const RoutingDimension *dimension)
#define DCHECK_LT(val1, val2)
Definition: base/logging.h:889
DimensionSchedulingStatus ComputeRouteCumulCost(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, int64_t *optimal_cost)