OR-Tools  9.6
routing_cuts.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 <cstdint>
18 #include <functional>
19 #include <utility>
20 #include <vector>
21 
22 #include "ortools/base/logging.h"
23 #include "ortools/base/mathutil.h"
24 #include "ortools/sat/integer.h"
26 
27 namespace operations_research {
28 namespace sat {
29 
30 namespace {
31 
32 // Add a cut of the form Sum_{outgoing arcs from S} lp >= rhs_lower_bound.
33 //
34 // Note that we used to also add the same cut for the incoming arcs, but because
35 // of flow conservation on these problems, the outgoing flow is always the same
36 // as the incoming flow, so adding this extra cut doesn't seem relevant.
37 void AddOutgoingCut(
38  int num_nodes, int subset_size, const std::vector<bool>& in_subset,
39  const std::vector<int>& tails, const std::vector<int>& heads,
40  const std::vector<Literal>& literals,
41  const std::vector<double>& literal_lp_values, int64_t rhs_lower_bound,
43  LinearConstraintManager* manager, Model* model) {
44  // A node is said to be optional if it can be excluded from the subcircuit,
45  // in which case there is a self-loop on that node.
46  // If there are optional nodes, use extended formula:
47  // sum(cut) >= 1 - optional_loop_in - optional_loop_out
48  // where optional_loop_in's node is in subset, optional_loop_out's is out.
49  // TODO(user): Favor optional loops fixed to zero at root.
50  int num_optional_nodes_in = 0;
51  int num_optional_nodes_out = 0;
52  int optional_loop_in = -1;
53  int optional_loop_out = -1;
54  for (int i = 0; i < tails.size(); ++i) {
55  if (tails[i] != heads[i]) continue;
56  if (in_subset[tails[i]]) {
57  num_optional_nodes_in++;
58  if (optional_loop_in == -1 ||
59  literal_lp_values[i] < literal_lp_values[optional_loop_in]) {
60  optional_loop_in = i;
61  }
62  } else {
63  num_optional_nodes_out++;
64  if (optional_loop_out == -1 ||
65  literal_lp_values[i] < literal_lp_values[optional_loop_out]) {
66  optional_loop_out = i;
67  }
68  }
69  }
70 
71  // TODO(user): The lower bound for CVRP is computed assuming all nodes must be
72  // served, if it is > 1 we lower it to one in the presence of optional nodes.
73  if (num_optional_nodes_in + num_optional_nodes_out > 0) {
74  CHECK_GE(rhs_lower_bound, 1);
75  rhs_lower_bound = 1;
76  }
77 
78  LinearConstraintBuilder outgoing(model, IntegerValue(rhs_lower_bound),
80  double sum_outgoing = 0.0;
81 
82  // Add outgoing arcs, compute outgoing flow.
83  for (int i = 0; i < tails.size(); ++i) {
84  if (in_subset[tails[i]] && !in_subset[heads[i]]) {
85  sum_outgoing += literal_lp_values[i];
86  CHECK(outgoing.AddLiteralTerm(literals[i], IntegerValue(1)));
87  }
88  }
89 
90  // Support optional nodes if any.
91  if (num_optional_nodes_in + num_optional_nodes_out > 0) {
92  // When all optionals of one side are excluded in lp solution, no cut.
93  if (num_optional_nodes_in == subset_size &&
94  (optional_loop_in == -1 ||
95  literal_lp_values[optional_loop_in] > 1.0 - 1e-6)) {
96  return;
97  }
98  if (num_optional_nodes_out == num_nodes - subset_size &&
99  (optional_loop_out == -1 ||
100  literal_lp_values[optional_loop_out] > 1.0 - 1e-6)) {
101  return;
102  }
103 
104  // There is no mandatory node in subset, add optional_loop_in.
105  if (num_optional_nodes_in == subset_size) {
106  CHECK(
107  outgoing.AddLiteralTerm(literals[optional_loop_in], IntegerValue(1)));
108  sum_outgoing += literal_lp_values[optional_loop_in];
109  }
110 
111  // There is no mandatory node out of subset, add optional_loop_out.
112  if (num_optional_nodes_out == num_nodes - subset_size) {
113  CHECK(outgoing.AddLiteralTerm(literals[optional_loop_out],
114  IntegerValue(1)));
115  sum_outgoing += literal_lp_values[optional_loop_out];
116  }
117  }
118 
119  if (sum_outgoing < rhs_lower_bound - 1e-6) {
120  manager->AddCut(outgoing.Build(), "Circuit", lp_values);
121  }
122 }
123 
124 } // namespace
125 
126 void GenerateInterestingSubsets(int num_nodes,
127  const std::vector<std::pair<int, int>>& arcs,
128  int min_subset_size, int stop_at_num_components,
129  std::vector<int>* subset_data,
130  std::vector<absl::Span<const int>>* subsets) {
131  subset_data->resize(num_nodes);
132  subsets->clear();
133 
134  // We will do a union-find by adding one by one the arc of the lp solution
135  // in the order above. Every intermediate set during this construction will
136  // be a candidate for a cut.
137  //
138  // In parallel to the union-find, to efficiently reconstruct these sets (at
139  // most num_nodes), we construct a "decomposition forest" of the different
140  // connected components. Note that we don't exploit any asymmetric nature of
141  // the graph here. This is exactly the algo 6.3 in the book above.
142  int num_components = num_nodes;
143  std::vector<int> parent(num_nodes);
144  std::vector<int> root(num_nodes);
145  for (int i = 0; i < num_nodes; ++i) {
146  parent[i] = i;
147  root[i] = i;
148  }
149  auto get_root_and_compress_path = [&root](int node) {
150  int r = node;
151  while (root[r] != r) r = root[r];
152  while (root[node] != r) {
153  const int next = root[node];
154  root[node] = r;
155  node = next;
156  }
157  return r;
158  };
159  for (const auto& [initial_tail, initial_head] : arcs) {
160  if (num_components <= stop_at_num_components) break;
161  const int tail = get_root_and_compress_path(initial_tail);
162  const int head = get_root_and_compress_path(initial_head);
163  if (tail != head) {
164  // Update the decomposition forest, note that the number of nodes is
165  // growing.
166  const int new_node = parent.size();
167  parent.push_back(new_node);
168  parent[head] = new_node;
169  parent[tail] = new_node;
170  --num_components;
171 
172  // It is important that the union-find representative is the same node.
173  root.push_back(new_node);
174  root[head] = new_node;
175  root[tail] = new_node;
176  }
177  }
178 
179  // For each node in the decomposition forest, try to add a cut for the set
180  // formed by the nodes and its children. To do that efficiently, we first
181  // order the nodes so that for each node in a tree, the set of children forms
182  // a consecutive span in the subset_data vector. This vector just lists the
183  // nodes in the "pre-order" graph traversal order. The Spans will point inside
184  // the subset_data vector, it is why we initialize it once and for all.
185  int new_size = 0;
186  {
187  std::vector<absl::InlinedVector<int, 2>> graph(parent.size());
188  for (int i = 0; i < parent.size(); ++i) {
189  if (parent[i] != i) graph[parent[i]].push_back(i);
190  }
191  std::vector<int> queue;
192  std::vector<bool> seen(graph.size(), false);
193  std::vector<int> start_index(parent.size());
194  for (int i = 0; i < parent.size(); ++i) {
195  // Note that because of the way we constructed 'parent', the graph is a
196  // binary tree. This is not required for the correctness of the algorithm
197  // here though.
198  CHECK(graph[i].empty() || graph[i].size() == 2);
199  if (parent[i] != i) continue;
200 
201  // Explore the subtree rooted at node i.
202  CHECK(!seen[i]);
203  queue.push_back(i);
204  while (!queue.empty()) {
205  const int node = queue.back();
206  if (seen[node]) {
207  queue.pop_back();
208  // All the children of node are in the span [start, end) of the
209  // subset_data vector.
210  const int start = start_index[node];
211  if (new_size - start >= min_subset_size) {
212  subsets->emplace_back(&(*subset_data)[start], new_size - start);
213  }
214  continue;
215  }
216  seen[node] = true;
217  start_index[node] = new_size;
218  if (node < num_nodes) (*subset_data)[new_size++] = node;
219  for (const int child : graph[node]) {
220  if (!seen[child]) queue.push_back(child);
221  }
222  }
223  }
224  }
225 
226  DCHECK_EQ(new_size, num_nodes);
227 }
228 
229 // We roughly follow the algorithm described in section 6 of "The Traveling
230 // Salesman Problem, A computational Study", David L. Applegate, Robert E.
231 // Bixby, Vasek Chvatal, William J. Cook.
232 //
233 // Note that this is mainly a "symmetric" case algo, but it does still work for
234 // the asymmetric case.
236  int num_nodes, const std::vector<int>& tails, const std::vector<int>& heads,
237  const std::vector<Literal>& literals,
239  absl::Span<const int64_t> demands, int64_t capacity,
240  LinearConstraintManager* manager, Model* model) {
241  if (num_nodes <= 2) return;
242 
243  // We will collect only the arcs with a positive lp_values to speed up some
244  // computation below.
245  struct Arc {
246  int tail;
247  int head;
248  double lp_value;
249  };
250  std::vector<Arc> relevant_arcs;
251 
252  // Sort the arcs by non-increasing lp_values.
253  std::vector<double> literal_lp_values(literals.size());
254  std::vector<std::pair<double, int>> arc_by_decreasing_lp_values;
255  auto* encoder = model->GetOrCreate<IntegerEncoder>();
256  for (int i = 0; i < literals.size(); ++i) {
257  double lp_value;
258  const IntegerVariable direct_view = encoder->GetLiteralView(literals[i]);
259  if (direct_view != kNoIntegerVariable) {
260  lp_value = lp_values[direct_view];
261  } else {
262  lp_value =
263  1.0 - lp_values[encoder->GetLiteralView(literals[i].Negated())];
264  }
265  literal_lp_values[i] = lp_value;
266 
267  if (lp_value < 1e-6) continue;
268  relevant_arcs.push_back({tails[i], heads[i], lp_value});
269  arc_by_decreasing_lp_values.push_back({lp_value, i});
270  }
271  std::sort(arc_by_decreasing_lp_values.begin(),
272  arc_by_decreasing_lp_values.end(),
273  std::greater<std::pair<double, int>>());
274 
275  std::vector<std::pair<int, int>> ordered_arcs;
276  for (const auto& [score, arc] : arc_by_decreasing_lp_values) {
277  ordered_arcs.push_back({tails[arc], heads[arc]});
278  }
279  std::vector<int> subset_data;
280  std::vector<absl::Span<const int>> subsets;
281  GenerateInterestingSubsets(num_nodes, ordered_arcs,
282  /*min_subset_size=*/2,
283  /*stop_at_num_components=*/2, &subset_data,
284  &subsets);
285 
286  const int depot = 0;
287  if (!demands.empty()) {
288  // Add the depot so that we have a trivial bound on the number of
289  // vehicle.
290  subsets.push_back(absl::MakeSpan(&depot, 1));
291  }
292 
293  // Compute the total demands in order to know the minimum incoming/outgoing
294  // flow.
295  int64_t total_demands = 0;
296  if (!demands.empty()) {
297  for (const int64_t demand : demands) total_demands += demand;
298  }
299 
300  // Process each subsets and add any violated cut.
301  std::vector<bool> in_subset(num_nodes, false);
302  for (const absl::Span<const int> subset : subsets) {
303  DCHECK_GE(subset.size(), 1);
304  DCHECK_LT(subset.size(), num_nodes);
305 
306  // These fields will be left untouched if demands.empty().
307  bool contain_depot = false;
308  int64_t subset_demand = 0;
309 
310  // Initialize "in_subset" and the subset demands.
311  for (const int n : subset) {
312  in_subset[n] = true;
313  if (!demands.empty()) {
314  if (n == 0) contain_depot = true;
315  subset_demand += demands[n];
316  }
317  }
318 
319  // Compute a lower bound on the outgoing flow.
320  //
321  // TODO(user): This lower bound assume all nodes in subset must be served.
322  // If this is not the case, we are really defensive in AddOutgoingCut().
323  // Improve depending on where the self-loop are.
324  //
325  // TODO(user): It could be very interesting to see if this "min outgoing
326  // flow" cannot be automatically infered from the constraint in the
327  // precedence graph. This might work if we assume that any kind of path
328  // cumul constraint is encoded with constraints:
329  // [edge => value_head >= value_tail + edge_weight].
330  // We could take the minimum incoming edge weight per node in the set, and
331  // use the cumul variable domain to infer some capacity.
332  int64_t min_outgoing_flow = 1;
333  if (!demands.empty()) {
334  min_outgoing_flow =
335  contain_depot ? CeilOfRatio(total_demands - subset_demand, capacity)
336  : CeilOfRatio(subset_demand, capacity);
337  }
338 
339  // We still need to serve nodes with a demand of zero, and in the corner
340  // case where all node in subset have a zero demand, the formula above
341  // result in a min_outgoing_flow of zero.
342  min_outgoing_flow = std::max(min_outgoing_flow, int64_t{1});
343 
344  // Compute the current outgoing flow out of the subset.
345  //
346  // This can take a significant portion of the running time, it is why it is
347  // faster to do it only on arcs with non-zero lp values which should be in
348  // linear number rather than the total number of arc which can be quadratic.
349  //
350  // TODO(user): For the symmetric case there is an even faster algo. See if
351  // it can be generalized to the asymmetric one if become needed.
352  // Reference is algo 6.4 of the "The Traveling Salesman Problem" book
353  // mentionned above.
354  double outgoing_flow = 0.0;
355  for (const auto arc : relevant_arcs) {
356  if (in_subset[arc.tail] && !in_subset[arc.head]) {
357  outgoing_flow += arc.lp_value;
358  }
359  }
360 
361  // Add a cut if the current outgoing flow is not enough.
362  if (outgoing_flow < min_outgoing_flow - 1e-6) {
363  AddOutgoingCut(num_nodes, subset.size(), in_subset, tails, heads,
364  literals, literal_lp_values,
365  /*rhs_lower_bound=*/min_outgoing_flow, lp_values, manager,
366  model);
367  }
368 
369  // Sparse clean up.
370  for (const int n : subset) in_subset[n] = false;
371  }
372 }
373 
374 namespace {
375 
376 // Returns for each literal its integer view, or the view of its negation.
377 std::vector<IntegerVariable> GetAssociatedVariables(
378  const std::vector<Literal>& literals, Model* model) {
379  auto* encoder = model->GetOrCreate<IntegerEncoder>();
380  std::vector<IntegerVariable> result;
381  for (const Literal l : literals) {
382  const IntegerVariable direct_view = encoder->GetLiteralView(l);
383  if (direct_view != kNoIntegerVariable) {
384  result.push_back(direct_view);
385  } else {
386  result.push_back(encoder->GetLiteralView(l.Negated()));
387  DCHECK_NE(result.back(), kNoIntegerVariable);
388  }
389  }
390  return result;
391 }
392 
393 // This is especially useful to remove fixed self loop.
394 void FilterFalseArcsAtLevelZero(std::vector<int>& tails,
395  std::vector<int>& heads,
396  std::vector<Literal>& literals, Model* model) {
397  const Trail& trail = *model->GetOrCreate<Trail>();
398  if (trail.CurrentDecisionLevel() != 0) return;
399 
400  int new_size = 0;
401  const int size = static_cast<int>(tails.size());
402  const VariablesAssignment& assignment = trail.Assignment();
403  for (int i = 0; i < size; ++i) {
404  if (assignment.LiteralIsFalse(literals[i])) continue;
405  tails[new_size] = tails[i];
406  heads[new_size] = heads[i];
407  literals[new_size] = literals[i];
408  ++new_size;
409  }
410  if (new_size < size) {
411  tails.resize(new_size);
412  heads.resize(new_size);
413  literals.resize(new_size);
414  }
415 }
416 
417 } // namespace
418 
419 // We use a basic algorithm to detect components that are not connected to the
420 // rest of the graph in the LP solution, and add cuts to force some arcs to
421 // enter and leave this component from outside.
423  int num_nodes, std::vector<int> tails, std::vector<int> heads,
424  std::vector<Literal> literals, Model* model) {
425  CutGenerator result;
426  result.vars = GetAssociatedVariables(literals, model);
427  result.generate_cuts =
428  [num_nodes, tails, heads, literals, model](
430  LinearConstraintManager* manager) mutable {
431  FilterFalseArcsAtLevelZero(tails, heads, literals, model);
433  num_nodes, tails, heads, literals, lp_values,
434  /*demands=*/{}, /*capacity=*/0, manager, model);
435  return true;
436  };
437  return result;
438 }
439 
440 CutGenerator CreateCVRPCutGenerator(int num_nodes, std::vector<int> tails,
441  std::vector<int> heads,
442  std::vector<Literal> literals,
443  std::vector<int64_t> demands,
444  int64_t capacity, Model* model) {
445  CutGenerator result;
446  result.vars = GetAssociatedVariables(literals, model);
447  result.generate_cuts =
448  [num_nodes, tails, heads, demands, capacity, literals, model](
450  LinearConstraintManager* manager) mutable {
451  FilterFalseArcsAtLevelZero(tails, heads, literals, model);
452  SeparateSubtourInequalities(num_nodes, tails, heads, literals,
453  lp_values, demands, capacity, manager,
454  model);
455  return true;
456  };
457  return result;
458 }
459 
460 // This is really similar to SeparateSubtourInequalities, see the reference
461 // there.
463  int num_nodes, const std::vector<int>& tails, const std::vector<int>& heads,
464  const std::vector<AffineExpression>& arc_capacities,
465  std::function<void(const std::vector<bool>& in_subset,
466  IntegerValue* min_incoming_flow,
467  IntegerValue* min_outgoing_flow)>
468  get_flows,
470  LinearConstraintManager* manager, Model* model) {
471  // We will collect only the arcs with a positive lp capacity value to speed up
472  // some computation below.
473  struct Arc {
474  int tail;
475  int head;
476  double lp_value;
477  IntegerValue offset;
478  };
479  std::vector<Arc> relevant_arcs;
480 
481  // Often capacities have a coeff > 1.
482  // We currently exploit this if all coeff have a gcd > 1.
483  int64_t gcd = 0;
484 
485  // Sort the arcs by non-increasing lp_values.
486  std::vector<std::pair<double, int>> arc_by_decreasing_lp_values;
487  for (int i = 0; i < arc_capacities.size(); ++i) {
488  const double lp_value = arc_capacities[i].LpValue(lp_values);
489  if (!arc_capacities[i].IsConstant()) {
490  gcd = MathUtil::GCD64(gcd, std::abs(arc_capacities[i].coeff.value()));
491  }
492  if (lp_value < 1e-6 && arc_capacities[i].constant == 0) continue;
493  relevant_arcs.push_back(
494  {tails[i], heads[i], lp_value, arc_capacities[i].constant});
495  arc_by_decreasing_lp_values.push_back({lp_value, i});
496  }
497  if (gcd == 0) return;
498  std::sort(arc_by_decreasing_lp_values.begin(),
499  arc_by_decreasing_lp_values.end(),
500  std::greater<std::pair<double, int>>());
501 
502  std::vector<std::pair<int, int>> ordered_arcs;
503  for (const auto& [score, arc] : arc_by_decreasing_lp_values) {
504  if (tails[arc] == -1) continue;
505  if (heads[arc] == -1) continue;
506  ordered_arcs.push_back({tails[arc], heads[arc]});
507  }
508  std::vector<int> subset_data;
509  std::vector<absl::Span<const int>> subsets;
510  GenerateInterestingSubsets(num_nodes, ordered_arcs,
511  /*min_subset_size=*/1,
512  /*stop_at_num_components=*/1, &subset_data,
513  &subsets);
514 
515  // Process each subsets and add any violated cut.
516  std::vector<bool> in_subset(num_nodes, false);
517  for (const absl::Span<const int> subset : subsets) {
518  DCHECK(!subset.empty());
519  DCHECK_LE(subset.size(), num_nodes);
520 
521  // Initialize "in_subset" and the subset demands.
522  for (const int n : subset) in_subset[n] = true;
523 
524  IntegerValue min_incoming_flow;
525  IntegerValue min_outgoing_flow;
526  get_flows(in_subset, &min_incoming_flow, &min_outgoing_flow);
527 
528  // We will sum the offset of all incoming/outgoing arc capacities.
529  // Note that all arcs with a non-zero offset are part of relevant_arcs.
530  IntegerValue incoming_offset(0);
531  IntegerValue outgoing_offset(0);
532 
533  // Compute the current flow in and out of the subset.
534  //
535  // This can take a significant portion of the running time, it is why it is
536  // faster to do it only on arcs with non-zero lp values which should be in
537  // linear number rather than the total number of arc which can be quadratic.
538  double lp_outgoing_flow = 0.0;
539  double lp_incoming_flow = 0.0;
540  for (const auto arc : relevant_arcs) {
541  if (arc.tail != -1 && in_subset[arc.tail]) {
542  if (arc.head == -1 || !in_subset[arc.head]) {
543  incoming_offset += arc.offset;
544  lp_outgoing_flow += arc.lp_value;
545  }
546  } else {
547  if (arc.head != -1 && in_subset[arc.head]) {
548  outgoing_offset += arc.offset;
549  lp_incoming_flow += arc.lp_value;
550  }
551  }
552  }
553 
554  // If the gcd is greater than one, because all variables are integer we
555  // can round the flow lower bound to the next multiple of the gcd.
556  //
557  // TODO(user): Alternatively, try MIR heuristics if the coefficients in
558  // the capacities are not all the same.
559  if (gcd > 1) {
560  const IntegerValue test_incoming = min_incoming_flow - incoming_offset;
561  const IntegerValue new_incoming =
562  CeilRatio(test_incoming, IntegerValue(gcd)) * IntegerValue(gcd);
563  const IntegerValue incoming_delta = new_incoming - test_incoming;
564  if (incoming_delta > 0) min_incoming_flow += incoming_delta;
565  }
566  if (gcd > 1) {
567  const IntegerValue test_outgoing = min_outgoing_flow - outgoing_offset;
568  const IntegerValue new_outgoing =
569  CeilRatio(test_outgoing, IntegerValue(gcd)) * IntegerValue(gcd);
570  const IntegerValue outgoing_delta = new_outgoing - test_outgoing;
571  if (outgoing_delta > 0) min_outgoing_flow += outgoing_delta;
572  }
573 
574  if (lp_incoming_flow < ToDouble(min_incoming_flow) - 1e-6) {
575  VLOG(2) << "INCOMING CUT " << lp_incoming_flow
576  << " >= " << min_incoming_flow << " size " << subset.size()
577  << " offset " << incoming_offset << " gcd " << gcd;
578  LinearConstraintBuilder cut(model, min_incoming_flow, kMaxIntegerValue);
579  for (int i = 0; i < tails.size(); ++i) {
580  if ((tails[i] == -1 || !in_subset[tails[i]]) &&
581  (heads[i] != -1 && in_subset[heads[i]])) {
582  cut.AddTerm(arc_capacities[i], 1.0);
583  }
584  }
585  manager->AddCut(cut.Build(), "IncomingFlow", lp_values);
586  }
587 
588  if (lp_outgoing_flow < ToDouble(min_outgoing_flow) - 1e-6) {
589  VLOG(2) << "OUGOING CUT " << lp_outgoing_flow
590  << " >= " << min_outgoing_flow << " size " << subset.size()
591  << " offset " << outgoing_offset << " gcd " << gcd;
592  LinearConstraintBuilder cut(model, min_outgoing_flow, kMaxIntegerValue);
593  for (int i = 0; i < tails.size(); ++i) {
594  if ((tails[i] != -1 && in_subset[tails[i]]) &&
595  (heads[i] == -1 || !in_subset[heads[i]])) {
596  cut.AddTerm(arc_capacities[i], 1.0);
597  }
598  }
599  manager->AddCut(cut.Build(), "OutgoingFlow", lp_values);
600  }
601 
602  // Sparse clean up.
603  for (const int n : subset) in_subset[n] = false;
604  }
605 }
606 
608  int num_nodes, const std::vector<int>& tails, const std::vector<int>& heads,
609  const std::vector<AffineExpression>& arc_capacities,
610  std::function<void(const std::vector<bool>& in_subset,
611  IntegerValue* min_incoming_flow,
612  IntegerValue* min_outgoing_flow)>
613  get_flows,
614  Model* model) {
615  CutGenerator result;
616  for (const AffineExpression expr : arc_capacities) {
617  if (!expr.IsConstant()) result.vars.push_back(expr.var);
618  }
619  result.generate_cuts =
620  [=](const absl::StrongVector<IntegerVariable, double>& lp_values,
621  LinearConstraintManager* manager) {
622  SeparateFlowInequalities(num_nodes, tails, heads, arc_capacities,
623  get_flows, lp_values, manager, model);
624  return true;
625  };
626  return result;
627 }
628 
629 } // namespace sat
630 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
void push_back(const value_type &x)
static int64_t GCD64(int64_t x, int64_t y)
Definition: mathutil.h:107
const IntegerVariable GetLiteralView(Literal lit) const
Definition: integer.h:558
void AddTerm(IntegerVariable var, IntegerValue coeff)
bool AddCut(const LinearConstraint &ct, std::string type_name, const absl::StrongVector< IntegerVariable, double > &lp_solution, std::string extra_info="")
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:42
Block * next
GRBmodel * model
int arc
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
CutGenerator CreateStronglyConnectedGraphCutGenerator(int num_nodes, std::vector< int > tails, std::vector< int > heads, std::vector< Literal > literals, Model *model)
IntegerValue CeilRatio(IntegerValue dividend, IntegerValue positive_divisor)
Definition: integer.h:89
IntType CeilOfRatio(IntType numerator, IntType denominator)
Definition: sat/util.h:428
CutGenerator CreateCVRPCutGenerator(int num_nodes, std::vector< int > tails, std::vector< int > heads, std::vector< Literal > literals, std::vector< int64_t > demands, int64_t capacity, Model *model)
void GenerateInterestingSubsets(int num_nodes, const std::vector< std::pair< int, int >> &arcs, int min_subset_size, int stop_at_num_components, std::vector< int > *subset_data, std::vector< absl::Span< const int >> *subsets)
const IntegerVariable kNoIntegerVariable(-1)
void SeparateFlowInequalities(int num_nodes, const std::vector< int > &tails, const std::vector< int > &heads, const std::vector< AffineExpression > &arc_capacities, std::function< void(const std::vector< bool > &in_subset, IntegerValue *min_incoming_flow, IntegerValue *min_outgoing_flow)> get_flows, const absl::StrongVector< IntegerVariable, double > &lp_values, LinearConstraintManager *manager, Model *model)
void SeparateSubtourInequalities(int num_nodes, const std::vector< int > &tails, const std::vector< int > &heads, const std::vector< Literal > &literals, const absl::StrongVector< IntegerVariable, double > &lp_values, absl::Span< const int64_t > demands, int64_t capacity, LinearConstraintManager *manager, Model *model)
double ToDouble(IntegerValue value)
Definition: integer.h:77
CutGenerator CreateFlowCutGenerator(int num_nodes, const std::vector< int > &tails, const std::vector< int > &heads, const std::vector< AffineExpression > &arc_capacities, std::function< void(const std::vector< bool > &in_subset, IntegerValue *min_incoming_flow, IntegerValue *min_outgoing_flow)> get_flows, Model *model)
Collection of objects used to extend the Constraint Solver library.
int64_t demand
Definition: resource.cc:126
int64_t capacity
int64_t tail
int64_t head
int64_t start
std::vector< IntegerVariable > vars
Definition: cuts.h:50
std::function< bool(const absl::StrongVector< IntegerVariable, double > &lp_values, LinearConstraintManager *manager)> generate_cuts
Definition: cuts.h:54
#define VLOG(verboselevel)
Definition: vlog.h:39