OR-Tools  9.6
routing_lp_scheduling.cc
Go to the documentation of this file.
1 // Copyright 2010-2022 Google LLC
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
15 
16 #include <algorithm>
17 #include <cmath>
18 #include <cstdint>
19 #include <deque>
20 #include <functional>
21 #include <iterator>
22 #include <limits>
23 #include <memory>
24 #include <numeric>
25 #include <string>
26 #include <utility>
27 #include <vector>
28 
29 #include "absl/algorithm/container.h"
30 #include "absl/container/flat_hash_map.h"
31 #include "absl/container/flat_hash_set.h"
32 #include "absl/log/check.h"
33 #include "absl/strings/str_format.h"
34 #include "absl/time/time.h"
35 #include "ortools/base/dump_vars.h"
37 #include "ortools/base/logging.h"
38 #include "ortools/base/mathutil.h"
41 #include "ortools/constraint_solver/routing_parameters.pb.h"
42 #include "ortools/glop/parameters.pb.h"
44 #include "ortools/sat/cp_model.pb.h"
45 #include "ortools/sat/lp_utils.h"
49 
50 namespace operations_research {
51 
52 namespace {
53 
54 // The following sets of parameters give the fastest response time without
55 // impacting solutions found negatively.
56 glop::GlopParameters GetGlopParametersForLocalLP() {
57  glop::GlopParameters parameters;
58  parameters.set_use_dual_simplex(true);
59  parameters.set_use_preprocessing(false);
60  return parameters;
61 }
62 
63 glop::GlopParameters GetGlopParametersForGlobalLP() {
64  glop::GlopParameters parameters;
65  parameters.set_use_dual_simplex(true);
66  return parameters;
67 }
68 
69 bool GetCumulBoundsWithOffset(const RoutingDimension& dimension,
70  int64_t node_index, int64_t cumul_offset,
71  int64_t* lower_bound, int64_t* upper_bound) {
72  DCHECK(lower_bound != nullptr);
73  DCHECK(upper_bound != nullptr);
74 
75  const IntVar& cumul_var = *dimension.CumulVar(node_index);
76  *upper_bound = cumul_var.Max();
77  if (*upper_bound < cumul_offset) {
78  return false;
79  }
80 
81  const int64_t first_after_offset =
82  std::max(dimension.GetFirstPossibleGreaterOrEqualValueForNode(
83  node_index, cumul_offset),
84  cumul_var.Min());
85  DCHECK_LT(first_after_offset, std::numeric_limits<int64_t>::max());
86  *lower_bound = CapSub(first_after_offset, cumul_offset);
87  DCHECK_GE(*lower_bound, 0);
88 
90  return true;
91  }
92  *upper_bound = CapSub(*upper_bound, cumul_offset);
93  DCHECK_GE(*upper_bound, *lower_bound);
94  return true;
95 }
96 
97 int64_t GetFirstPossibleValueForCumulWithOffset(
98  const RoutingDimension& dimension, int64_t node_index,
99  int64_t lower_bound_without_offset, int64_t cumul_offset) {
100  return CapSub(
101  dimension.GetFirstPossibleGreaterOrEqualValueForNode(
102  node_index, CapAdd(lower_bound_without_offset, cumul_offset)),
103  cumul_offset);
104 }
105 
106 int64_t GetLastPossibleValueForCumulWithOffset(
107  const RoutingDimension& dimension, int64_t node_index,
108  int64_t upper_bound_without_offset, int64_t cumul_offset) {
109  return CapSub(
110  dimension.GetLastPossibleLessOrEqualValueForNode(
111  node_index, CapAdd(upper_bound_without_offset, cumul_offset)),
112  cumul_offset);
113 }
114 
115 // Finds the pickup/delivery pairs of nodes on a given vehicle's route.
116 // Returns the vector of visited pair indices, and stores the corresponding
117 // pickup/delivery indices in visited_pickup_delivery_indices_for_pair_.
118 // NOTE: Supposes that visited_pickup_delivery_indices_for_pair is correctly
119 // sized and initialized to {-1, -1} for all pairs.
120 void StoreVisitedPickupDeliveryPairsOnRoute(
121  const RoutingDimension& dimension, int vehicle,
122  const std::function<int64_t(int64_t)>& next_accessor,
123  std::vector<int>* visited_pairs,
124  std::vector<std::pair<int64_t, int64_t>>*
125  visited_pickup_delivery_indices_for_pair) {
126  // visited_pickup_delivery_indices_for_pair must be all {-1, -1}.
127  DCHECK_EQ(visited_pickup_delivery_indices_for_pair->size(),
128  dimension.model()->GetPickupAndDeliveryPairs().size());
129  DCHECK(std::all_of(visited_pickup_delivery_indices_for_pair->begin(),
130  visited_pickup_delivery_indices_for_pair->end(),
131  [](std::pair<int64_t, int64_t> p) {
132  return p.first == -1 && p.second == -1;
133  }));
134  visited_pairs->clear();
135  if (!dimension.HasPickupToDeliveryLimits()) {
136  return;
137  }
138  const RoutingModel& model = *dimension.model();
139 
140  int64_t node_index = model.Start(vehicle);
141  while (!model.IsEnd(node_index)) {
142  const std::vector<std::pair<int, int>>& pickup_index_pairs =
143  model.GetPickupIndexPairs(node_index);
144  const std::vector<std::pair<int, int>>& delivery_index_pairs =
145  model.GetDeliveryIndexPairs(node_index);
146  if (!pickup_index_pairs.empty()) {
147  // The current node is a pickup. We verify that it belongs to a single
148  // pickup index pair and that it's not a delivery, and store the index.
149  DCHECK(delivery_index_pairs.empty());
150  DCHECK_EQ(pickup_index_pairs.size(), 1);
151  (*visited_pickup_delivery_indices_for_pair)[pickup_index_pairs[0].first]
152  .first = node_index;
153  visited_pairs->push_back(pickup_index_pairs[0].first);
154  } else if (!delivery_index_pairs.empty()) {
155  // The node is a delivery. We verify that it belongs to a single
156  // delivery pair, and set the limit with its pickup if one has been
157  // visited for this pair.
158  DCHECK_EQ(delivery_index_pairs.size(), 1);
159  const int pair_index = delivery_index_pairs[0].first;
160  std::pair<int64_t, int64_t>& pickup_delivery_index =
161  (*visited_pickup_delivery_indices_for_pair)[pair_index];
162  if (pickup_delivery_index.first < 0) {
163  // This case should not happen, as a delivery must have its pickup
164  // on the route, but we ignore it here.
165  node_index = next_accessor(node_index);
166  continue;
167  }
168  pickup_delivery_index.second = node_index;
169  }
170  node_index = next_accessor(node_index);
171  }
172 }
173 
174 } // namespace
175 
176 // LocalDimensionCumulOptimizer
177 
179  const RoutingDimension* dimension,
180  RoutingSearchParameters::SchedulingSolver solver_type)
181  : optimizer_core_(dimension, /*use_precedence_propagator=*/false) {
182  // Using one solver per vehicle in the hope that if routes don't change this
183  // will be faster.
184  const int vehicles = dimension->model()->vehicles();
185  solver_.resize(vehicles);
186  switch (solver_type) {
187  case RoutingSearchParameters::SCHEDULING_GLOP: {
188  const glop::GlopParameters parameters = GetGlopParametersForLocalLP();
189  for (int vehicle = 0; vehicle < vehicles; ++vehicle) {
190  // TODO(user): Instead of passing false, detect if the relaxation
191  // will always violate the MIPL constraints.
192  solver_[vehicle] =
193  std::make_unique<RoutingGlopWrapper>(false, parameters);
194  }
195  break;
196  }
197  case RoutingSearchParameters::SCHEDULING_CP_SAT: {
198  for (int vehicle = 0; vehicle < vehicles; ++vehicle) {
199  solver_[vehicle] = std::make_unique<RoutingCPSatWrapper>();
200  }
201  break;
202  }
203  default:
204  LOG(DFATAL) << "Unrecognized solver type: " << solver_type;
205  }
206 }
207 
209  int vehicle, const std::function<int64_t(int64_t)>& next_accessor,
210  int64_t* optimal_cost) {
211  return optimizer_core_.OptimizeSingleRoute(vehicle, next_accessor, {},
212  solver_[vehicle].get(), nullptr,
213  nullptr, optimal_cost, nullptr);
214 }
215 
218  int vehicle, const std::function<int64_t(int64_t)>& next_accessor,
219  int64_t* optimal_cost_without_transits) {
220  int64_t cost = 0;
221  int64_t transit_cost = 0;
222  const DimensionSchedulingStatus status = optimizer_core_.OptimizeSingleRoute(
223  vehicle, next_accessor, {}, solver_[vehicle].get(), nullptr, nullptr,
224  &cost, &transit_cost);
226  optimal_cost_without_transits != nullptr) {
227  *optimal_cost_without_transits = CapSub(cost, transit_cost);
228  }
229  return status;
230 }
231 
232 std::vector<DimensionSchedulingStatus> LocalDimensionCumulOptimizer::
234  int vehicle, const std::function<int64_t(int64_t)>& next_accessor,
235  const std::function<int64_t(int64_t, int64_t)>& transit_accessor,
236  const std::vector<RoutingModel::ResourceGroup::Resource>& resources,
237  const std::vector<int>& resource_indices, bool optimize_vehicle_costs,
238  std::vector<int64_t>* optimal_costs_without_transits,
239  std::vector<std::vector<int64_t>>* optimal_cumuls,
240  std::vector<std::vector<int64_t>>* optimal_breaks) {
241  return optimizer_core_.OptimizeSingleRouteWithResources(
242  vehicle, next_accessor, transit_accessor, {}, resources, resource_indices,
243  optimize_vehicle_costs, solver_[vehicle].get(),
244  optimal_costs_without_transits, optimal_cumuls, optimal_breaks);
245 }
246 
248  int vehicle, const std::function<int64_t(int64_t)>& next_accessor,
249  const RoutingModel::RouteDimensionTravelInfo& dimension_travel_info,
250  std::vector<int64_t>* optimal_cumuls,
251  std::vector<int64_t>* optimal_breaks) {
252  return optimizer_core_.OptimizeSingleRoute(
253  vehicle, next_accessor, dimension_travel_info, solver_[vehicle].get(),
254  optimal_cumuls, optimal_breaks, nullptr, nullptr);
255 }
256 
259  int vehicle, const std::function<int64_t(int64_t)>& next_accessor,
260  const RoutingModel::RouteDimensionTravelInfo& dimension_travel_info,
261  std::vector<int64_t>* optimal_cumuls, std::vector<int64_t>* optimal_breaks,
262  int64_t* optimal_cost) {
263  return optimizer_core_.OptimizeSingleRoute(
264  vehicle, next_accessor, dimension_travel_info, solver_[vehicle].get(),
265  optimal_cumuls, optimal_breaks, optimal_cost, nullptr);
266 }
267 
270  int vehicle, const std::function<int64_t(int64_t)>& next_accessor,
271  const RoutingModel::RouteDimensionTravelInfo& dimension_travel_info,
272  const std::vector<int64_t>& solution_cumul_values,
273  const std::vector<int64_t>& solution_break_values, int64_t* solution_cost,
274  int64_t* cost_offset, bool reuse_previous_model_if_possible, bool clear_lp,
275  absl::Duration* solve_duration) {
276  RoutingLinearSolverWrapper* solver = solver_[vehicle].get();
277  return optimizer_core_.ComputeSingleRouteSolutionCost(
278  vehicle, next_accessor, dimension_travel_info, solver,
279  solution_cumul_values, solution_break_values, solution_cost, nullptr,
280  cost_offset, reuse_previous_model_if_possible, clear_lp,
281  /*clear_solution_constraints=*/true, solve_duration);
282 }
283 
286  int vehicle, const std::function<int64_t(int64_t)>& next_accessor,
287  const RoutingModel::RouteDimensionTravelInfo& dimension_travel_info,
289  std::vector<int64_t>* packed_cumuls, std::vector<int64_t>* packed_breaks) {
290  return optimizer_core_.OptimizeAndPackSingleRoute(
291  vehicle, next_accessor, dimension_travel_info, resource,
292  solver_[vehicle].get(), packed_cumuls, packed_breaks);
293 }
294 
295 const int CumulBoundsPropagator::kNoParent = -2;
296 const int CumulBoundsPropagator::kParentToBePropagated = -1;
297 
299  : dimension_(*dimension), num_nodes_(2 * dimension->cumuls().size()) {
300  outgoing_arcs_.resize(num_nodes_);
301  node_in_queue_.resize(num_nodes_, false);
302  tree_parent_node_of_.resize(num_nodes_, kNoParent);
303  propagated_bounds_.resize(num_nodes_);
304  visited_pickup_delivery_indices_for_pair_.resize(
305  dimension->model()->GetPickupAndDeliveryPairs().size(), {-1, -1});
306 }
307 
308 void CumulBoundsPropagator::AddArcs(int first_index, int second_index,
309  int64_t offset) {
310  // Add arc first_index + offset <= second_index
311  outgoing_arcs_[PositiveNode(first_index)].push_back(
312  {PositiveNode(second_index), offset});
313  AddNodeToQueue(PositiveNode(first_index));
314  // Add arc -second_index + transit <= -first_index
315  outgoing_arcs_[NegativeNode(second_index)].push_back(
316  {NegativeNode(first_index), offset});
317  AddNodeToQueue(NegativeNode(second_index));
318 }
319 
320 bool CumulBoundsPropagator::InitializeArcsAndBounds(
321  const std::function<int64_t(int64_t)>& next_accessor, int64_t cumul_offset,
322  const std::vector<RoutingModel::RouteDimensionTravelInfo>*
323  dimension_travel_info_per_route) {
324  propagated_bounds_.assign(num_nodes_, std::numeric_limits<int64_t>::min());
325 
326  for (std::vector<ArcInfo>& arcs : outgoing_arcs_) {
327  arcs.clear();
328  }
329 
330  RoutingModel* const model = dimension_.model();
331  std::vector<int64_t>& lower_bounds = propagated_bounds_;
332 
333  for (int vehicle = 0; vehicle < model->vehicles(); vehicle++) {
334  const std::function<int64_t(int64_t, int64_t)>& transit_accessor =
335  dimension_.transit_evaluator(vehicle);
336 
337  int node = model->Start(vehicle);
338  int index_on_route = 0;
339  while (true) {
340  int64_t cumul_lb, cumul_ub;
341  if (!GetCumulBoundsWithOffset(dimension_, node, cumul_offset, &cumul_lb,
342  &cumul_ub)) {
343  return false;
344  }
345  lower_bounds[PositiveNode(node)] = cumul_lb;
346  if (cumul_ub < std::numeric_limits<int64_t>::max()) {
347  lower_bounds[NegativeNode(node)] = -cumul_ub;
348  }
349 
350  if (model->IsEnd(node)) {
351  break;
352  }
353 
354  const int next = next_accessor(node);
355  int64_t transit = transit_accessor(node, next);
356  if (dimension_travel_info_per_route != nullptr &&
357  !dimension_travel_info_per_route->empty()) {
358  const RoutingModel::RouteDimensionTravelInfo::TransitionInfo&
359  transition_info = (*dimension_travel_info_per_route)[vehicle]
360  .transition_info[index_on_route];
361  transit = transition_info.compressed_travel_value_lower_bound +
362  transition_info.pre_travel_transit_value +
363  transition_info.post_travel_transit_value;
364  ++index_on_route;
365  }
366  const IntVar& slack_var = *dimension_.SlackVar(node);
367  // node + transit + slack_var == next
368  // Add arcs for node + transit + slack_min <= next
369  AddArcs(node, next, CapAdd(transit, slack_var.Min()));
370  if (slack_var.Max() < std::numeric_limits<int64_t>::max()) {
371  // Add arcs for node + transit + slack_max >= next.
372  AddArcs(next, node, CapSub(-slack_var.Max(), transit));
373  }
374 
375  node = next;
376  }
377 
378  // Add vehicle span upper bound: end - span_ub <= start.
379  const int64_t span_ub = dimension_.GetSpanUpperBoundForVehicle(vehicle);
380  if (span_ub < std::numeric_limits<int64_t>::max()) {
381  AddArcs(model->End(vehicle), model->Start(vehicle), -span_ub);
382  }
383 
384  // Set pickup/delivery limits on route.
385  std::vector<int> visited_pairs;
386  StoreVisitedPickupDeliveryPairsOnRoute(
387  dimension_, vehicle, next_accessor, &visited_pairs,
388  &visited_pickup_delivery_indices_for_pair_);
389  for (int pair_index : visited_pairs) {
390  const int64_t pickup_index =
391  visited_pickup_delivery_indices_for_pair_[pair_index].first;
392  const int64_t delivery_index =
393  visited_pickup_delivery_indices_for_pair_[pair_index].second;
394  visited_pickup_delivery_indices_for_pair_[pair_index] = {-1, -1};
395 
396  DCHECK_GE(pickup_index, 0);
397  if (delivery_index < 0) {
398  // We didn't encounter a delivery for this pickup.
399  continue;
400  }
401 
402  const int64_t limit = dimension_.GetPickupToDeliveryLimitForPair(
403  pair_index, model->GetPickupIndexPairs(pickup_index)[0].second,
404  model->GetDeliveryIndexPairs(delivery_index)[0].second);
405  if (limit < std::numeric_limits<int64_t>::max()) {
406  // delivery_cumul - limit <= pickup_cumul.
407  AddArcs(delivery_index, pickup_index, -limit);
408  }
409  }
410  }
411 
412  for (const RoutingDimension::NodePrecedence& precedence :
413  dimension_.GetNodePrecedences()) {
414  const int first_index = precedence.first_node;
415  const int second_index = precedence.second_node;
416  if (lower_bounds[PositiveNode(first_index)] ==
418  lower_bounds[PositiveNode(second_index)] ==
420  // One of the nodes is unperformed, so the precedence rule doesn't apply.
421  continue;
422  }
423  AddArcs(first_index, second_index, precedence.offset);
424  }
425 
426  return true;
427 }
428 
429 bool CumulBoundsPropagator::UpdateCurrentLowerBoundOfNode(int node,
430  int64_t new_lb,
431  int64_t offset) {
432  const int cumul_var_index = node / 2;
433 
434  if (node == PositiveNode(cumul_var_index)) {
435  // new_lb is a lower bound of the cumul of variable 'cumul_var_index'.
436  propagated_bounds_[node] = GetFirstPossibleValueForCumulWithOffset(
437  dimension_, cumul_var_index, new_lb, offset);
438  } else {
439  // -new_lb is an upper bound of the cumul of variable 'cumul_var_index'.
440  const int64_t new_ub = CapSub(0, new_lb);
441  propagated_bounds_[node] =
442  CapSub(0, GetLastPossibleValueForCumulWithOffset(
443  dimension_, cumul_var_index, new_ub, offset));
444  }
445 
446  // Test that the lower/upper bounds do not cross each other.
447  const int64_t cumul_lower_bound =
448  propagated_bounds_[PositiveNode(cumul_var_index)];
449 
450  const int64_t negated_cumul_upper_bound =
451  propagated_bounds_[NegativeNode(cumul_var_index)];
452 
453  return CapAdd(negated_cumul_upper_bound, cumul_lower_bound) <= 0;
454 }
455 
456 bool CumulBoundsPropagator::DisassembleSubtree(int source, int target) {
457  tmp_dfs_stack_.clear();
458  tmp_dfs_stack_.push_back(source);
459  while (!tmp_dfs_stack_.empty()) {
460  const int tail = tmp_dfs_stack_.back();
461  tmp_dfs_stack_.pop_back();
462  for (const ArcInfo& arc : outgoing_arcs_[tail]) {
463  const int child_node = arc.head;
464  if (tree_parent_node_of_[child_node] != tail) continue;
465  if (child_node == target) return false;
466  tree_parent_node_of_[child_node] = kParentToBePropagated;
467  tmp_dfs_stack_.push_back(child_node);
468  }
469  }
470  return true;
471 }
472 
474  const std::function<int64_t(int64_t)>& next_accessor, int64_t cumul_offset,
475  const std::vector<RoutingModel::RouteDimensionTravelInfo>*
476  dimension_travel_info_per_route) {
477  tree_parent_node_of_.assign(num_nodes_, kNoParent);
478  DCHECK(std::none_of(node_in_queue_.begin(), node_in_queue_.end(),
479  [](bool b) { return b; }));
480  DCHECK(bf_queue_.empty());
481 
482  if (!InitializeArcsAndBounds(next_accessor, cumul_offset,
483  dimension_travel_info_per_route)) {
484  return CleanupAndReturnFalse();
485  }
486 
487  std::vector<int64_t>& current_lb = propagated_bounds_;
488 
489  // Bellman-Ford-Tarjan algorithm.
490  while (!bf_queue_.empty()) {
491  const int node = bf_queue_.front();
492  bf_queue_.pop_front();
493  node_in_queue_[node] = false;
494 
495  if (tree_parent_node_of_[node] == kParentToBePropagated) {
496  // The parent of this node is still in the queue, so no need to process
497  // node now, since it will be re-enqued when its parent is processed.
498  continue;
499  }
500 
501  const int64_t lower_bound = current_lb[node];
502  for (const ArcInfo& arc : outgoing_arcs_[node]) {
503  // NOTE: kint64min as a lower bound means no lower bound at all, so we
504  // don't use this value to propagate.
505  const int64_t induced_lb =
508  : CapAdd(lower_bound, arc.offset);
509 
510  const int head_node = arc.head;
511  if (induced_lb <= current_lb[head_node]) {
512  // No update necessary for the head_node, continue to next children of
513  // node.
514  continue;
515  }
516  if (!UpdateCurrentLowerBoundOfNode(head_node, induced_lb, cumul_offset) ||
517  !DisassembleSubtree(head_node, node)) {
518  // The new lower bound is infeasible, or a positive cycle was detected
519  // in the precedence graph by DisassembleSubtree().
520  return CleanupAndReturnFalse();
521  }
522 
523  tree_parent_node_of_[head_node] = node;
524  AddNodeToQueue(head_node);
525  }
526  }
527  return true;
528 }
529 
531  const RoutingDimension* dimension, bool use_precedence_propagator)
532  : dimension_(dimension),
533  visited_pickup_delivery_indices_for_pair_(
534  dimension->model()->GetPickupAndDeliveryPairs().size(), {-1, -1}) {
535  if (use_precedence_propagator) {
536  propagator_ = std::make_unique<CumulBoundsPropagator>(dimension);
537  }
538  const RoutingModel& model = *dimension_->model();
539  if (dimension_->HasBreakConstraints()) {
540  // Initialize vehicle_to_first_index_ so the variables of the breaks of
541  // vehicle v are stored from vehicle_to_first_index_[v] to
542  // vehicle_to_first_index_[v+1] - 1.
543  const int num_vehicles = model.vehicles();
544  vehicle_to_all_break_variables_offset_.reserve(num_vehicles);
545  int num_break_vars = 0;
546  for (int vehicle = 0; vehicle < num_vehicles; ++vehicle) {
547  vehicle_to_all_break_variables_offset_.push_back(num_break_vars);
548  const auto& intervals = dimension_->GetBreakIntervalsOfVehicle(vehicle);
549  num_break_vars += 2 * intervals.size(); // 2 variables per break.
550  }
551  all_break_variables_.resize(num_break_vars, -1);
552  }
553  if (!model.GetDimensionResourceGroupIndices(dimension_).empty()) {
554  resource_group_to_resource_to_vehicle_assignment_variables_.resize(
555  model.GetResourceGroups().size());
556  }
557 }
558 
559 bool DimensionCumulOptimizerCore::InitSingleRoute(
560  int vehicle, const std::function<int64_t(int64_t)>& next_accessor,
561  const RouteDimensionTravelInfo& dimension_travel_info,
562  RoutingLinearSolverWrapper* solver, std::vector<int64_t>* cumul_values,
563  int64_t* cost, int64_t* transit_cost, int64_t* cumul_offset,
564  int64_t* const cost_offset) {
565  InitOptimizer(solver);
566  // Make sure SetRouteCumulConstraints will properly set the cumul bounds by
567  // looking at this route only.
568  DCHECK_EQ(propagator_.get(), nullptr);
569 
570  RoutingModel* model = dimension_->model();
571  const bool optimize_vehicle_costs =
572  (cumul_values != nullptr || cost != nullptr) &&
573  (!model->IsEnd(next_accessor(model->Start(vehicle))) ||
574  model->IsVehicleUsedWhenEmpty(vehicle));
575  *cumul_offset = dimension_->GetLocalOptimizerOffsetForVehicle(vehicle);
576  if (!SetRouteCumulConstraints(
577  vehicle, next_accessor, dimension_->transit_evaluator(vehicle),
578  dimension_travel_info, *cumul_offset, optimize_vehicle_costs, solver,
579  transit_cost, cost_offset)) {
580  return false;
581  }
582  if (model->CheckLimit()) {
583  return false;
584  }
585  return true;
586 }
587 
590  int vehicle, const std::function<int64_t(int64_t)>& next_accessor,
591  const RouteDimensionTravelInfo& dimension_travel_info,
593  const std::vector<int64_t>& solution_cumul_values,
594  const std::vector<int64_t>& solution_break_values, int64_t* cost,
595  int64_t* transit_cost, int64_t* cost_offset,
596  bool reuse_previous_model_if_possible, bool clear_lp,
597  bool clear_solution_constraints, absl::Duration* const solve_duration) {
598  absl::Duration solve_duration_value;
599  int64_t cost_offset_value;
600  if (!reuse_previous_model_if_possible || solver->ModelIsEmpty()) {
601  int64_t cumul_offset;
602  std::vector<int64_t> cumul_values;
603  if (!InitSingleRoute(vehicle, next_accessor, dimension_travel_info, solver,
604  &cumul_values, cost, transit_cost, &cumul_offset,
605  &cost_offset_value)) {
607  }
608  solve_duration_value = dimension_->model()->RemainingTime();
609  if (solve_duration != nullptr) *solve_duration = solve_duration_value;
610  if (cost_offset != nullptr) *cost_offset = cost_offset_value;
611  } else {
612  CHECK(cost_offset != nullptr)
613  << "Cannot reuse model without the cost_offset";
614  cost_offset_value = *cost_offset;
615  CHECK(solve_duration != nullptr)
616  << "Cannot reuse model without the solve_duration";
617  solve_duration_value = *solve_duration;
618  }
619 
620  // Constrains the cumuls.
621  DCHECK_EQ(solution_cumul_values.size(),
622  current_route_cumul_variables_.size());
623  for (int i = 0; i < current_route_cumul_variables_.size(); ++i) {
624  if (solution_cumul_values[i] < current_route_min_cumuls_[i] ||
625  solution_cumul_values[i] > current_route_max_cumuls_[i]) {
627  }
628  solver->SetVariableBounds(current_route_cumul_variables_[i],
629  /*lower_bound=*/solution_cumul_values[i],
630  /*upper_bound=*/solution_cumul_values[i]);
631  }
632 
633  // Constrains the breaks.
634  DCHECK_EQ(solution_break_values.size(),
635  current_route_break_variables_.size());
636  std::vector<int64_t> current_route_min_breaks(
637  current_route_break_variables_.size());
638  std::vector<int64_t> current_route_max_breaks(
639  current_route_break_variables_.size());
640  for (int i = 0; i < current_route_break_variables_.size(); ++i) {
641  current_route_min_breaks[i] =
642  solver->GetVariableLowerBound(current_route_break_variables_[i]);
643  current_route_max_breaks[i] =
644  solver->GetVariableUpperBound(current_route_break_variables_[i]);
645  solver->SetVariableBounds(current_route_break_variables_[i],
646  /*lower_bound=*/solution_break_values[i],
647  /*upper_bound=*/solution_break_values[i]);
648  }
649 
650  const DimensionSchedulingStatus status = solver->Solve(solve_duration_value);
652  solver->Clear();
653  return status;
654  }
655 
656  if (cost != nullptr) {
657  *cost = CapAdd(cost_offset_value, solver->GetObjectiveValue());
658  }
659 
660  if (clear_lp) {
661  solver->Clear();
662  } else if (clear_solution_constraints) {
663  for (int i = 0; i < current_route_cumul_variables_.size(); ++i) {
664  solver->SetVariableBounds(current_route_cumul_variables_[i],
665  /*lower_bound=*/current_route_min_cumuls_[i],
666  /*upper_bound=*/current_route_max_cumuls_[i]);
667  }
668  for (int i = 0; i < current_route_break_variables_.size(); ++i) {
669  solver->SetVariableBounds(current_route_break_variables_[i],
670  /*lower_bound=*/current_route_min_breaks[i],
671  /*upper_bound=*/current_route_max_breaks[i]);
672  }
673  }
674  return status;
675 }
676 
678  int vehicle, const std::function<int64_t(int64_t)>& next_accessor,
679  const RouteDimensionTravelInfo& dimension_travel_info,
680  RoutingLinearSolverWrapper* solver, std::vector<int64_t>* cumul_values,
681  std::vector<int64_t>* break_values, int64_t* cost, int64_t* transit_cost,
682  bool clear_lp) {
683  int64_t cumul_offset, cost_offset;
684  if (!InitSingleRoute(vehicle, next_accessor, dimension_travel_info, solver,
685  cumul_values, cost, transit_cost, &cumul_offset,
686  &cost_offset)) {
688  }
690  solver->Solve(dimension()->model()->RemainingTime());
692  solver->Clear();
693  return status;
694  }
695 
696  SetValuesFromLP(current_route_cumul_variables_, cumul_offset, solver,
697  cumul_values);
698  SetValuesFromLP(current_route_break_variables_, cumul_offset, solver,
699  break_values);
700  if (cost != nullptr) {
701  *cost = CapAdd(cost_offset, solver->GetObjectiveValue());
702  }
703 
704  if (clear_lp) {
705  solver->Clear();
706  }
707  return status;
708 }
709 
710 namespace {
711 
712 using ResourceGroup = RoutingModel::ResourceGroup;
713 
714 bool GetDomainOffsetBounds(const Domain& domain, int64_t offset,
716  const int64_t lower_bound =
717  std::max<int64_t>(CapSub(domain.Min(), offset), 0);
718  const int64_t upper_bound =
721  : CapSub(domain.Max(), offset);
722  if (lower_bound > upper_bound) return false;
723 
725  return true;
726 }
727 
728 bool GetIntervalIntersectionWithOffsetDomain(const ClosedInterval& interval,
729  const Domain& domain,
730  int64_t offset,
731  ClosedInterval* intersection) {
732  ClosedInterval domain_bounds;
733  if (!GetDomainOffsetBounds(domain, offset, &domain_bounds)) {
734  return false;
735  }
736  const int64_t intersection_lb = std::max(interval.start, domain_bounds.start);
737  const int64_t intersection_ub = std::min(interval.end, domain_bounds.end);
738  if (intersection_lb > intersection_ub) return false;
739 
740  *intersection = ClosedInterval(intersection_lb, intersection_ub);
741  return true;
742 }
743 
744 ClosedInterval GetVariableBounds(int index,
745  const RoutingLinearSolverWrapper& solver) {
746  return ClosedInterval(solver.GetVariableLowerBound(index),
747  solver.GetVariableUpperBound(index));
748 }
749 
750 bool TightenStartEndVariableBoundsWithResource(
751  const RoutingDimension& dimension, const ResourceGroup::Resource& resource,
752  const ClosedInterval& start_bounds, int start_index,
753  const ClosedInterval& end_bounds, int end_index, int64_t offset,
754  RoutingLinearSolverWrapper* solver) {
755  const ResourceGroup::Attributes& attributes =
756  resource.GetDimensionAttributes(&dimension);
757  ClosedInterval new_start_bounds;
758  ClosedInterval new_end_bounds;
759  return GetIntervalIntersectionWithOffsetDomain(start_bounds,
760  attributes.start_domain(),
761  offset, &new_start_bounds) &&
762  solver->SetVariableBounds(start_index, new_start_bounds.start,
763  new_start_bounds.end) &&
764  GetIntervalIntersectionWithOffsetDomain(
765  end_bounds, attributes.end_domain(), offset, &new_end_bounds) &&
766  solver->SetVariableBounds(end_index, new_end_bounds.start,
767  new_end_bounds.end);
768 }
769 
770 } // namespace
771 
772 std::vector<DimensionSchedulingStatus>
774  int vehicle, const std::function<int64_t(int64_t)>& next_accessor,
775  const std::function<int64_t(int64_t, int64_t)>& transit_accessor,
776  const RouteDimensionTravelInfo& dimension_travel_info,
777  const std::vector<RoutingModel::ResourceGroup::Resource>& resources,
778  const std::vector<int>& resource_indices, bool optimize_vehicle_costs,
780  std::vector<int64_t>* costs_without_transits,
781  std::vector<std::vector<int64_t>>* cumul_values,
782  std::vector<std::vector<int64_t>>* break_values, bool clear_lp) {
783  if (resource_indices.empty()) return {};
784 
785  InitOptimizer(solver);
786  // Make sure SetRouteCumulConstraints will properly set the cumul bounds by
787  // looking at this route only.
788  DCHECK_EQ(propagator_.get(), nullptr);
789  DCHECK_NE(costs_without_transits, nullptr);
790  costs_without_transits->clear();
791 
792  RoutingModel* const model = dimension()->model();
793  if (model->IsEnd(next_accessor(model->Start(vehicle))) &&
794  !model->IsVehicleUsedWhenEmpty(vehicle)) {
795  // An unused empty vehicle doesn't require resources.
796  return {};
797  }
798 
799  const int64_t cumul_offset =
800  dimension_->GetLocalOptimizerOffsetForVehicle(vehicle);
801  int64_t cost_offset = 0;
802  int64_t transit_cost = 0;
803  if (!SetRouteCumulConstraints(vehicle, next_accessor, transit_accessor,
804  dimension_travel_info, cumul_offset,
805  optimize_vehicle_costs, solver, &transit_cost,
806  &cost_offset)) {
808  }
809 
810  costs_without_transits->assign(resource_indices.size(), -1);
811  if (cumul_values != nullptr) {
812  cumul_values->assign(resource_indices.size(), {});
813  }
814  if (break_values != nullptr) {
815  break_values->assign(resource_indices.size(), {});
816  }
817 
818  DCHECK_GE(current_route_cumul_variables_.size(), 2);
819 
820  const int start_cumul = current_route_cumul_variables_[0];
821  const ClosedInterval start_bounds = GetVariableBounds(start_cumul, *solver);
822  const int end_cumul = current_route_cumul_variables_.back();
823  const ClosedInterval end_bounds = GetVariableBounds(end_cumul, *solver);
824  std::vector<DimensionSchedulingStatus> statuses;
825  for (int i = 0; i < resource_indices.size(); i++) {
826  if (model->CheckLimit()) {
827  // The model's deadline has been reached, stop.
828  costs_without_transits->clear();
829  if (cumul_values != nullptr) {
830  cumul_values->clear();
831  }
832  if (break_values != nullptr) {
833  break_values->clear();
834  }
835  return {};
836  }
837  if (!TightenStartEndVariableBoundsWithResource(
838  *dimension_, resources[resource_indices[i]], start_bounds,
839  start_cumul, end_bounds, end_cumul, cumul_offset, solver)) {
840  // The resource attributes don't match this vehicle.
841  statuses.push_back(DimensionSchedulingStatus::INFEASIBLE);
842  continue;
843  }
844 
845  statuses.push_back(solver->Solve(model->RemainingTime()));
846  if (statuses.back() == DimensionSchedulingStatus::INFEASIBLE) {
847  continue;
848  }
849  costs_without_transits->at(i) =
850  optimize_vehicle_costs
851  ? CapSub(CapAdd(cost_offset, solver->GetObjectiveValue()),
852  transit_cost)
853  : 0;
854 
855  if (cumul_values != nullptr) {
856  SetValuesFromLP(current_route_cumul_variables_, cumul_offset, solver,
857  &cumul_values->at(i));
858  }
859  if (break_values != nullptr) {
860  SetValuesFromLP(current_route_break_variables_, cumul_offset, solver,
861  &break_values->at(i));
862  }
863  }
864 
865  if (clear_lp) {
866  solver->Clear();
867  }
868  return statuses;
869 }
870 
872  const std::function<int64_t(int64_t)>& next_accessor,
873  const std::vector<RouteDimensionTravelInfo>&
874  dimension_travel_info_per_route,
875  RoutingLinearSolverWrapper* solver, std::vector<int64_t>* cumul_values,
876  std::vector<int64_t>* break_values,
877  std::vector<std::vector<int>>* resource_indices_per_group, int64_t* cost,
878  int64_t* transit_cost, bool clear_lp) {
879  InitOptimizer(solver);
880 
881  // If both "cumul_values" and "cost" parameters are null, we don't try to
882  // optimize the cost and stop at the first feasible solution.
883  const bool optimize_costs = (cumul_values != nullptr) || (cost != nullptr);
884  bool has_vehicles_being_optimized = false;
885 
886  const int64_t cumul_offset = dimension_->GetGlobalOptimizerOffset();
887 
888  if (propagator_ != nullptr &&
889  !propagator_->PropagateCumulBounds(next_accessor, cumul_offset,
890  &dimension_travel_info_per_route)) {
892  }
893 
894  int64_t total_transit_cost = 0;
895  int64_t total_cost_offset = 0;
896  const RoutingModel* model = dimension()->model();
897  for (int vehicle = 0; vehicle < model->vehicles(); vehicle++) {
898  int64_t route_transit_cost = 0;
899  int64_t route_cost_offset = 0;
900  const bool vehicle_is_used =
901  !model->IsEnd(next_accessor(model->Start(vehicle))) ||
902  model->IsVehicleUsedWhenEmpty(vehicle);
903  const bool optimize_vehicle_costs = optimize_costs && vehicle_is_used;
904  const RouteDimensionTravelInfo& dimension_travel_info =
905  dimension_travel_info_per_route.empty()
907  : dimension_travel_info_per_route[vehicle];
908  if (!SetRouteCumulConstraints(
909  vehicle, next_accessor, dimension_->transit_evaluator(vehicle),
910  dimension_travel_info, cumul_offset, optimize_vehicle_costs, solver,
911  &route_transit_cost, &route_cost_offset)) {
913  }
914  total_transit_cost = CapAdd(total_transit_cost, route_transit_cost);
915  total_cost_offset = CapAdd(total_cost_offset, route_cost_offset);
916  has_vehicles_being_optimized |= optimize_vehicle_costs;
917  }
918  if (transit_cost != nullptr) {
919  *transit_cost = total_transit_cost;
920  }
921 
922  if (!SetGlobalConstraints(next_accessor, cumul_offset,
923  has_vehicles_being_optimized, solver)) {
925  }
926 
928  solver->Solve(model->RemainingTime());
930  solver->Clear();
931  return status;
932  }
933 
934  // TODO(user): In case the status is RELAXED_OPTIMAL_ONLY, check we can
935  // safely avoid filling variable and cost values.
936  SetValuesFromLP(index_to_cumul_variable_, cumul_offset, solver, cumul_values);
937  SetValuesFromLP(all_break_variables_, cumul_offset, solver, break_values);
938  SetResourceIndices(solver, resource_indices_per_group);
939 
940  if (cost != nullptr) {
941  *cost = CapAdd(solver->GetObjectiveValue(), total_cost_offset);
942  }
943 
944  if (clear_lp) {
945  solver->Clear();
946  }
947  return status;
948 }
949 
951  const std::function<int64_t(int64_t)>& next_accessor,
952  const std::vector<RouteDimensionTravelInfo>&
953  dimension_travel_info_per_route,
954  RoutingLinearSolverWrapper* solver, std::vector<int64_t>* cumul_values,
955  std::vector<int64_t>* break_values,
956  std::vector<std::vector<int>>* resource_indices_per_group) {
957  // Note: We pass a non-nullptr cost to the Optimize() method so the costs
958  // are optimized by the solver.
959  int64_t cost = 0;
960  const glop::GlopParameters original_params = GetGlopParametersForGlobalLP();
961  glop::GlopParameters packing_parameters;
962  if (!solver->IsCPSATSolver()) {
963  packing_parameters = original_params;
964  packing_parameters.set_use_dual_simplex(false);
965  packing_parameters.set_use_preprocessing(true);
966  solver->SetParameters(packing_parameters.SerializeAsString());
967  }
969  if (Optimize(next_accessor, dimension_travel_info_per_route, solver,
970  /*cumul_values=*/nullptr, /*break_values=*/nullptr,
971  /*resource_indices_per_group=*/nullptr, &cost,
972  /*transit_cost=*/nullptr,
973  /*clear_lp=*/false) == DimensionSchedulingStatus::INFEASIBLE) {
975  }
977  std::vector<int> vehicles(dimension()->model()->vehicles());
978  std::iota(vehicles.begin(), vehicles.end(), 0);
979  // Subtle: Even if the status was RELAXED_OPTIMAL_ONLY we try to pack just
980  // in case packing manages to make the solution completely feasible.
981  status = PackRoutes(vehicles, solver, packing_parameters);
982  }
983  if (!solver->IsCPSATSolver()) {
984  solver->SetParameters(original_params.SerializeAsString());
985  }
987  return status;
988  }
989  // TODO(user): In case the status is RELAXED_OPTIMAL_ONLY, check we can
990  // safely avoid filling variable values.
991  const int64_t global_offset = dimension_->GetGlobalOptimizerOffset();
992  SetValuesFromLP(index_to_cumul_variable_, global_offset, solver,
993  cumul_values);
994  SetValuesFromLP(all_break_variables_, global_offset, solver, break_values);
995  SetResourceIndices(solver, resource_indices_per_group);
996  solver->Clear();
997  return status;
998 }
999 
1002  int vehicle, const std::function<int64_t(int64_t)>& next_accessor,
1003  const RouteDimensionTravelInfo& dimension_travel_info,
1004  const RoutingModel::ResourceGroup::Resource* resource,
1005  RoutingLinearSolverWrapper* solver, std::vector<int64_t>* cumul_values,
1006  std::vector<int64_t>* break_values) {
1007  const glop::GlopParameters original_params = GetGlopParametersForLocalLP();
1008  glop::GlopParameters packing_parameters;
1009  if (!solver->IsCPSATSolver()) {
1010  packing_parameters = original_params;
1011  packing_parameters.set_use_dual_simplex(false);
1012  packing_parameters.set_use_preprocessing(true);
1013  solver->SetParameters(packing_parameters.SerializeAsString());
1014  }
1016  if (resource == nullptr) {
1017  // Note: We pass a non-nullptr cost to the OptimizeSingleRoute() method so
1018  // the costs are optimized by the LP.
1019  int64_t cost = 0;
1020  if (OptimizeSingleRoute(vehicle, next_accessor, dimension_travel_info,
1021  solver, /*cumul_values=*/nullptr,
1022  /*break_values=*/nullptr, &cost,
1023  /*transit_cost=*/nullptr, /*clear_lp=*/false) ==
1026  }
1027  } else {
1028  std::vector<int64_t> costs_without_transits;
1029  const std::vector<DimensionSchedulingStatus> statuses =
1031  vehicle, next_accessor, dimension_->transit_evaluator(vehicle),
1032  dimension_travel_info, {*resource}, {0},
1033  /*optimize_vehicle_costs=*/true, solver, &costs_without_transits,
1034  /*cumul_values=*/nullptr,
1035  /*break_values=*/nullptr, /*clear_lp=*/false);
1036  if (dimension_->model()->CheckLimit()) {
1038  } else {
1039  DCHECK_EQ(statuses.size(), 1);
1040  status = statuses[0];
1041  }
1042  }
1043 
1045  status = PackRoutes({vehicle}, solver, packing_parameters);
1046  }
1047  if (!solver->IsCPSATSolver()) {
1048  solver->SetParameters(original_params.SerializeAsString());
1049  }
1050 
1053  }
1054  const int64_t local_offset =
1055  dimension_->GetLocalOptimizerOffsetForVehicle(vehicle);
1056  SetValuesFromLP(current_route_cumul_variables_, local_offset, solver,
1057  cumul_values);
1058  SetValuesFromLP(current_route_break_variables_, local_offset, solver,
1059  break_values);
1060  solver->Clear();
1061  return status;
1062 }
1063 
1064 DimensionSchedulingStatus DimensionCumulOptimizerCore::PackRoutes(
1065  std::vector<int> vehicles, RoutingLinearSolverWrapper* solver,
1066  const glop::GlopParameters& packing_parameters) {
1067  const RoutingModel* model = dimension_->model();
1068 
1069  // NOTE(user): Given our constraint matrix, our problem *should* always
1070  // have an integer optimal solution, in which case we can round to the nearest
1071  // integer both for the objective constraint bound (returned by
1072  // GetObjectiveValue()) and the end cumul variable bound after minimizing
1073  // (see b/154381899 showcasing an example where std::ceil leads to an
1074  // "imperfect" packing due to rounding precision errors).
1075  // If this DCHECK ever fails, it can be removed but the code below should be
1076  // adapted to have a 2-phase approach, solving once with the rounded value as
1077  // bound and if this fails, solve again using std::ceil.
1078  DCHECK(solver->SolutionIsInteger());
1079 
1080  // Minimize the route end times without increasing the cost.
1081  solver->AddObjectiveConstraint();
1082  solver->ClearObjective();
1083  for (int vehicle : vehicles) {
1084  solver->SetObjectiveCoefficient(
1085  index_to_cumul_variable_[model->End(vehicle)], 1);
1086  }
1087 
1088  glop::GlopParameters current_params;
1089  const auto retry_solving = [&current_params, model, solver]() {
1090  // NOTE: To bypass some cases of false negatives due to imprecisions, we try
1091  // running Glop with a different use_dual_simplex parameter when running
1092  // into an infeasible status.
1093  current_params.set_use_dual_simplex(!current_params.use_dual_simplex());
1094  solver->SetParameters(current_params.SerializeAsString());
1095  return solver->Solve(model->RemainingTime());
1096  };
1097  if (solver->Solve(model->RemainingTime()) ==
1099  if (solver->IsCPSATSolver()) {
1101  }
1102 
1103  current_params = packing_parameters;
1104  if (retry_solving() == DimensionSchedulingStatus::INFEASIBLE) {
1106  }
1107  }
1108 
1109  // Maximize the route start times without increasing the cost or the route end
1110  // times.
1111  solver->ClearObjective();
1112  for (int vehicle : vehicles) {
1113  const int end_cumul_var = index_to_cumul_variable_[model->End(vehicle)];
1114  // end_cumul_var <= solver.GetValue(end_cumul_var)
1115  solver->SetVariableBounds(
1116  end_cumul_var, solver->GetVariableLowerBound(end_cumul_var),
1117  MathUtil::FastInt64Round(solver->GetValue(end_cumul_var)));
1118 
1119  // Maximize the starts of the routes.
1120  solver->SetObjectiveCoefficient(
1121  index_to_cumul_variable_[model->Start(vehicle)], -1);
1122  }
1123 
1124  DimensionSchedulingStatus status = solver->Solve(model->RemainingTime());
1125  if (!solver->IsCPSATSolver() &&
1127  status = retry_solving();
1128  }
1129  return status;
1130 }
1131 
1132 #define SET_DEBUG_VARIABLE_NAME(solver, var, name) \
1133  do { \
1134  if (DEBUG_MODE) { \
1135  solver->SetVariableName(var, name); \
1136  } \
1137  } while (false)
1138 
1139 void DimensionCumulOptimizerCore::InitOptimizer(
1140  RoutingLinearSolverWrapper* solver) {
1141  solver->Clear();
1142  index_to_cumul_variable_.assign(dimension_->cumuls().size(), -1);
1143  max_end_cumul_ = solver->CreateNewPositiveVariable();
1144  SET_DEBUG_VARIABLE_NAME(solver, max_end_cumul_, "max_end_cumul");
1145  min_start_cumul_ = solver->CreateNewPositiveVariable();
1146  SET_DEBUG_VARIABLE_NAME(solver, min_start_cumul_, "min_start_cumul");
1147 }
1148 
1149 bool DimensionCumulOptimizerCore::ExtractRouteCumulBounds(
1150  const std::vector<int64_t>& route, int64_t cumul_offset) {
1151  const int route_size = route.size();
1152  current_route_min_cumuls_.resize(route_size);
1153  current_route_max_cumuls_.resize(route_size);
1154 
1155  // Extract cumul min/max and fixed transits from CP.
1156  for (int pos = 0; pos < route_size; ++pos) {
1157  if (!GetCumulBoundsWithOffset(*dimension_, route[pos], cumul_offset,
1158  &current_route_min_cumuls_[pos],
1159  &current_route_max_cumuls_[pos])) {
1160  return false;
1161  }
1162  }
1163  return true;
1164 }
1165 
1166 bool DimensionCumulOptimizerCore::TightenRouteCumulBounds(
1167  const std::vector<int64_t>& route, const std::vector<int64_t>& min_transits,
1168  int64_t cumul_offset) {
1169  const int route_size = route.size();
1170  if (propagator_ != nullptr) {
1171  for (int pos = 0; pos < route_size; pos++) {
1172  const int64_t node = route[pos];
1173  current_route_min_cumuls_[pos] = propagator_->CumulMin(node);
1174  DCHECK_GE(current_route_min_cumuls_[pos], 0);
1175  current_route_max_cumuls_[pos] = propagator_->CumulMax(node);
1176  DCHECK_GE(current_route_max_cumuls_[pos], current_route_min_cumuls_[pos]);
1177  }
1178  return true;
1179  }
1180 
1181  // Refine cumul bounds using
1182  // cumul[i+1] >= cumul[i] + fixed_transit[i] + slack[i].
1183  for (int pos = 1; pos < route_size; ++pos) {
1184  const int64_t slack_min = dimension_->SlackVar(route[pos - 1])->Min();
1185  current_route_min_cumuls_[pos] = std::max(
1186  current_route_min_cumuls_[pos],
1187  CapAdd(
1188  CapAdd(current_route_min_cumuls_[pos - 1], min_transits[pos - 1]),
1189  slack_min));
1190  current_route_min_cumuls_[pos] = GetFirstPossibleValueForCumulWithOffset(
1191  *dimension_, route[pos], current_route_min_cumuls_[pos], cumul_offset);
1192  if (current_route_min_cumuls_[pos] > current_route_max_cumuls_[pos]) {
1193  return false;
1194  }
1195  }
1196 
1197  for (int pos = route_size - 2; pos >= 0; --pos) {
1198  // If cumul_max[pos+1] is kint64max, it will be translated to
1199  // double +infinity, so it must not constrain cumul_max[pos].
1200  if (current_route_max_cumuls_[pos + 1] <
1202  const int64_t slack_min = dimension_->SlackVar(route[pos])->Min();
1203  current_route_max_cumuls_[pos] = std::min(
1204  current_route_max_cumuls_[pos],
1205  CapSub(CapSub(current_route_max_cumuls_[pos + 1], min_transits[pos]),
1206  slack_min));
1207  current_route_max_cumuls_[pos] = GetLastPossibleValueForCumulWithOffset(
1208  *dimension_, route[pos], current_route_max_cumuls_[pos],
1209  cumul_offset);
1210  if (current_route_max_cumuls_[pos] < current_route_min_cumuls_[pos]) {
1211  return false;
1212  }
1213  }
1214  }
1215  return true;
1216 }
1217 
1219  const std::vector<SlopeAndYIntercept>& slope_and_y_intercept) {
1220  CHECK(!slope_and_y_intercept.empty());
1221  std::vector<bool> convex(slope_and_y_intercept.size(), false);
1222  double previous_slope = std::numeric_limits<double>::max();
1223  for (int i = 0; i < slope_and_y_intercept.size(); ++i) {
1224  const auto& pair = slope_and_y_intercept[i];
1225  if (pair.slope < previous_slope) {
1226  convex[i] = true;
1227  }
1228  previous_slope = pair.slope;
1229  }
1230  return convex;
1231 }
1232 
1233 std::vector<SlopeAndYIntercept> PiecewiseLinearFormulationToSlopeAndYIntercept(
1235  PiecewiseLinearFormulation& pwl_function,
1236  int index_start, int index_end) {
1237  if (index_end < 0) index_end = pwl_function.x_anchors.size() - 1;
1238  const int num_segments = index_end - index_start;
1239  DCHECK_GE(num_segments, 1);
1240  std::vector<SlopeAndYIntercept> slope_and_y_intercept(num_segments);
1241  for (int seg = index_start; seg < index_end; ++seg) {
1242  auto& [slope, y_intercept] = slope_and_y_intercept[seg - index_start];
1243  slope = (pwl_function.y_anchors[seg + 1] - pwl_function.y_anchors[seg]) /
1244  static_cast<double>(pwl_function.x_anchors[seg + 1] -
1245  pwl_function.x_anchors[seg]);
1246  y_intercept =
1247  pwl_function.y_anchors[seg] - slope * pwl_function.x_anchors[seg];
1248  }
1249  return slope_and_y_intercept;
1250 }
1251 
1252 namespace {
1253 
1254 // Find a "good" scaling factor for constraints with non-integers coefficients.
1255 // See sat::FindBestScalingAndComputeErrors() for more infos.
1256 double FindBestScaling(const std::vector<double>& coefficients,
1257  const std::vector<double>& lower_bounds,
1258  const std::vector<double>& upper_bounds,
1259  int64_t max_absolute_activity,
1260  double wanted_absolute_activity_precision) {
1261  double unused_relative_coeff_error = 0;
1262  double unused_scaled_sum_error = 0;
1264  coefficients, lower_bounds, upper_bounds, max_absolute_activity,
1265  wanted_absolute_activity_precision, &unused_relative_coeff_error,
1266  &unused_scaled_sum_error);
1267 }
1268 
1269 // Returns the value of pwl(x) with pwl a PiecewiseLinearFormulation, knowing
1270 // that x ∈ [pwl.x[upper_segment_index-1], pwl.x[upper_segment_index]].
1271 int64_t PieceWiseLinearFormulationValueKnownSegment(
1272  const RoutingModel::RouteDimensionTravelInfo::TransitionInfo::
1273  PiecewiseLinearFormulation& pwl,
1274  int64_t x, int upper_segment_index, double delta = 0) {
1275  DCHECK_GE(upper_segment_index, 1);
1276  DCHECK_LE(upper_segment_index, pwl.x_anchors.size() - 1);
1277  const double alpha =
1278  static_cast<double>(pwl.y_anchors[upper_segment_index] -
1279  pwl.y_anchors[upper_segment_index - 1]) /
1280  (pwl.x_anchors[upper_segment_index] -
1281  pwl.x_anchors[upper_segment_index - 1]);
1282  const double beta = pwl.y_anchors[upper_segment_index] -
1283  pwl.x_anchors[upper_segment_index] * alpha;
1284  return std::ceil(alpha * x + beta + delta);
1285 }
1286 
1287 } // namespace
1288 
1291  PiecewiseLinearFormulation& pwl,
1292  int64_t x, int64_t* value, double delta) {
1293  // Search for first element xi such that xi < x.
1294  const auto upper_segment =
1295  std::upper_bound(pwl.x_anchors.begin(), pwl.x_anchors.end(), x);
1296  const int upper_segment_index =
1297  std::distance(pwl.x_anchors.begin(), upper_segment);
1298 
1299  // Checking bounds
1300  if (upper_segment_index == 0) {
1302  } else if (upper_segment == pwl.x_anchors.end()) {
1303  if (x == pwl.x_anchors.back()) {
1304  *value = std::ceil(pwl.y_anchors.back() + delta);
1306  }
1308  }
1309 
1310  *value = PieceWiseLinearFormulationValueKnownSegment(
1311  pwl, x, upper_segment_index, delta);
1313 }
1314 
1317  PiecewiseLinearFormulation& pwl,
1318  int64_t x, double delta) {
1319  int64_t y_value;
1321  ComputePiecewiseLinearFormulationValue(pwl, x, &y_value, delta);
1322  switch (status) {
1324  // The status should be specified.
1325  LOG(FATAL) << "Unspecified PiecewiseEvaluationStatus.";
1326  break;
1328  // x is in the bounds, therefore, simply return the computed value.
1329  return y_value;
1331  // In the convex case, if x <= lower_bound, the most restrictive
1332  // constraint will be the first one.
1333  return PieceWiseLinearFormulationValueKnownSegment(pwl, x, 1, delta);
1335  // In the convex case, if x >= upper_bound, the most restrictive
1336  // constraint will be the last one.
1337  return PieceWiseLinearFormulationValueKnownSegment(
1338  pwl, x, pwl.x_anchors.size() - 1, delta);
1339  }
1340 }
1341 
1342 bool DimensionCumulOptimizerCore::SetRouteTravelConstraints(
1343  const RouteDimensionTravelInfo& dimension_travel_info,
1344  const std::vector<int>& lp_slacks,
1345  const std::vector<int64_t>& fixed_transit,
1346  RoutingLinearSolverWrapper* solver) {
1347  const std::vector<int>& lp_cumuls = current_route_cumul_variables_;
1348  const int path_size = lp_cumuls.size();
1349 
1350  if (dimension_travel_info.transition_info.empty()) {
1351  // Travel is not travel-start dependent.
1352  // Add all path constraints to LP:
1353  // cumul[i] + fixed_transit[i] + slack[i] == cumul[i+1]
1354  // <=> fixed_transit[i] == cumul[i+1] - cumul[i] - slack[i].
1355  for (int pos = 0; pos < path_size - 1; ++pos) {
1356  const int ct =
1357  solver->CreateNewConstraint(fixed_transit[pos], fixed_transit[pos]);
1358  solver->SetCoefficient(ct, lp_cumuls[pos + 1], 1);
1359  solver->SetCoefficient(ct, lp_cumuls[pos], -1);
1360  solver->SetCoefficient(ct, lp_slacks[pos], -1);
1361  }
1362  return true;
1363  }
1364 
1365  // See:
1366  // https://docs.google.com/document/d/1niFfbCNjh70VepvVk4Ft8QgQAcrA5hrye-Vu_k0VgWw/edit?usp=sharing&resourcekey=0-rUJUHuWPjn1wWZaA0uDdtg
1367  for (int pos = 0; pos < path_size - 1; ++pos) {
1368  // Add a traffic-aware compression cost, for every path.
1369  // compression_cost represents the cost of the ABSOLUTE compression of the
1370  // travel.
1371  const int compression_cost = solver->CreateNewPositiveVariable();
1372  SET_DEBUG_VARIABLE_NAME(solver, compression_cost,
1373  absl::StrFormat("compression_cost(%ld)", pos));
1374  // relative_compression_cost represents the cost of the RELATIVE compression
1375  // of the travel. This is the real cost used. In theory,
1376  // relative_compression_cost = compression_cost / Tᵣ, where Tᵣ is the travel
1377  // value (computed with the PWL). In practice, this requires a
1378  // multiplication which is slow, so several approximations are implemented
1379  // below.
1380  const int relative_compression_cost = solver->CreateNewPositiveVariable();
1382  solver, relative_compression_cost,
1383  absl::StrFormat("relative_compression_cost(%ld)", pos));
1384 
1385  const RoutingModel::RouteDimensionTravelInfo::TransitionInfo&
1386  transition_info = dimension_travel_info.transition_info[pos];
1387  const RouteDimensionTravelInfo::TransitionInfo::PiecewiseLinearFormulation&
1388  travel_function = transition_info.travel_start_dependent_travel;
1389  const int num_pwl_anchors = travel_function.x_anchors.size();
1390  DCHECK_GE(num_pwl_anchors, 2)
1391  << "Travel value PWL must have at least 2 points";
1392  DCHECK_EQ(num_pwl_anchors, travel_function.y_anchors.size())
1393  << "Travel value PWL must have as many x anchors than y.";
1394 
1395  // 1. Create the travel value variable and set its constraints.
1396  // 1.a. Create Variables for the start and value of a travel
1397  const int64_t pre_travel_transit = transition_info.pre_travel_transit_value;
1398  const int64_t post_travel_transit =
1399  transition_info.post_travel_transit_value;
1400  const int64_t compressed_travel_value_lower_bound =
1401  transition_info.compressed_travel_value_lower_bound;
1402  const int64_t travel_value_upper_bound =
1403  dimension_travel_info.transition_info[pos].travel_value_upper_bound;
1404  // The lower bound of travel_value is already implemented by constraints as
1405  // travel_value >= compressed_travel_value (defined below) and
1406  // compressed_travel_value has compressed_travel_value_lower_bound as a
1407  // lower bound. The bound is added for the sake of clarity and being
1408  // explicit.
1409  const int travel_value = solver->AddVariable(
1410  compressed_travel_value_lower_bound, travel_value_upper_bound);
1411  SET_DEBUG_VARIABLE_NAME(solver, travel_value,
1412  absl::StrFormat("travel_value(%ld)", pos));
1413  const int travel_start = solver->AddVariable(
1414  current_route_min_cumuls_[pos] + pre_travel_transit,
1415  current_route_max_cumuls_[pos + 1] - post_travel_transit -
1416  compressed_travel_value_lower_bound);
1417  SET_DEBUG_VARIABLE_NAME(solver, travel_start,
1418  absl::StrFormat("travel_start(%ld)", pos));
1419  // travel_start = cumul[pos] + pre_travel[pos]
1420  // This becomes: pre_travel[pos] = travel_start - cumul[pos]
1421  solver->AddLinearConstraint(pre_travel_transit, pre_travel_transit,
1422  {{travel_start, 1}, {lp_cumuls[pos], -1}});
1423 
1424  // Find segments that are in bounds
1425  // Only the segments in [index_anchor_start, index_anchor_end[ are in
1426  // bounds, the others can therefore be discarded.
1427  int index_anchor_start = 0;
1428  while (index_anchor_start < num_pwl_anchors - 1 &&
1429  travel_function.x_anchors[index_anchor_start + 1] <=
1430  current_route_min_cumuls_[pos] + pre_travel_transit) {
1431  ++index_anchor_start;
1432  }
1433  int index_anchor_end = num_pwl_anchors - 1;
1434  while (index_anchor_end > 0 &&
1435  travel_function.x_anchors[index_anchor_end - 1] >=
1436  current_route_max_cumuls_[pos] + pre_travel_transit) {
1437  --index_anchor_end;
1438  }
1439  // Check that there is at least one segment.
1440  if (index_anchor_start >= index_anchor_end) return false;
1441 
1442  // Precompute the slopes and y-intercept as they will be used to detect
1443  // convexities and in the constraints.
1444  const std::vector<SlopeAndYIntercept> slope_and_y_intercept =
1446  travel_function, index_anchor_start, index_anchor_end);
1447 
1448  // Optimize binary variables by detecting convexities
1449  const std::vector<bool> convexities =
1450  SlopeAndYInterceptToConvexityRegions(slope_and_y_intercept);
1451 
1452  int nb_bin_variables = 0;
1453  for (const bool convexity : convexities) {
1454  if (convexity) {
1455  ++nb_bin_variables;
1456  if (nb_bin_variables >= 2) break;
1457  }
1458  }
1459  const bool need_bins = (nb_bin_variables > 1);
1460  // 1.b. Create a constraint so that the start belongs to only one segment
1461  const int travel_start_in_one_segment_ct =
1462  need_bins ? solver->CreateNewConstraint(1, 1)
1463  : -1; // -1 is a placeholder value here
1464 
1465  int belongs_to_this_segment_var;
1466  for (int seg = 0; seg < convexities.size(); ++seg) {
1467  if (need_bins && convexities[seg]) {
1468  belongs_to_this_segment_var = solver->AddVariable(0, 1);
1470  solver, belongs_to_this_segment_var,
1471  absl::StrFormat("travel_start(%ld)belongs_to_seg(%ld)", pos, seg));
1472  solver->SetCoefficient(travel_start_in_one_segment_ct,
1473  belongs_to_this_segment_var, 1);
1474 
1475  // 1.c. Link the binary variable to the departure time
1476  // If the travel_start value is outside the PWL, the closest segment
1477  // will be used. This is why some bounds are infinite.
1478  const int64_t lower_bound_interval =
1479  seg > 0 ? travel_function.x_anchors[index_anchor_start + seg]
1480  : current_route_min_cumuls_[pos] + pre_travel_transit;
1481  int64_t end_of_seg = seg + 1;
1482  while (end_of_seg < num_pwl_anchors - 1 && !convexities[end_of_seg]) {
1483  ++end_of_seg;
1484  }
1485  const int64_t higher_bound_interval =
1486  end_of_seg < num_pwl_anchors - 1
1487  ? travel_function.x_anchors[index_anchor_start + end_of_seg]
1488  : current_route_max_cumuls_[pos] + pre_travel_transit;
1489  const int travel_start_in_segment_ct = solver->AddLinearConstraint(
1490  lower_bound_interval, higher_bound_interval, {{travel_start, 1}});
1491  solver->SetEnforcementLiteral(travel_start_in_segment_ct,
1492  belongs_to_this_segment_var);
1493  }
1494 
1495  // 1.d. Compute the slope and y_intercept, the
1496  // coefficient used in the constraint, for each segment.
1497  const auto [slope, y_intercept] = slope_and_y_intercept[seg];
1498  // Starting later should always mean arriving later
1499  DCHECK_GE(slope, -1.0) << "Travel value PWL should have a slope >= -1";
1500 
1501  // 1.e. Define the linearization of travel_value
1502  // travel_value - slope * travel_start[pos] = y_intercept, for each
1503  // segment. In order to have a softer constraint, we only impose:
1504  // travel_value - slope * travel_start[pos] >= y_intercept
1505  // and since the cost is increasing in the travel_value, it will
1506  // Minimize it. In addition, since we are working with integers, we
1507  // add a relaxation of 0.5 which gives:
1508  // travel_value - slope * travel_start[pos] >= y_intercept - 0.5.
1509  const double upper_bound = current_route_max_cumuls_[pos];
1510  const double factor = FindBestScaling(
1511  {1.0, -slope, y_intercept - 0.5}, /*lower_bounds=*/
1512  {static_cast<double>(compressed_travel_value_lower_bound), 0, 1},
1513  /*upper_bounds=*/
1514  {static_cast<double>(travel_value_upper_bound), upper_bound, 1},
1515  /*max_absolute_activity=*/(int64_t{1} << 62),
1516  /*wanted_absolute_activity_precision=*/1e-3);
1517  // If no correct scaling is found, factor can be equal to 0. This will be
1518  // translated as an unfeasible model as, it will not constraint the
1519  // travel_value with a factor of 0.
1520  if (factor <= 0) return false;
1521 
1522  const int linearization_ct = solver->AddLinearConstraint(
1523  MathUtil::FastInt64Round(factor * (y_intercept - 0.5)),
1525  {{travel_value, MathUtil::FastInt64Round(factor)},
1526  {travel_start, MathUtil::FastInt64Round(-factor * slope)}});
1527  if (need_bins) {
1528  solver->SetEnforcementLiteral(linearization_ct,
1529  belongs_to_this_segment_var);
1530  }
1531 
1532  // ====== UNCOMMENT TO USE ORDER0 ERROR APPROXIMATION ===== //
1533  // Normally cost_scaled = C₂×(Tᵣ - T)²/Tᵣ
1534  // but here we approximate it as cost_scaled = C₂×(Tᵣ - T)²/Tm
1535  // with Tm the average travel value of this segment.
1536  // Therefore, we compute cost_scaled = cost / Tm
1537  // So the cost_function must be defined as cost = C₂×(Tᵣ - T)²
1538  // const int64_t Tm = (transit_function.y_anchors[seg] +
1539  // transit_function.y_anchors[seg + 1]) / 2; The constraint is
1540  // implemented as: cost_scaled * Tm >= cost const int cost_ct =
1541  // solver->AddLinearConstraint(0, std::numeric_limits<int64_t>::max(),
1542  // {{cost_scaled, Tm}, {cost, -1}});
1543  // solver->SetEnforcementLiteral(cost_ct, belongs_to_this_segment_var);
1544  }
1545 
1546  // 2. Create a variable for the compressed_travel_value.
1547  // cumul[pos + 1] = cumul[pos] + slack[pos] + pre_travel_transit[pos] +
1548  // compressed_travel_value[pos] + post_travel_transit[pos] This becomes:
1549  // post_travel_transit[pos] + pre_travel_transit[pos] = cumul[pos + 1] -
1550  // cumul[pos] - slack[pos] - compressed_travel_value[pos] The higher bound
1551  // of compressed_travel_value is already implemented by constraints as
1552  // travel_compression_absolute = travel_value - compressed_travel_value > 0
1553  // (see below) and travel_value has travel_value_upper_bound as an upper
1554  // bound. The bound is added for the sake of clarity and being explicit.
1555  const int compressed_travel_value = solver->AddVariable(
1556  compressed_travel_value_lower_bound, travel_value_upper_bound);
1558  solver, compressed_travel_value,
1559  absl::StrFormat("compressed_travel_value(%ld)", pos));
1560  solver->AddLinearConstraint(post_travel_transit + pre_travel_transit,
1561  post_travel_transit + pre_travel_transit,
1562  {{compressed_travel_value, -1},
1563  {lp_cumuls[pos + 1], 1},
1564  {lp_cumuls[pos], -1},
1565  {lp_slacks[pos], -1}});
1566 
1567  // 2. Create the travel value compression variable
1568  // travel_compression_absolute == travel_value - compressed_travel_value
1569  // This becomes: 0 = travel_compression_absolute - travel_value +
1570  // compressed_travel_value travel_compression_absolute must be positive or
1571  // equal to 0.
1572  const int travel_compression_absolute = solver->AddVariable(
1573  0, travel_value_upper_bound - compressed_travel_value_lower_bound);
1575  solver, travel_compression_absolute,
1576  absl::StrFormat("travel_compression_absolute(%ld)", pos));
1577 
1578  solver->AddLinearConstraint(0, 0,
1579  {{travel_compression_absolute, 1},
1580  {travel_value, -1},
1581  {compressed_travel_value, 1}});
1582 
1583  // 3. Add a cost per unit of travel
1584  // The travel_cost_coefficient is set with the travel_value and not the
1585  // compressed_travel_value to not give the incentive to compress a little
1586  // bit in order to same some cost per travel.
1587  solver->SetObjectiveCoefficient(
1588  travel_value, dimension_travel_info.travel_cost_coefficient);
1589 
1590  // 4. Adds a convex cost in epsilon
1591  // Here we DCHECK that the cost function is indeed convex
1592  const RouteDimensionTravelInfo::TransitionInfo::PiecewiseLinearFormulation&
1593  cost_function =
1594  dimension_travel_info.transition_info[pos].travel_compression_cost;
1595  const std::vector<SlopeAndYIntercept> cost_slope_and_y_intercept =
1597  const double cost_max = ComputeConvexPiecewiseLinearFormulationValue(
1598  cost_function,
1599  travel_value_upper_bound - compressed_travel_value_lower_bound);
1600  double previous_slope = 0;
1601  for (int seg = 0; seg < cost_function.x_anchors.size() - 1; ++seg) {
1602  const auto [slope, y_intercept] = cost_slope_and_y_intercept[seg];
1603  // Check convexity
1604  DCHECK_GE(slope, previous_slope)
1605  << "Compression error is not convex. Segment " << (1 + seg)
1606  << " out of " << (cost_function.x_anchors.size() - 1);
1607  previous_slope = slope;
1608  const double factor = FindBestScaling(
1609  {1.0, -slope, y_intercept}, /*lower_bounds=*/
1610  {0, static_cast<double>(compressed_travel_value_lower_bound), 1},
1611  /*upper_bounds=*/
1612  {cost_max, static_cast<double>(travel_value_upper_bound), 1},
1613  /*max_absolute_activity=*/(static_cast<int64_t>(1) << 62),
1614  /*wanted_absolute_activity_precision=*/1e-3);
1615  // If no correct scaling is found, factor can be equal to 0. This will be
1616  // translated as an unfeasible model as, it will not constraint the
1617  // compression_cost with a factor of 0.
1618  if (factor <= 0) return false;
1619 
1620  solver->AddLinearConstraint(
1621  MathUtil::FastInt64Round(factor * y_intercept),
1623  {{compression_cost, std::round(factor)},
1624  {travel_compression_absolute,
1625  MathUtil::FastInt64Round(-factor * slope)}});
1626  }
1627  // ====== UNCOMMENT TO USE PRODUCT TO COMPUTE THE EXACT ERROR ===== //
1628  // Normally cost_scaled = C₂×(Tᵣ - T)²/Tᵣ
1629  // and here we compute cost_scaled = cost / Tᵣ
1630  // So the cost_function must be defined as cost = C₂×(Tᵣ - T)²
1631  // const int prod = solver->CreateNewPositiveVariable();
1632  // solver->AddProductConstraint(prod, {cost_scaled, travel_value});
1633  // The constraint is implemented as: cost_scaled * Tᵣ >= cost
1634  // solver->AddLinearConstraint(0, std::numeric_limits<int64_t>::max(),
1635  // {{prod, 1}, {cost, -1}});
1636 
1637  // ====== UNCOMMENT TO USE AVERAGE ERROR APPROXIMATION ===== //
1638  // Normally cost_scaled = C₂×(Tᵣ - T)²/Tᵣ
1639  // but here we approximate it as cost_scaled = C₂×(Tᵣ - T)²/Tₐ
1640  // with Tₐ the average travel value (on all the segments).
1641  // Since we do not have access to Tₐ here, we define cost_scaled as
1642  // cost_scaled = cost. So the cost_function must be defined as cost =
1643  // C₂×(Tᵣ - T)²/Tₐ The constraint is implemented as: cost_scaled >= cost
1644  solver->AddLinearConstraint(
1646  {{relative_compression_cost, 1}, {compression_cost, -1}});
1647 
1648  solver->SetObjectiveCoefficient(relative_compression_cost, 1.0);
1649  }
1650  return true;
1651 }
1652 
1653 bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
1654  int vehicle, const std::function<int64_t(int64_t)>& next_accessor,
1655  const std::function<int64_t(int64_t, int64_t)>& transit_accessor,
1656  const RouteDimensionTravelInfo& dimension_travel_info, int64_t cumul_offset,
1657  bool optimize_costs, RoutingLinearSolverWrapper* solver,
1658  int64_t* route_transit_cost, int64_t* route_cost_offset) {
1659  RoutingModel* const model = dimension_->model();
1660  // Extract the vehicle's path from next_accessor.
1661  std::vector<int64_t> path;
1662  {
1663  int node = model->Start(vehicle);
1664  path.push_back(node);
1665  while (!model->IsEnd(node)) {
1666  node = next_accessor(node);
1667  path.push_back(node);
1668  }
1669  DCHECK_GE(path.size(), 2);
1670  }
1671  const int path_size = path.size();
1672 
1673  std::vector<int64_t> fixed_transit(path_size - 1);
1674  {
1675  for (int pos = 1; pos < path_size; ++pos) {
1676  fixed_transit[pos - 1] = transit_accessor(path[pos - 1], path[pos]);
1677  }
1678  }
1679  if (!ExtractRouteCumulBounds(path, cumul_offset)) {
1680  return false;
1681  }
1682  if (dimension_travel_info.transition_info.empty()) {
1683  if (!TightenRouteCumulBounds(path, fixed_transit, cumul_offset)) {
1684  return false;
1685  }
1686  } else {
1687  // Tighten the bounds with the lower bound of the transit value
1688  std::vector<int64_t> min_transit(path_size - 1);
1689  for (int pos = 0; pos < path_size - 1; ++pos) {
1690  const RouteDimensionTravelInfo::TransitionInfo& transition =
1691  dimension_travel_info.transition_info[pos];
1692  min_transit[pos] = transition.pre_travel_transit_value +
1693  transition.compressed_travel_value_lower_bound +
1694  transition.post_travel_transit_value;
1695  }
1696  if (!TightenRouteCumulBounds(path, min_transit, cumul_offset)) {
1697  return false;
1698  }
1699  }
1700 
1701  // LP Model variables, current_route_cumul_variables_ and lp_slacks.
1702  // Create LP variables for cumuls.
1703  std::vector<int>& lp_cumuls = current_route_cumul_variables_;
1704  lp_cumuls.assign(path_size, -1);
1705  for (int pos = 0; pos < path_size; ++pos) {
1706  const int lp_cumul = solver->CreateNewPositiveVariable();
1707  SET_DEBUG_VARIABLE_NAME(solver, lp_cumul,
1708  absl::StrFormat("lp_cumul(%ld)", pos));
1709  index_to_cumul_variable_[path[pos]] = lp_cumul;
1710  lp_cumuls[pos] = lp_cumul;
1711  if (!solver->SetVariableBounds(lp_cumul, current_route_min_cumuls_[pos],
1712  current_route_max_cumuls_[pos])) {
1713  return false;
1714  }
1715  const SortedDisjointIntervalList& forbidden =
1716  dimension_->forbidden_intervals()[path[pos]];
1717  if (forbidden.NumIntervals() > 0) {
1718  std::vector<int64_t> starts;
1719  std::vector<int64_t> ends;
1720  for (const ClosedInterval interval :
1721  dimension_->GetAllowedIntervalsInRange(
1722  path[pos], CapAdd(current_route_min_cumuls_[pos], cumul_offset),
1723  CapAdd(current_route_max_cumuls_[pos], cumul_offset))) {
1724  starts.push_back(CapSub(interval.start, cumul_offset));
1725  ends.push_back(CapSub(interval.end, cumul_offset));
1726  }
1727  solver->SetVariableDisjointBounds(lp_cumul, starts, ends);
1728  }
1729  }
1730  // Create LP variables for slacks.
1731  std::vector<int> lp_slacks(path_size - 1, -1);
1732  for (int pos = 0; pos < path_size - 1; ++pos) {
1733  const IntVar* cp_slack = dimension_->SlackVar(path[pos]);
1734  lp_slacks[pos] = solver->CreateNewPositiveVariable();
1735  SET_DEBUG_VARIABLE_NAME(solver, lp_slacks[pos],
1736  absl::StrFormat("lp_slacks(%ld)", pos));
1737  if (!solver->SetVariableBounds(lp_slacks[pos], cp_slack->Min(),
1738  cp_slack->Max())) {
1739  return false;
1740  }
1741  }
1742 
1743  if (!SetRouteTravelConstraints(dimension_travel_info, lp_slacks,
1744  fixed_transit, solver)) {
1745  return false;
1746  }
1747 
1748  if (route_cost_offset != nullptr) *route_cost_offset = 0;
1749  if (optimize_costs) {
1750  // Add soft upper bounds.
1751  for (int pos = 0; pos < path_size; ++pos) {
1752  if (!dimension_->HasCumulVarSoftUpperBound(path[pos])) continue;
1753  const int64_t coef =
1754  dimension_->GetCumulVarSoftUpperBoundCoefficient(path[pos]);
1755  if (coef == 0) continue;
1756  int64_t bound = dimension_->GetCumulVarSoftUpperBound(path[pos]);
1757  if (bound < cumul_offset && route_cost_offset != nullptr) {
1758  // Add coef * (cumul_offset - bound) to the cost offset.
1759  *route_cost_offset = CapAdd(*route_cost_offset,
1760  CapProd(CapSub(cumul_offset, bound), coef));
1761  }
1762  bound = std::max<int64_t>(0, CapSub(bound, cumul_offset));
1763  if (current_route_max_cumuls_[pos] <= bound) {
1764  // constraint is never violated.
1765  continue;
1766  }
1767  const int soft_ub_diff = solver->CreateNewPositiveVariable();
1768  SET_DEBUG_VARIABLE_NAME(solver, soft_ub_diff,
1769  absl::StrFormat("soft_ub_diff(%ld)", pos));
1770  solver->SetObjectiveCoefficient(soft_ub_diff, coef);
1771  // cumul - soft_ub_diff <= bound.
1772  const int ct = solver->CreateNewConstraint(
1774  solver->SetCoefficient(ct, lp_cumuls[pos], 1);
1775  solver->SetCoefficient(ct, soft_ub_diff, -1);
1776  }
1777  // Add soft lower bounds.
1778  for (int pos = 0; pos < path_size; ++pos) {
1779  if (!dimension_->HasCumulVarSoftLowerBound(path[pos])) continue;
1780  const int64_t coef =
1781  dimension_->GetCumulVarSoftLowerBoundCoefficient(path[pos]);
1782  if (coef == 0) continue;
1783  const int64_t bound = std::max<int64_t>(
1784  0, CapSub(dimension_->GetCumulVarSoftLowerBound(path[pos]),
1785  cumul_offset));
1786  if (current_route_min_cumuls_[pos] >= bound) {
1787  // constraint is never violated.
1788  continue;
1789  }
1790  const int soft_lb_diff = solver->CreateNewPositiveVariable();
1791  SET_DEBUG_VARIABLE_NAME(solver, soft_lb_diff,
1792  absl::StrFormat("soft_lb_diff(%ld)", pos));
1793  solver->SetObjectiveCoefficient(soft_lb_diff, coef);
1794  // bound - cumul <= soft_lb_diff
1795  const int ct = solver->CreateNewConstraint(
1797  solver->SetCoefficient(ct, lp_cumuls[pos], 1);
1798  solver->SetCoefficient(ct, soft_lb_diff, 1);
1799  }
1800  }
1801  // Add pickup and delivery limits.
1802  std::vector<int> visited_pairs;
1803  StoreVisitedPickupDeliveryPairsOnRoute(
1804  *dimension_, vehicle, next_accessor, &visited_pairs,
1805  &visited_pickup_delivery_indices_for_pair_);
1806  for (int pair_index : visited_pairs) {
1807  const int64_t pickup_index =
1808  visited_pickup_delivery_indices_for_pair_[pair_index].first;
1809  const int64_t delivery_index =
1810  visited_pickup_delivery_indices_for_pair_[pair_index].second;
1811  visited_pickup_delivery_indices_for_pair_[pair_index] = {-1, -1};
1812 
1813  DCHECK_GE(pickup_index, 0);
1814  if (delivery_index < 0) {
1815  // We didn't encounter a delivery for this pickup.
1816  continue;
1817  }
1818 
1819  const int64_t limit = dimension_->GetPickupToDeliveryLimitForPair(
1820  pair_index, model->GetPickupIndexPairs(pickup_index)[0].second,
1821  model->GetDeliveryIndexPairs(delivery_index)[0].second);
1822  if (limit < std::numeric_limits<int64_t>::max()) {
1823  // delivery_cumul - pickup_cumul <= limit.
1824  const int ct = solver->CreateNewConstraint(
1826  solver->SetCoefficient(ct, index_to_cumul_variable_[delivery_index], 1);
1827  solver->SetCoefficient(ct, index_to_cumul_variable_[pickup_index], -1);
1828  }
1829  }
1830 
1831  // Add span bound constraint.
1832  const int64_t span_bound = dimension_->GetSpanUpperBoundForVehicle(vehicle);
1833  if (span_bound < std::numeric_limits<int64_t>::max()) {
1834  // end_cumul - start_cumul <= bound
1835  const int ct = solver->CreateNewConstraint(
1836  std::numeric_limits<int64_t>::min(), span_bound);
1837  solver->SetCoefficient(ct, lp_cumuls.back(), 1);
1838  solver->SetCoefficient(ct, lp_cumuls.front(), -1);
1839  }
1840  // Add span cost.
1841  const int64_t span_cost_coef =
1842  dimension_->GetSpanCostCoefficientForVehicle(vehicle);
1843  if (optimize_costs && span_cost_coef > 0) {
1844  solver->SetObjectiveCoefficient(lp_cumuls.back(), span_cost_coef);
1845  solver->SetObjectiveCoefficient(lp_cumuls.front(), -span_cost_coef);
1846  }
1847  // Add soft span cost.
1848  if (optimize_costs && dimension_->HasSoftSpanUpperBounds()) {
1849  const BoundCost bound_cost =
1850  dimension_->GetSoftSpanUpperBoundForVehicle(vehicle);
1851  if (bound_cost.bound < std::numeric_limits<int64_t>::max() &&
1852  bound_cost.cost > 0) {
1853  const int span_violation = solver->CreateNewPositiveVariable();
1854  SET_DEBUG_VARIABLE_NAME(solver, span_violation, "span_violation");
1855  // end - start <= bound + span_violation
1856  const int violation = solver->CreateNewConstraint(
1857  std::numeric_limits<int64_t>::min(), bound_cost.bound);
1858  solver->SetCoefficient(violation, lp_cumuls.back(), 1.0);
1859  solver->SetCoefficient(violation, lp_cumuls.front(), -1.0);
1860  solver->SetCoefficient(violation, span_violation, -1.0);
1861  // Add span_violation * cost to objective.
1862  solver->SetObjectiveCoefficient(span_violation, bound_cost.cost);
1863  }
1864  }
1865  // Add global span constraint.
1866  if (optimize_costs && dimension_->global_span_cost_coefficient() > 0) {
1867  // min_start_cumul_ <= cumuls[start]
1868  int ct =
1869  solver->CreateNewConstraint(std::numeric_limits<int64_t>::min(), 0);
1870  solver->SetCoefficient(ct, min_start_cumul_, 1);
1871  solver->SetCoefficient(ct, lp_cumuls.front(), -1);
1872  // max_end_cumul_ >= cumuls[end]
1873  ct = solver->CreateNewConstraint(0, std::numeric_limits<int64_t>::max());
1874  solver->SetCoefficient(ct, max_end_cumul_, 1);
1875  solver->SetCoefficient(ct, lp_cumuls.back(), -1);
1876  }
1877  // Fill transit cost if specified.
1878  if (route_transit_cost != nullptr) {
1879  if (optimize_costs && span_cost_coef > 0) {
1880  const int64_t total_fixed_transit = std::accumulate(
1881  fixed_transit.begin(), fixed_transit.end(), 0, CapAdd);
1882  *route_transit_cost = CapProd(total_fixed_transit, span_cost_coef);
1883  } else {
1884  *route_transit_cost = 0;
1885  }
1886  }
1887  // For every break that must be inside the route, the duration of that break
1888  // must be flowed in the slacks of arcs that can intersect the break.
1889  // This LP modelization is correct but not complete:
1890  // can miss some cases where the breaks cannot fit.
1891  // TODO(user): remove the need for returns in the code below.
1892  current_route_break_variables_.clear();
1893  if (!dimension_->HasBreakConstraints()) return true;
1894  const std::vector<IntervalVar*>& breaks =
1895  dimension_->GetBreakIntervalsOfVehicle(vehicle);
1896  const int num_breaks = breaks.size();
1897  // When there are no breaks, only break distance needs to be modeled,
1898  // and it reduces to a span maximum.
1899  // TODO(user): Also add the case where no breaks can intersect the route.
1900  if (num_breaks == 0) {
1901  int64_t maximum_route_span = std::numeric_limits<int64_t>::max();
1902  for (const auto& distance_duration :
1903  dimension_->GetBreakDistanceDurationOfVehicle(vehicle)) {
1904  maximum_route_span =
1905  std::min(maximum_route_span, distance_duration.first);
1906  }
1907  if (maximum_route_span < std::numeric_limits<int64_t>::max()) {
1908  const int ct = solver->CreateNewConstraint(
1909  std::numeric_limits<int64_t>::min(), maximum_route_span);
1910  solver->SetCoefficient(ct, lp_cumuls.back(), 1);
1911  solver->SetCoefficient(ct, lp_cumuls.front(), -1);
1912  }
1913  return true;
1914  }
1915  // Gather visit information: the visit of node i has [start, end) =
1916  // [cumul[i] - post_travel[i-1], cumul[i] + pre_travel[i]).
1917  // Breaks cannot overlap those visit intervals.
1918  std::vector<int64_t> pre_travel(path_size - 1, 0);
1919  std::vector<int64_t> post_travel(path_size - 1, 0);
1920  {
1921  const int pre_travel_index =
1922  dimension_->GetPreTravelEvaluatorOfVehicle(vehicle);
1923  if (pre_travel_index != -1) {
1924  FillPathEvaluation(path, model->TransitCallback(pre_travel_index),
1925  &pre_travel);
1926  }
1927  const int post_travel_index =
1928  dimension_->GetPostTravelEvaluatorOfVehicle(vehicle);
1929  if (post_travel_index != -1) {
1930  FillPathEvaluation(path, model->TransitCallback(post_travel_index),
1931  &post_travel);
1932  }
1933  }
1934  // If the solver is CPSAT, it will need to represent the times at which
1935  // breaks are scheduled, those variables are used both in the pure breaks
1936  // part and in the break distance part of the model.
1937  // Otherwise, it doesn't need the variables and they are not created.
1938  std::vector<int> lp_break_start;
1939  std::vector<int> lp_break_duration;
1940  std::vector<int> lp_break_end;
1941  if (solver->IsCPSATSolver()) {
1942  lp_break_start.resize(num_breaks, -1);
1943  lp_break_duration.resize(num_breaks, -1);
1944  lp_break_end.resize(num_breaks, -1);
1945  }
1946 
1947  std::vector<int> slack_exact_lower_bound_ct(path_size - 1, -1);
1948  std::vector<int> slack_linear_lower_bound_ct(path_size - 1, -1);
1949 
1950  const int64_t vehicle_start_min = current_route_min_cumuls_.front();
1951  const int64_t vehicle_start_max = current_route_max_cumuls_.front();
1952  const int64_t vehicle_end_min = current_route_min_cumuls_.back();
1953  const int64_t vehicle_end_max = current_route_max_cumuls_.back();
1954  const int all_break_variables_offset =
1955  vehicle_to_all_break_variables_offset_[vehicle];
1956  for (int br = 0; br < num_breaks; ++br) {
1957  const IntervalVar& break_var = *breaks[br];
1958  if (!break_var.MustBePerformed()) continue;
1959  const int64_t break_start_min = CapSub(break_var.StartMin(), cumul_offset);
1960  const int64_t break_start_max = CapSub(break_var.StartMax(), cumul_offset);
1961  const int64_t break_end_min = CapSub(break_var.EndMin(), cumul_offset);
1962  const int64_t break_end_max = CapSub(break_var.EndMax(), cumul_offset);
1963  const int64_t break_duration_min = break_var.DurationMin();
1964  const int64_t break_duration_max = break_var.DurationMax();
1965  // The CPSAT solver encodes all breaks that can intersect the route,
1966  // the LP solver only encodes the breaks that must intersect the route.
1967  if (solver->IsCPSATSolver()) {
1968  if (break_end_max <= vehicle_start_min ||
1969  vehicle_end_max <= break_start_min) {
1970  all_break_variables_[all_break_variables_offset + 2 * br] = -1;
1971  all_break_variables_[all_break_variables_offset + 2 * br + 1] = -1;
1972  current_route_break_variables_.push_back(-1);
1973  current_route_break_variables_.push_back(-1);
1974  continue;
1975  }
1976  lp_break_start[br] =
1977  solver->AddVariable(break_start_min, break_start_max);
1978  SET_DEBUG_VARIABLE_NAME(solver, lp_break_start[br],
1979  absl::StrFormat("lp_break_start(%ld)", br));
1980  lp_break_end[br] = solver->AddVariable(break_end_min, break_end_max);
1981  SET_DEBUG_VARIABLE_NAME(solver, lp_break_end[br],
1982  absl::StrFormat("lp_break_end(%ld)", br));
1983  lp_break_duration[br] =
1984  solver->AddVariable(break_duration_min, break_duration_max);
1985  SET_DEBUG_VARIABLE_NAME(solver, lp_break_duration[br],
1986  absl::StrFormat("lp_break_duration(%ld)", br));
1987  // start + duration = end.
1988  solver->AddLinearConstraint(0, 0,
1989  {{lp_break_end[br], 1},
1990  {lp_break_start[br], -1},
1991  {lp_break_duration[br], -1}});
1992  // Record index of variables
1993  all_break_variables_[all_break_variables_offset + 2 * br] =
1994  lp_break_start[br];
1995  all_break_variables_[all_break_variables_offset + 2 * br + 1] =
1996  lp_break_end[br];
1997  current_route_break_variables_.push_back(lp_break_start[br]);
1998  current_route_break_variables_.push_back(lp_break_end[br]);
1999  } else {
2000  if (break_end_min <= vehicle_start_max ||
2001  vehicle_end_min <= break_start_max) {
2002  all_break_variables_[all_break_variables_offset + 2 * br] = -1;
2003  all_break_variables_[all_break_variables_offset + 2 * br + 1] = -1;
2004  current_route_break_variables_.push_back(-1);
2005  current_route_break_variables_.push_back(-1);
2006  continue;
2007  }
2008  }
2009 
2010  // Create a constraint for every break, that forces it to be scheduled
2011  // in exactly one place, i.e. one slack or before/after the route.
2012  // sum_i break_in_slack_i == 1.
2013  const int break_in_one_slack_ct = solver->CreateNewConstraint(1, 1);
2014 
2015  if (solver->IsCPSATSolver()) {
2016  // Break can be before route.
2017  if (break_end_min <= vehicle_start_max) {
2018  const int ct = solver->AddLinearConstraint(
2020  {{lp_cumuls.front(), 1}, {lp_break_end[br], -1}});
2021  const int break_is_before_route = solver->AddVariable(0, 1);
2023  solver, break_is_before_route,
2024  absl::StrFormat("break_is_before_route(%ld)", br));
2025  solver->SetEnforcementLiteral(ct, break_is_before_route);
2026  solver->SetCoefficient(break_in_one_slack_ct, break_is_before_route, 1);
2027  }
2028  // Break can be after route.
2029  if (vehicle_end_min <= break_start_max) {
2030  const int ct = solver->AddLinearConstraint(
2032  {{lp_break_start[br], 1}, {lp_cumuls.back(), -1}});
2033  const int break_is_after_route = solver->AddVariable(0, 1);
2035  solver, break_is_after_route,
2036  absl::StrFormat("break_is_after_route(%ld)", br));
2037  solver->SetEnforcementLiteral(ct, break_is_after_route);
2038  solver->SetCoefficient(break_in_one_slack_ct, break_is_after_route, 1);
2039  }
2040  }
2041 
2042  // Add the possibility of fitting the break during each slack where it can.
2043  for (int pos = 0; pos < path_size - 1; ++pos) {
2044  // Pass on slacks that cannot start before, cannot end after,
2045  // or are not long enough to contain the break.
2046  const int64_t slack_start_min =
2047  CapAdd(current_route_min_cumuls_[pos], pre_travel[pos]);
2048  if (slack_start_min > break_start_max) break;
2049  const int64_t slack_end_max =
2050  CapSub(current_route_max_cumuls_[pos + 1], post_travel[pos]);
2051  if (break_end_min > slack_end_max) continue;
2052  const int64_t slack_duration_max =
2053  std::min(CapSub(CapSub(current_route_max_cumuls_[pos + 1],
2054  current_route_min_cumuls_[pos]),
2055  fixed_transit[pos]),
2056  dimension_->SlackVar(path[pos])->Max());
2057  if (slack_duration_max < break_duration_min) continue;
2058 
2059  // Break can fit into slack: make LP variable, add to break and slack
2060  // constraints.
2061  // Make a linearized slack lower bound (lazily), that represents
2062  // sum_br break_duration_min(br) * break_in_slack(br, pos) <=
2063  // lp_slacks(pos).
2064  const int break_in_slack = solver->AddVariable(0, 1);
2066  solver, break_in_slack,
2067  absl::StrFormat("break_in_slack(%ld, %ld)", br, pos));
2068  if (slack_linear_lower_bound_ct[pos] == -1) {
2069  slack_linear_lower_bound_ct[pos] = solver->AddLinearConstraint(
2070  std::numeric_limits<int64_t>::min(), 0, {{lp_slacks[pos], -1}});
2071  }
2072  // To keep the model clean
2073  // (cf. glop::LinearProgram::NotifyThatColumnsAreClean), constraints on
2074  // break_in_slack need to be in ascending order.
2075  if (break_in_one_slack_ct < slack_linear_lower_bound_ct[pos]) {
2076  solver->SetCoefficient(break_in_one_slack_ct, break_in_slack, 1);
2077  solver->SetCoefficient(slack_linear_lower_bound_ct[pos], break_in_slack,
2078  break_duration_min);
2079  } else {
2080  solver->SetCoefficient(slack_linear_lower_bound_ct[pos], break_in_slack,
2081  break_duration_min);
2082  solver->SetCoefficient(break_in_one_slack_ct, break_in_slack, 1);
2083  }
2084 
2085  if (solver->IsCPSATSolver()) {
2086  // Exact relation between breaks, slacks and cumul variables.
2087  // Make an exact slack lower bound (lazily), that represents
2088  // sum_br break_duration(br) * break_in_slack(br, pos) <=
2089  // lp_slacks(pos).
2090  const int break_duration_in_slack =
2091  solver->AddVariable(0, slack_duration_max);
2093  solver, break_duration_in_slack,
2094  absl::StrFormat("break_duration_in_slack(%ld, %ld)", br, pos));
2095  solver->AddProductConstraint(break_duration_in_slack,
2096  {break_in_slack, lp_break_duration[br]});
2097  if (slack_exact_lower_bound_ct[pos] == -1) {
2098  slack_exact_lower_bound_ct[pos] = solver->AddLinearConstraint(
2099  std::numeric_limits<int64_t>::min(), 0, {{lp_slacks[pos], -1}});
2100  }
2101  solver->SetCoefficient(slack_exact_lower_bound_ct[pos],
2102  break_duration_in_slack, 1);
2103  // If break_in_slack_i == 1, then
2104  // 1) break_start >= cumul[pos] + pre_travel[pos]
2105  const int break_start_after_current_ct = solver->AddLinearConstraint(
2106  pre_travel[pos], std::numeric_limits<int64_t>::max(),
2107  {{lp_break_start[br], 1}, {lp_cumuls[pos], -1}});
2108  solver->SetEnforcementLiteral(break_start_after_current_ct,
2109  break_in_slack);
2110  // 2) break_end <= cumul[pos+1] - post_travel[pos]
2111  const int break_ends_before_next_ct = solver->AddLinearConstraint(
2112  post_travel[pos], std::numeric_limits<int64_t>::max(),
2113  {{lp_cumuls[pos + 1], 1}, {lp_break_end[br], -1}});
2114  solver->SetEnforcementLiteral(break_ends_before_next_ct,
2115  break_in_slack);
2116  }
2117  }
2118  }
2119 
2120  if (!solver->IsCPSATSolver()) return true;
2121  if (!dimension_->GetBreakDistanceDurationOfVehicle(vehicle).empty()) {
2122  // If there is an optional interval, the following model would be wrong.
2123  // TODO(user): support optional intervals.
2124  for (const IntervalVar* interval :
2125  dimension_->GetBreakIntervalsOfVehicle(vehicle)) {
2126  if (!interval->MustBePerformed()) return true;
2127  }
2128  // When this feature is used, breaks are in sorted order.
2129  for (int br = 1; br < num_breaks; ++br) {
2130  if (lp_break_start[br] == -1 || lp_break_start[br - 1] == -1) continue;
2131  solver->AddLinearConstraint(
2133  {{lp_break_end[br - 1], -1}, {lp_break_start[br], 1}});
2134  }
2135  }
2136  for (const auto& distance_duration :
2137  dimension_->GetBreakDistanceDurationOfVehicle(vehicle)) {
2138  const int64_t limit = distance_duration.first;
2139  const int64_t min_break_duration = distance_duration.second;
2140  // Interbreak limit constraint: breaks are interpreted as being in sorted
2141  // order, and the maximum duration between two consecutive
2142  // breaks of duration more than 'min_break_duration' is 'limit'. This
2143  // considers the time until start of route and after end of route to be
2144  // infinite breaks.
2145  // The model for this constraint adds some 'cover_i' variables, such that
2146  // the breaks up to i and the start of route allows to go without a break.
2147  // With s_i the start of break i and e_i its end:
2148  // - the route start covers time from start to start + limit:
2149  // cover_0 = route_start + limit
2150  // - the coverage up to a given break is the largest of the coverage of the
2151  // previous break and if the break is long enough, break end + limit:
2152  // cover_{i+1} = max(cover_i,
2153  // e_i - s_i >= min_break_duration ? e_i + limit : -inf)
2154  // - the coverage of the last break must be at least the route end,
2155  // to ensure the time point route_end-1 is covered:
2156  // cover_{num_breaks} >= route_end
2157  // - similarly, time point s_i-1 must be covered by breaks up to i-1,
2158  // but only if the cover has not reached the route end.
2159  // For instance, a vehicle could have a choice between two days,
2160  // with a potential break on day 1 and a potential break on day 2,
2161  // but the break of day 1 does not have to cover that of day 2!
2162  // cover_{i-1} < route_end => s_i <= cover_{i-1}
2163  // This is sufficient to ensure that the union of the intervals
2164  // (-infinity, route_start], [route_end, +infinity) and all
2165  // [s_i, e_i+limit) where e_i - s_i >= min_break_duration is
2166  // the whole timeline (-infinity, +infinity).
2167  int previous_cover = solver->AddVariable(CapAdd(vehicle_start_min, limit),
2168  CapAdd(vehicle_start_max, limit));
2169  SET_DEBUG_VARIABLE_NAME(solver, previous_cover, "previous_cover");
2170  solver->AddLinearConstraint(limit, limit,
2171  {{previous_cover, 1}, {lp_cumuls.front(), -1}});
2172  for (int br = 0; br < num_breaks; ++br) {
2173  if (lp_break_start[br] == -1) continue;
2174  const int64_t break_end_min = CapSub(breaks[br]->EndMin(), cumul_offset);
2175  const int64_t break_end_max = CapSub(breaks[br]->EndMax(), cumul_offset);
2176  // break_is_eligible <=>
2177  // break_end - break_start >= break_minimum_duration.
2178  const int break_is_eligible = solver->AddVariable(0, 1);
2179  SET_DEBUG_VARIABLE_NAME(solver, break_is_eligible,
2180  absl::StrFormat("break_is_eligible(%ld)", br));
2181  const int break_is_not_eligible = solver->AddVariable(0, 1);
2183  solver, break_is_not_eligible,
2184  absl::StrFormat("break_is_not_eligible(%ld)", br));
2185  {
2186  solver->AddLinearConstraint(
2187  1, 1, {{break_is_eligible, 1}, {break_is_not_eligible, 1}});
2188  const int positive_ct = solver->AddLinearConstraint(
2189  min_break_duration, std::numeric_limits<int64_t>::max(),
2190  {{lp_break_end[br], 1}, {lp_break_start[br], -1}});
2191  solver->SetEnforcementLiteral(positive_ct, break_is_eligible);
2192  const int negative_ct = solver->AddLinearConstraint(
2193  std::numeric_limits<int64_t>::min(), min_break_duration - 1,
2194  {{lp_break_end[br], 1}, {lp_break_start[br], -1}});
2195  solver->SetEnforcementLiteral(negative_ct, break_is_not_eligible);
2196  }
2197  // break_is_eligible => break_cover == break_end + limit.
2198  // break_is_not_eligible => break_cover == vehicle_start_min + limit.
2199  // break_cover's initial domain is the smallest interval that contains the
2200  // union of sets {vehicle_start_min+limit} and
2201  // [break_end_min+limit, break_end_max+limit).
2202  const int break_cover = solver->AddVariable(
2203  CapAdd(std::min(vehicle_start_min, break_end_min), limit),
2204  CapAdd(std::max(vehicle_start_min, break_end_max), limit));
2205  SET_DEBUG_VARIABLE_NAME(solver, break_cover,
2206  absl::StrFormat("break_cover(%ld)", br));
2207  const int limit_cover_ct = solver->AddLinearConstraint(
2208  limit, limit, {{break_cover, 1}, {lp_break_end[br], -1}});
2209  solver->SetEnforcementLiteral(limit_cover_ct, break_is_eligible);
2210  const int empty_cover_ct = solver->AddLinearConstraint(
2211  CapAdd(vehicle_start_min, limit), CapAdd(vehicle_start_min, limit),
2212  {{break_cover, 1}});
2213  solver->SetEnforcementLiteral(empty_cover_ct, break_is_not_eligible);
2214 
2215  const int cover =
2216  solver->AddVariable(CapAdd(vehicle_start_min, limit),
2218  SET_DEBUG_VARIABLE_NAME(solver, cover, absl::StrFormat("cover(%ld)", br));
2219  solver->AddMaximumConstraint(cover, {previous_cover, break_cover});
2220  // Cover chaining. If route end is not covered, break start must be:
2221  // cover_{i-1} < route_end => s_i <= cover_{i-1}
2222  const int route_end_is_not_covered = solver->AddReifiedLinearConstraint(
2224  {{lp_cumuls.back(), 1}, {previous_cover, -1}});
2225  const int break_start_cover_ct = solver->AddLinearConstraint(
2227  {{previous_cover, 1}, {lp_break_start[br], -1}});
2228  solver->SetEnforcementLiteral(break_start_cover_ct,
2229  route_end_is_not_covered);
2230 
2231  previous_cover = cover;
2232  }
2233  solver->AddLinearConstraint(0, std::numeric_limits<int64_t>::max(),
2234  {{previous_cover, 1}, {lp_cumuls.back(), -1}});
2235  }
2236 
2237  return true;
2238 }
2239 
2240 bool DimensionCumulOptimizerCore::SetGlobalConstraints(
2241  const std::function<int64_t(int64_t)>& next_accessor, int64_t cumul_offset,
2242  bool optimize_costs, RoutingLinearSolverWrapper* solver) {
2243  // Global span cost =
2244  // global_span_cost_coefficient * (max_end_cumul - min_start_cumul).
2245  const int64_t global_span_coeff = dimension_->global_span_cost_coefficient();
2246  if (optimize_costs && global_span_coeff > 0) {
2247  solver->SetObjectiveCoefficient(max_end_cumul_, global_span_coeff);
2248  solver->SetObjectiveCoefficient(min_start_cumul_, -global_span_coeff);
2249  }
2250 
2251  // Node precedence constraints, set when both nodes are visited.
2252  for (const RoutingDimension::NodePrecedence& precedence :
2253  dimension_->GetNodePrecedences()) {
2254  const int first_cumul_var = index_to_cumul_variable_[precedence.first_node];
2255  const int second_cumul_var =
2256  index_to_cumul_variable_[precedence.second_node];
2257  if (first_cumul_var < 0 || second_cumul_var < 0) {
2258  // At least one of the nodes is not on any route, skip this precedence
2259  // constraint.
2260  continue;
2261  }
2262  DCHECK_NE(first_cumul_var, second_cumul_var)
2263  << "Dimension " << dimension_->name()
2264  << " has a self-precedence on node " << precedence.first_node << ".";
2265 
2266  // cumul[second_node] - cumul[first_node] >= offset.
2267  const int ct = solver->CreateNewConstraint(
2268  precedence.offset, std::numeric_limits<int64_t>::max());
2269  solver->SetCoefficient(ct, second_cumul_var, 1);
2270  solver->SetCoefficient(ct, first_cumul_var, -1);
2271  }
2272 
2273  if (!solver->IsCPSATSolver()) {
2274  // The resource attributes conditional constraints can only be added with
2275  // the CP-SAT MIP solver.
2276  return true;
2277  }
2278 
2279  const RoutingModel& model = *dimension_->model();
2280  const int num_vehicles = model.vehicles();
2281  const auto& resource_groups = model.GetResourceGroups();
2282  for (int rg_index : model.GetDimensionResourceGroupIndices(dimension_)) {
2283  // Resource domain constraints:
2284  // Every (used) vehicle requiring a resource from this group must be
2285  // assigned to exactly one resource in this group, and each resource must be
2286  // assigned to at most 1 vehicle requiring it.
2287  // For every resource r with Attributes A = resources[r].attributes(dim)
2288  // and every vehicle v, assign(r, v) == 1 -->
2289  // A.start_domain.Min() <= cumul[Start(v)] <= A.start_domain.Max(),
2290  // and
2291  // A.end_domain.Min() <= cumul[End(v)] <= A.end_domain.Max().
2292  const ResourceGroup& resource_group = *resource_groups[rg_index];
2293  DCHECK(!resource_group.GetVehiclesRequiringAResource().empty());
2294 
2295  const std::vector<ResourceGroup::Resource>& resources =
2296  resource_group.GetResources();
2297  int num_required_resources = 0;
2298  static const int kNoConstraint = -1;
2299  // Assignment constraints for vehicles: each (used) vehicle must have
2300  // exactly one resource assigned to it.
2301  std::vector<int> vehicle_constraints(model.vehicles(), kNoConstraint);
2302  for (int v : resource_group.GetVehiclesRequiringAResource()) {
2303  if (model.IsEnd(next_accessor(model.Start(v))) &&
2304  !model.IsVehicleUsedWhenEmpty(v)) {
2305  // We don't assign a driver to unused vehicles.
2306  continue;
2307  }
2308  num_required_resources++;
2309  vehicle_constraints[v] = solver->CreateNewConstraint(1, 1);
2310  }
2311  // Assignment constraints for resources: each resource must be assigned to
2312  // at most one (used) vehicle requiring one.
2313  const int num_resources = resources.size();
2314  std::vector<int> resource_constraints(num_resources, kNoConstraint);
2315  int num_available_resources = 0;
2316  for (int r = 0; r < num_resources; r++) {
2317  const ResourceGroup::Attributes& attributes =
2318  resources[r].GetDimensionAttributes(dimension_);
2319  if (attributes.start_domain().Max() < cumul_offset ||
2320  attributes.end_domain().Max() < cumul_offset) {
2321  // This resource's domain has a cumul max lower than the offset, so it's
2322  // not possible to restrict any vehicle start/end to this domain; skip
2323  // it.
2324  continue;
2325  }
2326  num_available_resources++;
2327  resource_constraints[r] = solver->CreateNewConstraint(0, 1);
2328  }
2329 
2330  if (num_required_resources > num_available_resources) {
2331  // There aren't enough resources in this group for vehicles requiring one.
2332  return false;
2333  }
2334 
2335  std::vector<int>& resource_to_vehicle_assignment_variables =
2336  resource_group_to_resource_to_vehicle_assignment_variables_[rg_index];
2337  resource_to_vehicle_assignment_variables.assign(
2338  num_resources * num_vehicles, -1);
2339  // Create assignment variables, add them to the corresponding constraints,
2340  // and create the reified constraints assign(r, v) == 1 -->
2341  // A(r).start_domain.Min() <= cumul[Start(v)] <= A(r).start_domain.Max(),
2342  // and
2343  // A(r).end_domain.Min() <= cumul[End(v)] <= A(r).end_domain.Max().
2344  for (int r = 0; r < num_resources; r++) {
2345  if (resource_constraints[r] == kNoConstraint) continue;
2346  const ResourceGroup::Attributes& attributes =
2347  resources[r].GetDimensionAttributes(dimension_);
2348  for (int v : resource_group.GetVehiclesRequiringAResource()) {
2349  if (vehicle_constraints[v] == kNoConstraint) continue;
2350 
2351  const int assign_r_to_v = solver->AddVariable(0, 1);
2353  solver, assign_r_to_v,
2354  absl::StrFormat("assign_r_to_v(%ld, %ld)", r, v));
2355  resource_to_vehicle_assignment_variables[r * num_vehicles + v] =
2356  assign_r_to_v;
2357  // To keep the model clean
2358  // (cf. glop::LinearProgram::NotifyThatColumnsAreClean), constraints on
2359  // assign_r_to_v need to be in ascending order.
2360  if (vehicle_constraints[v] < resource_constraints[r]) {
2361  solver->SetCoefficient(vehicle_constraints[v], assign_r_to_v, 1);
2362  solver->SetCoefficient(resource_constraints[r], assign_r_to_v, 1);
2363  } else {
2364  solver->SetCoefficient(resource_constraints[r], assign_r_to_v, 1);
2365  solver->SetCoefficient(vehicle_constraints[v], assign_r_to_v, 1);
2366  }
2367 
2368  const auto& add_domain_constraint =
2369  [&solver, cumul_offset, assign_r_to_v](const Domain& domain,
2370  int cumul_variable) {
2371  if (domain == Domain::AllValues()) {
2372  return;
2373  }
2374  ClosedInterval cumul_bounds;
2375  if (!GetDomainOffsetBounds(domain, cumul_offset, &cumul_bounds)) {
2376  // This domain cannot be assigned to this vehicle.
2377  solver->SetVariableBounds(assign_r_to_v, 0, 0);
2378  return;
2379  }
2380  const int cumul_constraint = solver->AddLinearConstraint(
2381  cumul_bounds.start, cumul_bounds.end, {{cumul_variable, 1}});
2382  solver->SetEnforcementLiteral(cumul_constraint, assign_r_to_v);
2383  };
2384  add_domain_constraint(attributes.start_domain(),
2385  index_to_cumul_variable_[model.Start(v)]);
2386  add_domain_constraint(attributes.end_domain(),
2387  index_to_cumul_variable_[model.End(v)]);
2388  }
2389  }
2390  }
2391  return true;
2392 }
2393 
2394 #undef SET_DEBUG_VARIABLE_NAME
2395 
2396 void DimensionCumulOptimizerCore::SetValuesFromLP(
2397  const std::vector<int>& lp_variables, int64_t offset,
2398  RoutingLinearSolverWrapper* solver, std::vector<int64_t>* lp_values) const {
2399  if (lp_values == nullptr) return;
2400  lp_values->assign(lp_variables.size(), std::numeric_limits<int64_t>::min());
2401  for (int i = 0; i < lp_variables.size(); i++) {
2402  const int lp_var = lp_variables[i];
2403  if (lp_var < 0) continue; // Keep default value, kint64min.
2404  const double lp_value_double = solver->GetValue(lp_var);
2405  const int64_t lp_value_int64 =
2406  (lp_value_double >= std::numeric_limits<int64_t>::max())
2408  : MathUtil::FastInt64Round(lp_value_double);
2409  (*lp_values)[i] = CapAdd(lp_value_int64, offset);
2410  }
2411 }
2412 
2413 void DimensionCumulOptimizerCore::SetResourceIndices(
2414  RoutingLinearSolverWrapper* solver,
2415  std::vector<std::vector<int>>* resource_indices_per_group) const {
2416  if (resource_indices_per_group == nullptr ||
2417  resource_group_to_resource_to_vehicle_assignment_variables_.empty()) {
2418  return;
2419  }
2420  const RoutingModel& model = *dimension_->model();
2421  const int num_vehicles = model.vehicles();
2422  DCHECK(!model.GetDimensionResourceGroupIndices(dimension_).empty());
2423  const auto& resource_groups = model.GetResourceGroups();
2424  resource_indices_per_group->resize(resource_groups.size());
2425  for (int rg_index : model.GetDimensionResourceGroupIndices(dimension_)) {
2426  const ResourceGroup& resource_group = *resource_groups[rg_index];
2427  DCHECK(!resource_group.GetVehiclesRequiringAResource().empty());
2428 
2429  const int num_resources = resource_group.Size();
2430  std::vector<int>& resource_indices =
2431  resource_indices_per_group->at(rg_index);
2432  resource_indices.assign(num_vehicles, -1);
2433  // Find the resource assigned to each vehicle.
2434  const std::vector<int>& resource_to_vehicle_assignment_variables =
2435  resource_group_to_resource_to_vehicle_assignment_variables_[rg_index];
2436  DCHECK_EQ(resource_to_vehicle_assignment_variables.size(),
2437  num_resources * num_vehicles);
2438  for (int v : resource_group.GetVehiclesRequiringAResource()) {
2439  for (int r = 0; r < num_resources; r++) {
2440  const int assignment_var =
2441  resource_to_vehicle_assignment_variables[r * num_vehicles + v];
2442  if (assignment_var >= 0 && solver->GetValue(assignment_var) == 1) {
2443  // This resource is assigned to this vehicle.
2444  resource_indices[v] = r;
2445  break;
2446  }
2447  }
2448  }
2449  }
2450 }
2451 
2452 // GlobalDimensionCumulOptimizer
2453 
2454 GlobalDimensionCumulOptimizer::GlobalDimensionCumulOptimizer(
2455  const RoutingDimension* dimension,
2456  RoutingSearchParameters::SchedulingSolver solver_type)
2457  : optimizer_core_(dimension,
2458  /*use_precedence_propagator=*/
2459  !dimension->GetNodePrecedences().empty()) {
2460  switch (solver_type) {
2461  case RoutingSearchParameters::SCHEDULING_GLOP: {
2462  solver_ = std::make_unique<RoutingGlopWrapper>(
2463  /*is_relaxation=*/!dimension->model()
2465  .empty(),
2466  GetGlopParametersForGlobalLP());
2467  break;
2468  }
2469  case RoutingSearchParameters::SCHEDULING_CP_SAT: {
2470  solver_ = std::make_unique<RoutingCPSatWrapper>();
2471  break;
2472  }
2473  default:
2474  LOG(DFATAL) << "Unrecognized solver type: " << solver_type;
2475  }
2476 }
2477 
2480  const std::function<int64_t(int64_t)>& next_accessor,
2481  int64_t* optimal_cost_without_transits) {
2482  int64_t cost = 0;
2483  int64_t transit_cost = 0;
2485  optimizer_core_.Optimize(next_accessor, {}, solver_.get(), nullptr,
2486  nullptr, nullptr, &cost, &transit_cost);
2488  optimal_cost_without_transits != nullptr) {
2489  *optimal_cost_without_transits = CapSub(cost, transit_cost);
2490  }
2491  return status;
2492 }
2493 
2495  const std::function<int64_t(int64_t)>& next_accessor,
2496  const std::vector<RoutingModel::RouteDimensionTravelInfo>&
2497  dimension_travel_info_per_route,
2498  std::vector<int64_t>* optimal_cumuls, std::vector<int64_t>* optimal_breaks,
2499  std::vector<std::vector<int>>* optimal_resource_indices) {
2500  return optimizer_core_.Optimize(next_accessor,
2501  dimension_travel_info_per_route,
2502  solver_.get(), optimal_cumuls, optimal_breaks,
2503  optimal_resource_indices, nullptr, nullptr);
2504 }
2505 
2507  const std::function<int64_t(int64_t)>& next_accessor,
2508  const std::vector<RoutingModel::RouteDimensionTravelInfo>&
2509  dimension_travel_info_per_route,
2510  std::vector<int64_t>* packed_cumuls, std::vector<int64_t>* packed_breaks,
2511  std::vector<std::vector<int>>* resource_indices) {
2512  return optimizer_core_.OptimizeAndPack(
2513  next_accessor, dimension_travel_info_per_route, solver_.get(),
2514  packed_cumuls, packed_breaks, resource_indices);
2515 }
2516 
2518  int v, const RoutingModel::ResourceGroup& resource_group,
2519  const std::function<int64_t(int64_t)>& next_accessor,
2520  const std::function<int64_t(int64_t, int64_t)>& transit_accessor,
2521  bool optimize_vehicle_costs, LocalDimensionCumulOptimizer* lp_optimizer,
2522  LocalDimensionCumulOptimizer* mp_optimizer,
2523  std::vector<int64_t>* assignment_costs,
2524  std::vector<std::vector<int64_t>>* cumul_values,
2525  std::vector<std::vector<int64_t>>* break_values) {
2526  DCHECK(lp_optimizer != nullptr);
2527  DCHECK(mp_optimizer != nullptr);
2528  const RoutingDimension* dimension = lp_optimizer->dimension();
2529  DCHECK_EQ(dimension, mp_optimizer->dimension());
2530  RoutingModel* const model = dimension->model();
2531  DCHECK_NE(assignment_costs, nullptr);
2532  if (!resource_group.VehicleRequiresAResource(v) ||
2533  (!model->IsVehicleUsedWhenEmpty(v) &&
2534  next_accessor(model->Start(v)) == model->End(v))) {
2535  assignment_costs->clear();
2536  return true;
2537  }
2538  if (model->CheckLimit()) {
2539  // The model's time limit has been reached, stop everything.
2540  return false;
2541  }
2542 
2543  const std::vector<ResourceGroup::Resource>& resources =
2544  resource_group.GetResources();
2545  const int num_resources = resources.size();
2546  std::vector<int> all_resource_indices(num_resources);
2547  std::iota(all_resource_indices.begin(), all_resource_indices.end(), 0);
2548  const bool use_mp_optimizer =
2549  dimension->HasBreakConstraints() &&
2550  !dimension->GetBreakIntervalsOfVehicle(v).empty();
2551  LocalDimensionCumulOptimizer* optimizer =
2552  use_mp_optimizer ? mp_optimizer : lp_optimizer;
2553  std::vector<DimensionSchedulingStatus> statuses =
2555  v, next_accessor, transit_accessor, resources, all_resource_indices,
2556  optimize_vehicle_costs, assignment_costs, cumul_values, break_values);
2557 
2558  if (assignment_costs->empty()) {
2559  // Couldn't assign any resource to this vehicle.
2560  return false;
2561  }
2562  DCHECK_EQ(assignment_costs->size(), num_resources);
2563  DCHECK_EQ(statuses.size(), num_resources);
2564  DCHECK(cumul_values == nullptr || cumul_values->size() == num_resources);
2565  DCHECK(break_values == nullptr || break_values->size() == num_resources);
2566 
2567  if (use_mp_optimizer) {
2568  // We already used the mp optimizer, so we don't need to recompute anything.
2569  // If all assignment costs are negative, it means no resource is feasible
2570  // for this vehicle.
2571  return absl::c_any_of(*assignment_costs,
2572  [](int64_t cost) { return cost >= 0; });
2573  }
2574 
2575  std::vector<int> mp_optimizer_resource_indices;
2576  for (int r = 0; r < num_resources; r++) {
2578  mp_optimizer_resource_indices.push_back(r);
2579  }
2580  }
2581 
2582  std::vector<int64_t> mp_assignment_costs;
2583  std::vector<std::vector<int64_t>> mp_cumul_values;
2584  std::vector<std::vector<int64_t>> mp_break_values;
2586  v, next_accessor, transit_accessor, resources,
2587  mp_optimizer_resource_indices, optimize_vehicle_costs,
2588  &mp_assignment_costs,
2589  cumul_values == nullptr ? nullptr : &mp_cumul_values,
2590  break_values == nullptr ? nullptr : &mp_break_values);
2591  if (!mp_optimizer_resource_indices.empty() && mp_assignment_costs.empty()) {
2592  // A timeout was reached during optimization.
2593  return false;
2594  }
2595  DCHECK_EQ(mp_assignment_costs.size(), mp_optimizer_resource_indices.size());
2596  DCHECK(cumul_values == nullptr ||
2597  mp_cumul_values.size() == mp_optimizer_resource_indices.size());
2598  DCHECK(break_values == nullptr ||
2599  mp_break_values.size() == mp_optimizer_resource_indices.size());
2600  for (int i = 0; i < mp_optimizer_resource_indices.size(); i++) {
2601  assignment_costs->at(mp_optimizer_resource_indices[i]) =
2602  mp_assignment_costs[i];
2603  if (cumul_values != nullptr) {
2604  cumul_values->at(mp_optimizer_resource_indices[i])
2605  .swap(mp_cumul_values[i]);
2606  }
2607  if (break_values != nullptr) {
2608  break_values->at(mp_optimizer_resource_indices[i])
2609  .swap(mp_break_values[i]);
2610  }
2611  }
2612  return absl::c_any_of(*assignment_costs,
2613  [](int64_t cost) { return cost >= 0; });
2614 }
2615 
2617  std::vector<int> vehicles, int num_resources,
2618  std::function<const std::vector<int64_t>*(int)>
2619  vehicle_to_resource_assignment_costs,
2620  std::vector<int>* resource_indices) {
2621  DCHECK_GE(num_resources, 1); // Else this whole function doesn't make sense.
2622  const int num_vehicles = vehicles.size();
2623  int num_total_vehicles = -1;
2624  if (resource_indices != nullptr) {
2625  num_total_vehicles = resource_indices->size();
2626  // When returning infeasible, 'resource_indices' must be cleared, so we do
2627  // it here preemptively.
2628  resource_indices->clear();
2629  DCHECK_GE(num_total_vehicles, num_vehicles);
2630  for (int v : vehicles) {
2631  DCHECK_GE(v, 0);
2632  DCHECK_LT(v, num_total_vehicles);
2633  }
2634  }
2635 
2636  // Collect vehicle_to_resource_assignment_costs(v) for all v ∈ vehicles.
2637  // Then detect trivial infeasibility cases, before doing the min-cost-flow:
2638  // - There are not enough resources overall.
2639  // - There is no resource assignable to a vehicle that needs one.
2640  std::vector<const std::vector<int64_t>*> vi_to_resource_cost(num_vehicles);
2641  int num_vehicles_to_assign = 0;
2642  for (int i = 0; i < num_vehicles; ++i) {
2643  vi_to_resource_cost[i] = vehicle_to_resource_assignment_costs(vehicles[i]);
2644  if (!vi_to_resource_cost[i]->empty()) {
2645  DCHECK_EQ(vi_to_resource_cost[i]->size(), num_resources);
2646  ++num_vehicles_to_assign;
2647  }
2648  }
2649  if (num_vehicles_to_assign > num_resources) {
2650  VLOG(3) << "Less resources (" << num_resources << ") than the vehicles"
2651  << " requiring one (" << num_vehicles_to_assign << ")";
2652  return -1; // Infeasible.
2653  }
2654  // Catch infeasibility cases where ComputeVehicleToResourcesAssignmentCosts()
2655  // hasn't "properly" initialized the vehicle to resource assignment costs
2656  // (this can happen for instance in the ResourceGroupAssignmentFilter when
2657  // routes are synchronized with an impossible first solution).
2658  for (int i = 0; i < num_vehicles; ++i) {
2659  if (!vi_to_resource_cost[i]->empty() &&
2660  *absl::c_max_element(*vi_to_resource_cost[i]) < 0) {
2661  VLOG(3) << "Vehicle #" << vehicles[i] << " has no feasible resource";
2662  return -1;
2663  }
2664  }
2665 
2666  // We may need to apply some cost scaling when using SimpleMinCostFlow:
2667  // 3 * max_arc_cost * num_nodes must be ≤ kint64max.
2668  // To do that, we first find the maximum arc cost.
2669  int64_t max_arc_cost = 0;
2670  for (const std::vector<int64_t>* costs : vi_to_resource_cost) {
2671  if (costs->empty()) continue;
2672  max_arc_cost = std::max(max_arc_cost, *absl::c_max_element(*costs));
2673  }
2674  // To avoid potential int64_t overflows, we tweak the above formula to:
2675  // max_acceptable_arc_cost = kint64max / (3 * num_nodes) - 1.
2676  // NOTE(user): SimpleMinCostFlow always adds a sink and source node (we
2677  // probably shouldn't add a sink/source node ourselves in the graph).
2678  const int real_num_nodes = 4 + num_vehicles + num_resources;
2679  const int64_t max_acceptable_arc_cost = kint64max / (3 * real_num_nodes) - 1;
2680  // We use a power of 2 for the cost scaling factor, to have clean (in)accuracy
2681  // properties. Note also that we must round *down* the costs.
2682  int cost_right_shift = 0;
2683  while ((max_arc_cost >> cost_right_shift) > max_acceptable_arc_cost) {
2684  ++cost_right_shift;
2685  }
2686 
2687  // Then, we create the SimpleMinCostFlow and run the assignment algorithm.
2688  // NOTE(user): We often don't create as many arcs as outlined below,
2689  // especially when num_vehicles_to_assign < vehicles.size(). But since we
2690  // want to eventually make this whole function incremental, we prefer sticking
2691  // with the whole 'vehicles' set.
2692  SimpleMinCostFlow flow(
2693  /*reserve_num_nodes*/ 2 + num_vehicles + num_resources,
2694  /*reserve_num_arcs*/ num_vehicles + num_vehicles * num_resources +
2695  num_resources);
2696  const int source_index = num_vehicles + num_resources;
2697  const int sink_index = source_index + 1;
2698  const auto resource_index = [num_vehicles](int r) {
2699  return num_vehicles + r;
2700  };
2701 
2702  // Used to store the arc indices, if we need to later recover the solution.
2703  FlatMatrix<ArcIndex> vehicle_to_resource_arc_index;
2704  if (resource_indices != nullptr) {
2705  vehicle_to_resource_arc_index =
2706  FlatMatrix<ArcIndex>(num_vehicles, num_resources, -1);
2707  }
2708  for (int vi = 0; vi < num_vehicles; ++vi) {
2709  const std::vector<int64_t>& assignment_costs = *vi_to_resource_cost[vi];
2710  if (assignment_costs.empty()) continue; // Doesn't need resources.
2711 
2712  // Add a source → vehicle arc to the min-cost-flow graph.
2713  flow.AddArcWithCapacityAndUnitCost(source_index, vi, 1, 0);
2714 
2715  // Add vehicle → resource arcs to the min-cost-flow graph.
2716  for (int r = 0; r < num_resources; r++) {
2717  const int64_t assignment_cost = assignment_costs[r];
2718  if (assignment_cost < 0) continue;
2720  vi, resource_index(r), 1, assignment_cost >> cost_right_shift);
2721  if (resource_indices != nullptr) {
2722  vehicle_to_resource_arc_index[vi][r] = arc;
2723  }
2724  }
2725  }
2726 
2727  // Add resource->sink arcs to the flow.
2728  for (int r = 0; r < num_resources; r++) {
2729  flow.AddArcWithCapacityAndUnitCost(resource_index(r), sink_index, 1, 0);
2730  }
2731 
2732  // Set the flow supply.
2733  flow.SetNodeSupply(source_index, num_vehicles_to_assign);
2734  flow.SetNodeSupply(sink_index, -num_vehicles_to_assign);
2735 
2736  // Solve the min-cost flow and return its cost.
2737  if (flow.Solve() != SimpleMinCostFlow::OPTIMAL) {
2738  VLOG(3) << "Non-OPTIMAL flow result";
2739  return -1;
2740  }
2741 
2742  if (resource_indices != nullptr) {
2743  // Fill the resource indices corresponding to the min-cost assignment.
2744  resource_indices->assign(num_total_vehicles, -1);
2745  for (int vi = 0; vi < num_vehicles; ++vi) {
2746  for (int r = 0; r < num_resources; r++) {
2747  const ArcIndex arc = vehicle_to_resource_arc_index[vi][r];
2748  if (arc >= 0 && flow.Flow(arc) > 0) {
2749  resource_indices->at(vehicles[vi]) = r;
2750  break;
2751  }
2752  }
2753  }
2754  }
2755 
2756  const int64_t cost = flow.OptimalCost();
2757  DCHECK_LE(cost, kint64max >> cost_right_shift);
2758  return cost << cost_right_shift;
2759 }
2760 
2761 std::string Int64ToStr(int64_t number) {
2762  if (number == kint64min) return "-infty";
2763  if (number == kint64max) return "+infty";
2764  return std::to_string(number);
2765 }
2766 
2767 std::string DomainToString(
2768  const ::google::protobuf::RepeatedField<int64_t>* domain) {
2769  if (domain->size() > 2 && domain->size() % 2 == 0) {
2770  std::string s = "∈ ";
2771  for (int i = 0; i < domain->size(); i += 2) {
2772  s += absl::StrFormat("[%s, %s]", Int64ToStr(domain->Get(i)),
2773  Int64ToStr(domain->Get(i + 1)));
2774  if (i < domain->size() - 2) s += " ∪ ";
2775  }
2776  return s;
2777  } else if (domain->size() == 2) {
2778  if (domain->Get(0) == domain->Get(1)) {
2779  return absl::StrFormat("= %s", Int64ToStr(domain->Get(0)));
2780  } else if (domain->Get(0) == 0 && domain->Get(1) == 1) {
2781  return "∈ Binary";
2782  } else if (domain->Get(0) == std::numeric_limits<int64_t>::min() &&
2783  domain->Get(1) == std::numeric_limits<int64_t>::max()) {
2784  return "∈ ℝ";
2785  } else if (domain->Get(0) == std::numeric_limits<int64_t>::min()) {
2786  return absl::StrFormat("≤ %s", Int64ToStr(domain->Get(1)));
2787  } else if (domain->Get(1) == std::numeric_limits<int64_t>::max()) {
2788  return absl::StrFormat("≥ %s", Int64ToStr(domain->Get(0)));
2789  }
2790  return absl::StrFormat("∈ [%ls, %s]", Int64ToStr(domain->Get(0)),
2791  Int64ToStr(domain->Get(1)));
2792  } else if (domain->size() == 1) {
2793  return absl::StrFormat("= %s", Int64ToStr(domain->Get(0)));
2794  } else {
2795  return absl::StrFormat("∈ Unknown domain (size=%ld)", domain->size());
2796  }
2797 }
2798 
2799 std::string VariableToString(
2800  std::pair<sat::IntegerVariableProto, int>& variable_pair,
2801  const sat::CpSolverResponse& response_) {
2802  std::string s = "";
2803  sat::IntegerVariableProto& variable = variable_pair.first;
2804  const int index = variable_pair.second;
2805  if (response_.IsInitialized() && variable.IsInitialized() &&
2806  (response_.status() == sat::CpSolverStatus::OPTIMAL ||
2807  response_.status() == sat::CpSolverStatus::FEASIBLE)) {
2808  const double lp_value_double = response_.solution(index);
2809  const int64_t lp_value_int64 =
2810  (lp_value_double >= std::numeric_limits<int64_t>::max())
2812  : MathUtil::FastInt64Round(lp_value_double);
2813  s += Int64ToStr(lp_value_int64) + " ";
2814  } else {
2815  s += "? ";
2816  }
2817  s += DomainToString(variable.mutable_domain());
2818  return s;
2819 }
2820 
2821 std::string ConstraintToString(const sat::ConstraintProto& constraint,
2822  const sat::CpModelProto& model_,
2823  bool show_enforcement = true) {
2824  std::string s = "";
2825  if (constraint.has_linear()) {
2826  const auto& linear = constraint.linear();
2827  for (int j = 0; j < linear.vars().size(); ++j) {
2828  const std::string sign = linear.coeffs(j) > 0 ? "+" : "-";
2829  const std::string mult =
2830  std::abs(linear.coeffs(j)) != 1
2831  ? std::to_string(std::abs(linear.coeffs(j))) + " * "
2832  : "";
2833  if (j > 0 || sign != "+") s += sign + " ";
2834  s += mult + model_.variables(linear.vars(j)).name() + " ";
2835  }
2836  s += DomainToString(&linear.domain());
2837 
2838  // Enforcement literal.
2839  if (show_enforcement) {
2840  for (int j = 0; j < constraint.enforcement_literal_size(); ++j) {
2841  s += (j == 0) ? "\t if " : " and ";
2842  s += model_.variables(constraint.enforcement_literal(j)).name();
2843  }
2844  }
2845  } else {
2846  s += constraint.ShortDebugString();
2847  }
2848  return s;
2849 }
2850 
2851 std::string VariablesToString(
2852  absl::flat_hash_map<std::string, std::pair<sat::IntegerVariableProto, int>>&
2853  variables,
2854  absl::flat_hash_map<std::string, std::vector<int>>& variable_instances,
2855  absl::flat_hash_map<std::string, absl::flat_hash_set<std::string>>&
2856  variable_childs,
2857  const sat::CpSolverResponse& response_, const std::string& variable,
2858  std::string prefix = "") {
2859  if (variable.empty()) {
2860  std::string s = "";
2861  const auto& childs = variable_childs[""];
2862  for (const std::string& child : childs) {
2863  s += prefix +
2864  VariablesToString(variables, variable_instances, variable_childs,
2865  response_, child, prefix) +
2866  prefix + "\n";
2867  }
2868  return s;
2869  }
2870 
2871  const auto& instances = variable_instances[variable];
2872  std::string variable_display = variable;
2873  std::size_t bracket_pos = variable.find_last_of(')');
2874  if (bracket_pos != std::string::npos) {
2875  variable_display = variable.substr(bracket_pos + 1);
2876  }
2877  std::string s = variable_display + " | ";
2878  prefix += std::string(variable_display.length(), ' ') + " | ";
2879  for (int i = 0; i < instances.size(); ++i) {
2880  const std::string instance_name =
2881  absl::StrFormat("%s(%ld)", variable, instances[i]);
2882  if (i > 0) s += prefix;
2883  s += absl::StrFormat("%ld: %s", instances[i],
2884  VariableToString(variables[instance_name], response_));
2885 
2886  // Childs
2887  const auto& childs = variable_childs[instance_name];
2888  for (const std::string& child : childs) {
2889  s += "\n" + prefix + "| ";
2890  s += VariablesToString(variables, variable_instances, variable_childs,
2891  response_, child, prefix + "| ");
2892  }
2893  if (childs.empty()) s += "\n";
2894  }
2895  return s;
2896 }
2897 
2898 std::string RoutingCPSatWrapper::PrintModel() const {
2899  // Constraints you want to separate.
2900  std::vector<std::vector<std::string>> constraints_apart;
2901  constraints_apart.push_back(
2902  {"compression_cost", "travel_compression_absolute"});
2903 
2904  // variable_instances links the lemma of a variable to the different number of
2905  // instantiation. For instance if you have in your model x(0), x(1) and x(4),
2906  // the key "x" will be associated to {0,1,4}.
2907  absl::flat_hash_map<std::string, std::vector<int>> variable_instances;
2908  // variable_children links a variable to its children. That is, if you have in
2909  // you model x(0), then typical childs would be {"x(0)in_segment(0)",
2910  // "x(0)in_segment(1)", "x(0)scaled", ...}
2911  absl::flat_hash_map<std::string, absl::flat_hash_set<std::string>>
2912  variable_children;
2913  // variables link the name of a variable to its Proto.
2914  absl::flat_hash_map<std::string, std::pair<sat::IntegerVariableProto, int>>
2915  variables;
2916  variable_children[""] = {};
2917 
2918  const int num_constraints = model_.constraints_size();
2919  const int num_variables = model_.variables_size();
2920  int num_binary_variables = 0;
2921  for (int i = 0; i < num_variables; ++i) {
2922  const auto& variable = model_.variables(i);
2923  const auto& name = variable.name();
2924  const int pos_bracket = name.find_last_of('(');
2925  if (pos_bracket != std::string::npos) {
2926  const std::string lemma = name.substr(0, pos_bracket);
2927  const int pos_closing_bracket = name.find_last_of(')');
2928  CHECK_NE(pos_closing_bracket, std::string::npos);
2929  const int index =
2930  std::stoi(name.substr(pos_bracket + 1, pos_closing_bracket));
2931  std::vector<int>* instances = gtl::FindOrNull(variable_instances, lemma);
2932  if (instances != nullptr) {
2933  instances->push_back(index);
2934  } else {
2935  variable_instances[lemma] = {index};
2936  }
2937  variable_children[name] = {};
2938 
2939  std::string parent = "";
2940  const int pos_parent_closing_bracket = lemma.find_last_of(')');
2941  if (pos_parent_closing_bracket != std::string::npos) {
2942  parent = lemma.substr(0, pos_parent_closing_bracket + 1);
2943  }
2944  variable_children[parent].emplace(lemma);
2945  variables[name] = std::make_pair(variable, i);
2946  if (variable.domain(0) == 0 & variable.domain(1) == 1) {
2947  ++num_binary_variables;
2948  }
2949  }
2950  }
2951 
2952  // Preparing constraints
2953  // the constraints hashmap associate enforcement to constraints.
2954  // If the ket is "", then the constraint has no enforcement and if the key is
2955  // "multiple", then the constraint has several enforcement. If the constraint
2956  // has a single enforcement, then the key will be the variable name of the
2957  // enforcement.
2958  absl::flat_hash_map<std::string, std::vector<sat::ConstraintProto>>
2959  constraints;
2960  absl::flat_hash_map<std::vector<std::string>,
2961  std::vector<sat::ConstraintProto>>
2962  constraint_groups;
2963  for (int i = 0; i < num_constraints; ++i) {
2964  const auto& constraint = model_.constraints(i);
2965  std::string enforcement = "";
2966  if (constraint.enforcement_literal_size() == 1) {
2967  enforcement = model_.variables(constraint.enforcement_literal(0)).name();
2968  } else if (constraint.enforcement_literal_size() > 1) {
2969  enforcement = "multiple";
2970  } else {
2971  if (constraint.has_linear()) {
2972  const auto& linear = constraint.linear();
2973  std::vector<std::string> key;
2974  for (int j = 0; j < linear.vars().size(); ++j) {
2975  std::string var_name = model_.variables(linear.vars(j)).name();
2976  std::string lemma = var_name.substr(0, var_name.find_last_of('('));
2977  key.push_back(lemma);
2978  }
2979  auto* constraint_group = gtl::FindOrNull(constraint_groups, key);
2980  if (constraint_group != nullptr) {
2981  constraint_group->push_back(constraint);
2982  } else {
2983  constraint_groups[key] = {constraint};
2984  }
2985  }
2986  }
2987  auto* constraints_enforced = gtl::FindOrNull(constraints, enforcement);
2988  if (constraints_enforced != nullptr) {
2989  constraints[enforcement].push_back(constraint);
2990  } else {
2991  constraints[enforcement] = {constraint};
2992  }
2993  }
2994 
2995  const std::string prefix_constraint = " • ";
2996  std::string s = "Using RoutingCPSatWrapper.\n";
2997  s += absl::StrFormat("\nObjective = %f\n", this->GetObjectiveValue());
2998 
2999  for (int i = 0; i < objective_coefficients_.size(); ++i) {
3000  double coeff = objective_coefficients_[i];
3001  if (coeff != 0) {
3002  s += absl::StrFormat(" | %f * %s\n", coeff, model_.variables(i).name());
3003  }
3004  }
3005 
3006  s += absl::StrFormat("\nVariables %d (%d Binary - %d Non Binary)\n",
3007  num_variables, num_binary_variables,
3008  num_variables - num_binary_variables);
3009  s += VariablesToString(variables, variable_instances, variable_children,
3010  response_, "", " | ");
3011  s += absl::StrFormat("\n\nConstraints (%d)\n", num_constraints);
3012 
3013  // Constraints NOT enforced
3014  s += "\n- Not enforced\n";
3015  bool at_least_one_not_enforced = false;
3016  for (const auto& pair : constraint_groups) {
3017  if (!std::count(constraints_apart.begin(), constraints_apart.end(),
3018  pair.first)) {
3019  for (const auto& constraint : pair.second) {
3020  s += prefix_constraint + ConstraintToString(constraint, model_, true) +
3021  "\n";
3022  at_least_one_not_enforced = true;
3023  }
3024  }
3025  }
3026  if (!at_least_one_not_enforced) {
3027  s += prefix_constraint + "None\n";
3028  }
3029 
3030  // Constraints with a SINGLE enforcement
3031  s += "\n- Single enforcement\n";
3032  bool at_least_one_single_enforced = false;
3033  for (const auto& pair : variable_instances) {
3034  const std::string lemma = pair.first;
3035  bool found_one_constraint = false;
3036  std::string prefix = "";
3037  for (int instance : pair.second) {
3038  const std::string enforcement =
3039  absl::StrFormat("%s(%d)", lemma, instance);
3040  auto* constraints_enforced = gtl::FindOrNull(constraints, enforcement);
3041  std::string prefix_instance = "";
3042  if (constraints_enforced != nullptr) {
3043  at_least_one_single_enforced = true;
3044  if (!found_one_constraint) {
3045  found_one_constraint = true;
3046  s += prefix_constraint + "if " + lemma + " | ";
3047  prefix =
3048  std::string(prefix_constraint.size() + 1 + lemma.size(), ' ') +
3049  " | ";
3050  } else {
3051  s += prefix;
3052  }
3053  s += absl::StrFormat("%d: | ", instance);
3054  prefix_instance = prefix + " | ";
3055  bool first = true;
3056  for (const auto& constraint : *constraints_enforced) {
3057  if (!first)
3058  s += prefix_instance;
3059  else
3060  first = false;
3061  s += ConstraintToString(constraint, model_, false) + "\n";
3062  }
3063  }
3064  }
3065  }
3066  if (!at_least_one_single_enforced) {
3067  s += prefix_constraint + "None\n";
3068  }
3069 
3070  // Constraints with MULTIPLE enforcement
3071  s += "\n- Multiple enforcement\n";
3072  auto* constraints_multiple_enforced =
3073  gtl::FindOrNull(constraints, "multiple");
3074  if (constraints_multiple_enforced != nullptr) {
3075  for (const auto& constraint : *constraints_multiple_enforced) {
3076  s += prefix_constraint + ConstraintToString(constraint, model_, true) +
3077  "\n";
3078  }
3079  } else {
3080  s += prefix_constraint + "None\n";
3081  }
3082 
3083  // Constraints apart
3084  s += "\n- Set apart\n";
3085  bool at_least_one_apart = false;
3086  for (const auto& pair : constraint_groups) {
3087  if (std::count(constraints_apart.begin(), constraints_apart.end(),
3088  pair.first)) {
3089  for (const auto& constraint : pair.second) {
3090  s += prefix_constraint + ConstraintToString(constraint, model_, true) +
3091  "\n";
3092  at_least_one_apart = true;
3093  }
3094  }
3095  }
3096  if (!at_least_one_apart) {
3097  s += prefix_constraint + "None\n";
3098  }
3099 
3100  return s;
3101 }
3102 
3103 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
const RoutingDimension & dimension() const
bool PropagateCumulBounds(const std::function< int64_t(int64_t)> &next_accessor, int64_t cumul_offset, const std::vector< RoutingModel::RouteDimensionTravelInfo > *dimension_travel_info_per_route=nullptr)
CumulBoundsPropagator(const RoutingDimension *dimension)
DimensionSchedulingStatus OptimizeSingleRoute(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, const RouteDimensionTravelInfo &dimension_travel_info, 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)
std::vector< DimensionSchedulingStatus > OptimizeSingleRouteWithResources(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, const std::function< int64_t(int64_t, int64_t)> &transit_accessor, const RouteDimensionTravelInfo &dimension_travel_info, const std::vector< RoutingModel::ResourceGroup::Resource > &resources, const std::vector< int > &resource_indices, bool optimize_vehicle_costs, RoutingLinearSolverWrapper *solver, std::vector< int64_t > *costs_without_transits, std::vector< std::vector< int64_t >> *cumul_values, std::vector< std::vector< int64_t >> *break_values, bool clear_lp=true)
DimensionCumulOptimizerCore(const RoutingDimension *dimension, bool use_precedence_propagator)
DimensionSchedulingStatus ComputeSingleRouteSolutionCost(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, const RouteDimensionTravelInfo &dimension_travel_info, RoutingLinearSolverWrapper *solver, const std::vector< int64_t > &solution_cumul_values, const std::vector< int64_t > &solution_break_values, int64_t *cost, int64_t *transit_cost, int64_t *cost_offset=nullptr, bool reuse_previous_model_if_possible=true, bool clear_lp=false, bool clear_solution_constraints=true, absl::Duration *const solve_duration=nullptr)
DimensionSchedulingStatus Optimize(const std::function< int64_t(int64_t)> &next_accessor, const std::vector< RouteDimensionTravelInfo > &dimension_travel_info_per_route, RoutingLinearSolverWrapper *solver, std::vector< int64_t > *cumul_values, std::vector< int64_t > *break_values, std::vector< std::vector< int >> *resource_indices_per_group, int64_t *cost, int64_t *transit_cost, bool clear_lp=true)
DimensionSchedulingStatus OptimizeAndPackSingleRoute(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, const RouteDimensionTravelInfo &dimension_travel_info, const RoutingModel::ResourceGroup::Resource *resource, RoutingLinearSolverWrapper *solver, std::vector< int64_t > *cumul_values, std::vector< int64_t > *break_values)
DimensionSchedulingStatus OptimizeAndPack(const std::function< int64_t(int64_t)> &next_accessor, const std::vector< RouteDimensionTravelInfo > &dimension_travel_info_per_route, RoutingLinearSolverWrapper *solver, std::vector< int64_t > *cumul_values, std::vector< int64_t > *break_values, std::vector< std::vector< int >> *resource_indices_per_group)
We call domain any subset of Int64 = [kint64min, kint64max].
static Domain AllValues()
Returns the full domain Int64.
int64_t Min() const
Returns the min value of the domain.
int64_t Max() const
Returns the max value of the domain.
DimensionSchedulingStatus ComputePackedCumuls(const std::function< int64_t(int64_t)> &next_accessor, const std::vector< RoutingModel::RouteDimensionTravelInfo > &dimension_travel_info_per_route, std::vector< int64_t > *packed_cumuls, std::vector< int64_t > *packed_breaks, std::vector< std::vector< int >> *resource_indices_per_group)
DimensionSchedulingStatus ComputeCumulCostWithoutFixedTransits(const std::function< int64_t(int64_t)> &next_accessor, int64_t *optimal_cost_without_transits)
DimensionSchedulingStatus ComputeCumuls(const std::function< int64_t(int64_t)> &next_accessor, const std::vector< RoutingModel::RouteDimensionTravelInfo > &dimension_travel_info_per_route, std::vector< int64_t > *optimal_cumuls, std::vector< int64_t > *optimal_breaks, std::vector< std::vector< int >> *optimal_resource_indices_per_group)
virtual int64_t Min() const =0
virtual int64_t Max() const =0
std::vector< DimensionSchedulingStatus > ComputeRouteCumulCostsForResourcesWithoutFixedTransits(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, const std::function< int64_t(int64_t, int64_t)> &transit_accessor, const std::vector< RoutingModel::ResourceGroup::Resource > &resources, const std::vector< int > &resource_indices, bool optimize_vehicle_costs, std::vector< int64_t > *optimal_costs_without_transits, std::vector< std::vector< int64_t >> *optimal_cumuls, std::vector< std::vector< int64_t >> *optimal_breaks)
DimensionSchedulingStatus ComputeRouteCumulCost(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, int64_t *optimal_cost)
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 ComputeRouteSolutionCost(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, const RoutingModel::RouteDimensionTravelInfo &dimension_travel_info, const std::vector< int64_t > &solution_cumul_values, const std::vector< int64_t > &solution_break_values, int64_t *solution_cost, int64_t *cost_offset=nullptr, bool reuse_previous_model_if_possible=false, bool clear_lp=true, absl::Duration *solve_duration=nullptr)
DimensionSchedulingStatus ComputePackedRouteCumuls(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, const RoutingModel::RouteDimensionTravelInfo &dimension_travel_info, const RoutingModel::ResourceGroup::Resource *resource, std::vector< int64_t > *packed_cumuls, std::vector< int64_t > *packed_breaks)
DimensionSchedulingStatus ComputeRouteCumulsAndCost(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, const RoutingModel::RouteDimensionTravelInfo &dimension_travel_info, std::vector< int64_t > *optimal_cumuls, std::vector< int64_t > *optimal_breaks, int64_t *optimal_cost)
DimensionSchedulingStatus ComputeRouteCumuls(int vehicle, const std::function< int64_t(int64_t)> &next_accessor, const RoutingModel::RouteDimensionTravelInfo &dimension_travel_info, std::vector< int64_t > *optimal_cumuls, std::vector< int64_t > *optimal_breaks)
static int64_t FastInt64Round(double x)
Definition: mathutil.h:138
Dimensions represent quantities accumulated at nodes along the routes.
Definition: routing.h:2750
const std::vector< IntVar * > & cumuls() const
Like CumulVar(), TransitVar(), SlackVar() but return the whole variable vectors instead (indexed by i...
Definition: routing.h:2779
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:7256
RoutingModel * model() const
Returns the model on which the dimension was created.
Definition: routing.h:2754
int64_t GetGlobalOptimizerOffset() const
Definition: routing.h:3094
int64_t GetPickupToDeliveryLimitForPair(int pair_index, int pickup, int delivery) const
Definition: routing.cc:7517
bool HasCumulVarSoftLowerBound(int64_t index) const
Returns true if a soft lower bound has been set for a given variable index.
Definition: routing.cc:7295
BoundCost GetSoftSpanUpperBoundForVehicle(int vehicle) const
Definition: routing.h:3118
int64_t GetSpanCostCoefficientForVehicle(int vehicle) const
Definition: routing.h:3074
int64_t global_span_cost_coefficient() const
Definition: routing.h:3090
int64_t GetSpanUpperBoundForVehicle(int vehicle) const
Definition: routing.h:3066
bool HasBreakConstraints() const
Returns true if any break interval or break distance was defined.
Definition: routing.cc:7458
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:7117
int GetPreTravelEvaluatorOfVehicle(int vehicle) const
!defined(SWIGPYTHON)
Definition: routing.cc:7469
bool HasCumulVarSoftUpperBound(int64_t index) const
Returns true if a soft upper bound has been set for a given variable index.
Definition: routing.cc:7243
const std::vector< IntervalVar * > & GetBreakIntervalsOfVehicle(int vehicle) const
Returns the break intervals set by SetBreakIntervalsOfVehicle().
Definition: routing.cc:7462
IntVar * SlackVar(int64_t index) const
Definition: routing.h:2774
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:2833
int64_t GetLocalOptimizerOffsetForVehicle(int vehicle) const
Definition: routing.h:3098
const std::string & name() const
Returns the name of the dimension.
Definition: routing.h:3019
const std::vector< NodePrecedence > & GetNodePrecedences() const
Definition: routing.h:3056
int64_t GetCumulVarSoftUpperBound(int64_t index) const
Returns the soft upper bound of a cumul variable for a given variable index.
Definition: routing.cc:7248
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:7497
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:7308
int GetPostTravelEvaluatorOfVehicle(int vehicle) const
Definition: routing.cc:7475
int64_t GetCumulVarSoftLowerBound(int64_t index) const
Returns the soft lower bound of a cumul variable for a given variable index.
Definition: routing.cc:7300
const std::vector< SortedDisjointIntervalList > & forbidden_intervals() const
Returns forbidden intervals for each node.
Definition: routing.h:2785
virtual void SetParameters(const std::string &parameters)=0
virtual int64_t GetObjectiveValue() const =0
virtual double GetValue(int index) const =0
virtual DimensionSchedulingStatus Solve(absl::Duration duration_limit)=0
virtual bool SetVariableBounds(int index, int64_t lower_bound, int64_t upper_bound)=0
virtual void SetObjectiveCoefficient(int index, double coefficient)=0
virtual int64_t GetVariableUpperBound(int index) const =0
virtual int64_t GetVariableLowerBound(int index) const =0
A Resource sets attributes (costs/constraints) for a set of dimensions.
Definition: routing.h:458
A ResourceGroup defines a set of available Resources with attributes on one or multiple dimensions.
Definition: routing.h:437
bool VehicleRequiresAResource(int vehicle) const
Definition: routing.h:493
const std::vector< Resource > & GetResources() const
Definition: routing.h:497
const std::vector< int > & GetDimensionResourceGroupIndices(const RoutingDimension *dimension) const
Returns the indices of resource groups for this dimension.
Definition: routing.cc:1775
bool CheckLimit(absl::Duration offset=absl::ZeroDuration())
Returns true if the search limit has been crossed with the given time offset.
Definition: routing.h:1634
const IndexPairs & GetPickupAndDeliveryPairs() const
Returns pickup and delivery pairs currently in the model.
Definition: routing.h:912
int vehicles() const
Returns the number of vehicle routes in the model.
Definition: routing.h:1652
absl::Duration RemainingTime() const
Returns the time left in the search limit.
Definition: routing.h:1640
ArcIndex AddArcWithCapacityAndUnitCost(NodeIndex tail, NodeIndex head, FlowQuantity capacity, CostValue unit_cost)
FlowQuantity Flow(ArcIndex arc) const
void SetNodeSupply(NodeIndex node, FlowQuantity supply)
int64_t b
Block * next
SatParameters parameters
const std::string name
const Constraint * ct
int64_t value
int64_t coef
Definition: expr_array.cc:1875
absl::Status status
Definition: g_gurobi.cc:41
absl::Span< const double > coefficients
GRBmodel * model
static const int64_t kint64max
static const int64_t kint64min
int arc
int index
const Collection::value_type::second_type * FindOrNull(const Collection &collection, const typename Collection::value_type::first_type &key)
Definition: map_util.h:60
double FindBestScalingAndComputeErrors(const std::vector< double > &coefficients, const std::vector< double > &lower_bounds, const std::vector< double > &upper_bounds, int64_t max_absolute_activity, double wanted_absolute_activity_precision, double *relative_coeff_error, double *scaled_sum_error)
Collection of objects used to extend the Constraint Solver library.
int64_t CapAdd(int64_t x, int64_t y)
std::vector< SlopeAndYIntercept > PiecewiseLinearFormulationToSlopeAndYIntercept(const RoutingModel::RouteDimensionTravelInfo::TransitionInfo::PiecewiseLinearFormulation &pwl_function, int index_start, int index_end)
std::string ConstraintToString(const sat::ConstraintProto &constraint, const sat::CpModelProto &model_, bool show_enforcement=true)
std::string DomainToString(const ::google::protobuf::RepeatedField< int64_t > *domain)
int64_t ComputeBestVehicleToResourceAssignment(std::vector< int > vehicles, int num_resources, std::function< const std::vector< int64_t > *(int)> vehicle_to_resource_assignment_costs, std::vector< int > *resource_indices)
int64_t CapSub(int64_t x, int64_t y)
int64_t ComputeConvexPiecewiseLinearFormulationValue(const RoutingModel::RouteDimensionTravelInfo::TransitionInfo::PiecewiseLinearFormulation &pwl, int64_t x, double delta)
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:6774
std::vector< bool > SlopeAndYInterceptToConvexityRegions(const std::vector< SlopeAndYIntercept > &slope_and_y_intercept)
std::string Int64ToStr(int64_t number)
std::string VariablesToString(absl::flat_hash_map< std::string, std::pair< sat::IntegerVariableProto, int >> &variables, absl::flat_hash_map< std::string, std::vector< int >> &variable_instances, absl::flat_hash_map< std::string, absl::flat_hash_set< std::string >> &variable_childs, const sat::CpSolverResponse &response_, const std::string &variable, std::string prefix="")
bool ComputeVehicleToResourcesAssignmentCosts(int v, const RoutingModel::ResourceGroup &resource_group, const std::function< int64_t(int64_t)> &next_accessor, const std::function< int64_t(int64_t, int64_t)> &transit_accessor, bool optimize_vehicle_costs, LocalDimensionCumulOptimizer *lp_optimizer, LocalDimensionCumulOptimizer *mp_optimizer, std::vector< int64_t > *assignment_costs, std::vector< std::vector< int64_t >> *cumul_values, std::vector< std::vector< int64_t >> *break_values)
PiecewiseEvaluationStatus ComputePiecewiseLinearFormulationValue(const RoutingModel::RouteDimensionTravelInfo::TransitionInfo::PiecewiseLinearFormulation &pwl, int64_t x, int64_t *value, double delta)
std::string VariableToString(std::pair< sat::IntegerVariableProto, int > &variable_pair, const sat::CpSolverResponse &response_)
int64_t delta
Definition: resource.cc:1695
IntervalVar * interval
Definition: resource.cc:101
IntVar * upper_bound
Definition: routing.cc:1087
IntVar * lower_bound
Definition: routing.cc:1086
int64_t bound
int64_t tail
int64_t cost
#define SET_DEBUG_VARIABLE_NAME(solver, var, name)
double distance
std::vector< double > lower_bounds
std::vector< double > upper_bounds
Represents a closed interval [start, end].
Contains the information for a single transition on the route.
Definition: routing.h:1347
Contains the information needed by the solver to optimize a dimension's cumuls with travel-start depe...
Definition: routing.h:1345
#define VLOG(verboselevel)
Definition: vlog.h:39