OR-Tools  9.0
cuts.cc
Go to the documentation of this file.
1 // Copyright 2010-2021 Google LLC
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
14 #include "ortools/sat/cuts.h"
15 
16 #include <algorithm>
17 #include <cmath>
18 #include <cstdint>
19 #include <functional>
20 #include <limits>
21 #include <memory>
22 #include <utility>
23 #include <vector>
24 
27 #include "ortools/base/stl_util.h"
29 #include "ortools/sat/integer.h"
30 #include "ortools/sat/intervals.h"
32 #include "ortools/sat/sat_base.h"
34 
35 namespace operations_research {
36 namespace sat {
37 
38 namespace {
39 
40 // Minimum amount of violation of the cut constraint by the solution. This
41 // is needed to avoid numerical issues and adding cuts with minor effect.
42 const double kMinCutViolation = 1e-4;
43 
44 // Returns the lp value of a Literal.
45 double GetLiteralLpValue(
46  const Literal lit,
48  const IntegerEncoder* encoder) {
49  const IntegerVariable direct_view = encoder->GetLiteralView(lit);
50  if (direct_view != kNoIntegerVariable) {
51  return lp_values[direct_view];
52  }
53  const IntegerVariable opposite_view = encoder->GetLiteralView(lit.Negated());
54  DCHECK_NE(opposite_view, kNoIntegerVariable);
55  return 1.0 - lp_values[opposite_view];
56 }
57 
58 // Returns a constraint that disallow all given variables to be at their current
59 // upper bound. The arguments must form a non-trival constraint of the form
60 // sum terms (coeff * var) <= upper_bound.
61 LinearConstraint GenerateKnapsackCutForCover(
62  const std::vector<IntegerVariable>& vars,
63  const std::vector<IntegerValue>& coeffs, const IntegerValue upper_bound,
64  const IntegerTrail& integer_trail) {
65  CHECK_EQ(vars.size(), coeffs.size());
66  CHECK_GT(vars.size(), 0);
67  LinearConstraint cut;
68  IntegerValue cut_upper_bound = IntegerValue(0);
69  IntegerValue max_coeff = coeffs[0];
70  // slack = \sum_{i}(coeffs[i] * upper_bound[i]) - upper_bound.
71  IntegerValue slack = -upper_bound;
72  for (int i = 0; i < vars.size(); ++i) {
73  const IntegerValue var_upper_bound =
74  integer_trail.LevelZeroUpperBound(vars[i]);
75  cut_upper_bound += var_upper_bound;
76  cut.vars.push_back(vars[i]);
77  cut.coeffs.push_back(IntegerValue(1));
78  max_coeff = std::max(max_coeff, coeffs[i]);
79  slack += coeffs[i] * var_upper_bound;
80  }
81  CHECK_GT(slack, 0.0) << "Invalid cover for knapsack cut.";
82  cut_upper_bound -= CeilRatio(slack, max_coeff);
83  cut.lb = kMinIntegerValue;
84  cut.ub = cut_upper_bound;
85  VLOG(2) << "Generated Knapsack Constraint:" << cut.DebugString();
86  return cut;
87 }
88 
89 bool SolutionSatisfiesConstraint(
90  const LinearConstraint& constraint,
92  const double activity = ComputeActivity(constraint, lp_values);
93  const double tolerance = 1e-6;
94  return (activity <= constraint.ub.value() + tolerance &&
95  activity >= constraint.lb.value() - tolerance)
96  ? true
97  : false;
98 }
99 
100 bool SmallRangeAndAllCoefficientsMagnitudeAreTheSame(
101  const LinearConstraint& constraint, IntegerTrail* integer_trail) {
102  if (constraint.vars.empty()) return true;
103 
104  const int64_t magnitude = std::abs(constraint.coeffs[0].value());
105  for (int i = 1; i < constraint.coeffs.size(); ++i) {
106  const IntegerVariable var = constraint.vars[i];
107  if (integer_trail->LevelZeroUpperBound(var) -
108  integer_trail->LevelZeroLowerBound(var) >
109  1) {
110  return false;
111  }
112  if (std::abs(constraint.coeffs[i].value()) != magnitude) {
113  return false;
114  }
115  }
116  return true;
117 }
118 
119 bool AllVarsTakeIntegerValue(
120  const std::vector<IntegerVariable> vars,
122  for (IntegerVariable var : vars) {
123  if (std::abs(lp_values[var] - std::round(lp_values[var])) > 1e-6) {
124  return false;
125  }
126  }
127  return true;
128 }
129 
130 // Returns smallest cover size for the given constraint taking into account
131 // level zero bounds. Smallest Cover size is computed as follows.
132 // 1. Compute the upper bound if all variables are shifted to have zero lower
133 // bound.
134 // 2. Sort all terms (coefficient * shifted upper bound) in non decreasing
135 // order.
136 // 3. Add terms in cover until term sum is smaller or equal to upper bound.
137 // 4. Add the last item which violates the upper bound. This forms the smallest
138 // cover. Return the size of this cover.
139 int GetSmallestCoverSize(const LinearConstraint& constraint,
140  const IntegerTrail& integer_trail) {
141  IntegerValue ub = constraint.ub;
142  std::vector<IntegerValue> sorted_terms;
143  for (int i = 0; i < constraint.vars.size(); ++i) {
144  const IntegerValue coeff = constraint.coeffs[i];
145  const IntegerVariable var = constraint.vars[i];
146  const IntegerValue var_ub = integer_trail.LevelZeroUpperBound(var);
147  const IntegerValue var_lb = integer_trail.LevelZeroLowerBound(var);
148  ub -= var_lb * coeff;
149  sorted_terms.push_back(coeff * (var_ub - var_lb));
150  }
151  std::sort(sorted_terms.begin(), sorted_terms.end(),
152  std::greater<IntegerValue>());
153  int smallest_cover_size = 0;
154  IntegerValue sorted_term_sum = IntegerValue(0);
155  while (sorted_term_sum <= ub &&
156  smallest_cover_size < constraint.vars.size()) {
157  sorted_term_sum += sorted_terms[smallest_cover_size++];
158  }
159  return smallest_cover_size;
160 }
161 
162 bool ConstraintIsEligibleForLifting(const LinearConstraint& constraint,
163  const IntegerTrail& integer_trail) {
164  for (const IntegerVariable var : constraint.vars) {
165  if (integer_trail.LevelZeroLowerBound(var) != IntegerValue(0) ||
166  integer_trail.LevelZeroUpperBound(var) != IntegerValue(1)) {
167  return false;
168  }
169  }
170  return true;
171 }
172 } // namespace
173 
175  const LinearConstraint& constraint,
177  const std::vector<IntegerValue>& cut_vars_original_coefficients,
178  const IntegerTrail& integer_trail, TimeLimit* time_limit,
179  LinearConstraint* cut) {
180  std::set<IntegerVariable> vars_in_cut;
181  for (IntegerVariable var : cut->vars) {
182  vars_in_cut.insert(var);
183  }
184 
185  std::vector<std::pair<IntegerValue, IntegerVariable>> non_zero_vars;
186  std::vector<std::pair<IntegerValue, IntegerVariable>> zero_vars;
187  for (int i = 0; i < constraint.vars.size(); ++i) {
188  const IntegerVariable var = constraint.vars[i];
189  if (integer_trail.LevelZeroLowerBound(var) != IntegerValue(0) ||
190  integer_trail.LevelZeroUpperBound(var) != IntegerValue(1)) {
191  continue;
192  }
193  if (vars_in_cut.find(var) != vars_in_cut.end()) continue;
194  const IntegerValue coeff = constraint.coeffs[i];
195  if (lp_values[var] <= 1e-6) {
196  zero_vars.push_back({coeff, var});
197  } else {
198  non_zero_vars.push_back({coeff, var});
199  }
200  }
201 
202  // Decide lifting sequence (nonzeros, zeros in nonincreasing order
203  // of coefficient ).
204  std::sort(non_zero_vars.rbegin(), non_zero_vars.rend());
205  std::sort(zero_vars.rbegin(), zero_vars.rend());
206 
207  std::vector<std::pair<IntegerValue, IntegerVariable>> lifting_sequence(
208  std::move(non_zero_vars));
209 
210  lifting_sequence.insert(lifting_sequence.end(), zero_vars.begin(),
211  zero_vars.end());
212 
213  // Form Knapsack.
214  std::vector<double> lifting_profits;
215  std::vector<double> lifting_weights;
216  for (int i = 0; i < cut->vars.size(); ++i) {
217  lifting_profits.push_back(cut->coeffs[i].value());
218  lifting_weights.push_back(cut_vars_original_coefficients[i].value());
219  }
220 
221  // Lift the cut.
222  bool is_lifted = false;
223  bool is_solution_optimal = false;
224  KnapsackSolverForCuts knapsack_solver("Knapsack cut lifter");
225  for (auto entry : lifting_sequence) {
226  is_solution_optimal = false;
227  const IntegerValue var_original_coeff = entry.first;
228  const IntegerVariable var = entry.second;
229  const IntegerValue lifting_capacity = constraint.ub - entry.first;
230  if (lifting_capacity <= IntegerValue(0)) continue;
231  knapsack_solver.Init(lifting_profits, lifting_weights,
232  lifting_capacity.value());
233  knapsack_solver.set_node_limit(100);
234  // NOTE: Since all profits and weights are integer, solution of
235  // knapsack is also integer.
236  // TODO(user): Use an integer solver or heuristic.
237  knapsack_solver.Solve(time_limit, &is_solution_optimal);
238  const double knapsack_upper_bound =
239  std::round(knapsack_solver.GetUpperBound());
240  const IntegerValue cut_coeff = cut->ub - knapsack_upper_bound;
241  if (cut_coeff > IntegerValue(0)) {
242  is_lifted = true;
243  cut->vars.push_back(var);
244  cut->coeffs.push_back(cut_coeff);
245  lifting_profits.push_back(cut_coeff.value());
246  lifting_weights.push_back(var_original_coeff.value());
247  }
248  }
249  return is_lifted;
250 }
251 
253  const LinearConstraint& constraint,
255  const IntegerTrail& integer_trail) {
256  IntegerValue ub = constraint.ub;
257  LinearConstraint constraint_with_left_vars;
258  for (int i = 0; i < constraint.vars.size(); ++i) {
259  const IntegerVariable var = constraint.vars[i];
260  const IntegerValue var_ub = integer_trail.LevelZeroUpperBound(var);
261  const IntegerValue coeff = constraint.coeffs[i];
262  if (var_ub.value() - lp_values[var] <= 1.0 - kMinCutViolation) {
263  constraint_with_left_vars.vars.push_back(var);
264  constraint_with_left_vars.coeffs.push_back(coeff);
265  } else {
266  // Variable not in cut
267  const IntegerValue var_lb = integer_trail.LevelZeroLowerBound(var);
268  ub -= coeff * var_lb;
269  }
270  }
271  constraint_with_left_vars.ub = ub;
272  constraint_with_left_vars.lb = constraint.lb;
273  return constraint_with_left_vars;
274 }
275 
277  const IntegerTrail& integer_trail) {
278  IntegerValue term_sum = IntegerValue(0);
279  for (int i = 0; i < constraint.vars.size(); ++i) {
280  const IntegerVariable var = constraint.vars[i];
281  const IntegerValue var_ub = integer_trail.LevelZeroUpperBound(var);
282  const IntegerValue coeff = constraint.coeffs[i];
283  term_sum += coeff * var_ub;
284  }
285  if (term_sum <= constraint.ub) {
286  VLOG(2) << "Filtered by cover filter";
287  return true;
288  }
289  return false;
290 }
291 
293  const LinearConstraint& preprocessed_constraint,
295  const IntegerTrail& integer_trail) {
296  std::vector<double> variable_upper_bound_distances;
297  for (const IntegerVariable var : preprocessed_constraint.vars) {
298  const IntegerValue var_ub = integer_trail.LevelZeroUpperBound(var);
299  variable_upper_bound_distances.push_back(var_ub.value() - lp_values[var]);
300  }
301  // Compute the min cover size.
302  const int smallest_cover_size =
303  GetSmallestCoverSize(preprocessed_constraint, integer_trail);
304 
305  std::nth_element(
306  variable_upper_bound_distances.begin(),
307  variable_upper_bound_distances.begin() + smallest_cover_size - 1,
308  variable_upper_bound_distances.end());
309  double cut_lower_bound = 0.0;
310  for (int i = 0; i < smallest_cover_size; ++i) {
311  cut_lower_bound += variable_upper_bound_distances[i];
312  }
313  if (cut_lower_bound >= 1.0 - kMinCutViolation) {
314  VLOG(2) << "Filtered by kappa heuristic";
315  return true;
316  }
317  return false;
318 }
319 
320 double GetKnapsackUpperBound(std::vector<KnapsackItem> items,
321  const double capacity) {
322  // Sort items by value by weight ratio.
323  std::sort(items.begin(), items.end(), std::greater<KnapsackItem>());
324  double left_capacity = capacity;
325  double profit = 0.0;
326  for (const KnapsackItem item : items) {
327  if (item.weight <= left_capacity) {
328  profit += item.profit;
329  left_capacity -= item.weight;
330  } else {
331  profit += (left_capacity / item.weight) * item.profit;
332  break;
333  }
334  }
335  return profit;
336 }
337 
339  const LinearConstraint& constraint,
341  const IntegerTrail& integer_trail) {
342  std::vector<KnapsackItem> items;
343  double capacity = -constraint.ub.value() - 1.0;
344  double sum_variable_profit = 0;
345  for (int i = 0; i < constraint.vars.size(); ++i) {
346  const IntegerVariable var = constraint.vars[i];
347  const IntegerValue var_ub = integer_trail.LevelZeroUpperBound(var);
348  const IntegerValue var_lb = integer_trail.LevelZeroLowerBound(var);
349  const IntegerValue coeff = constraint.coeffs[i];
350  KnapsackItem item;
351  item.profit = var_ub.value() - lp_values[var];
352  item.weight = (coeff * (var_ub - var_lb)).value();
353  items.push_back(item);
354  capacity += (coeff * var_ub).value();
355  sum_variable_profit += item.profit;
356  }
357 
358  // Return early if the required upper bound is negative since all the profits
359  // are non negative.
360  if (sum_variable_profit - 1.0 + kMinCutViolation < 0.0) return false;
361 
362  // Get the knapsack upper bound.
363  const double knapsack_upper_bound =
364  GetKnapsackUpperBound(std::move(items), capacity);
365  if (knapsack_upper_bound < sum_variable_profit - 1.0 + kMinCutViolation) {
366  VLOG(2) << "Filtered by knapsack upper bound";
367  return true;
368  }
369  return false;
370 }
371 
373  const LinearConstraint& preprocessed_constraint,
375  const IntegerTrail& integer_trail) {
376  if (ConstraintIsTriviallyTrue(preprocessed_constraint, integer_trail)) {
377  return false;
378  }
379  if (CanBeFilteredUsingCutLowerBound(preprocessed_constraint, lp_values,
380  integer_trail)) {
381  return false;
382  }
383  if (CanBeFilteredUsingKnapsackUpperBound(preprocessed_constraint, lp_values,
384  integer_trail)) {
385  return false;
386  }
387  return true;
388 }
389 
391  std::vector<LinearConstraint>* knapsack_constraints,
392  IntegerTrail* integer_trail) {
393  // If all coefficient are the same, the generated knapsack cuts cannot be
394  // stronger than the constraint itself. However, when we substitute variables
395  // using the implication graph, this is not longer true. So we only skip
396  // constraints with same coeff and no substitutions.
397  if (SmallRangeAndAllCoefficientsMagnitudeAreTheSame(constraint,
398  integer_trail)) {
399  return;
400  }
401  if (constraint.ub < kMaxIntegerValue) {
402  LinearConstraint canonical_knapsack_form;
403 
404  // Negate the variables with negative coefficients.
405  for (int i = 0; i < constraint.vars.size(); ++i) {
406  const IntegerVariable var = constraint.vars[i];
407  const IntegerValue coeff = constraint.coeffs[i];
408  if (coeff > IntegerValue(0)) {
409  canonical_knapsack_form.AddTerm(var, coeff);
410  } else {
411  canonical_knapsack_form.AddTerm(NegationOf(var), -coeff);
412  }
413  }
414  canonical_knapsack_form.ub = constraint.ub;
415  canonical_knapsack_form.lb = kMinIntegerValue;
416  knapsack_constraints->push_back(canonical_knapsack_form);
417  }
418 
419  if (constraint.lb > kMinIntegerValue) {
420  LinearConstraint canonical_knapsack_form;
421 
422  // Negate the variables with positive coefficients.
423  for (int i = 0; i < constraint.vars.size(); ++i) {
424  const IntegerVariable var = constraint.vars[i];
425  const IntegerValue coeff = constraint.coeffs[i];
426  if (coeff > IntegerValue(0)) {
427  canonical_knapsack_form.AddTerm(NegationOf(var), coeff);
428  } else {
429  canonical_knapsack_form.AddTerm(var, -coeff);
430  }
431  }
432  canonical_knapsack_form.ub = -constraint.lb;
433  canonical_knapsack_form.lb = kMinIntegerValue;
434  knapsack_constraints->push_back(canonical_knapsack_form);
435  }
436 }
437 
438 // TODO(user): Move the cut generator into a class and reuse variables.
440  const std::vector<LinearConstraint>& base_constraints,
441  const std::vector<IntegerVariable>& vars, Model* model) {
442  CutGenerator result;
443  result.vars = vars;
444 
445  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
446  std::vector<LinearConstraint> knapsack_constraints;
447  for (const LinearConstraint& constraint : base_constraints) {
448  // There is often a lot of small linear base constraints and it doesn't seem
449  // super useful to generate cuts for constraints of size 2. Any valid cut
450  // of size 1 should be already infered by the propagation.
451  //
452  // TODO(user): The case of size 2 is a bit less clear. investigate more if
453  // it is useful.
454  if (constraint.vars.size() <= 2) continue;
455 
456  ConvertToKnapsackForm(constraint, &knapsack_constraints, integer_trail);
457  }
458  VLOG(1) << "#knapsack constraints: " << knapsack_constraints.size();
459 
460  // Note(user): for Knapsack cuts, it seems always advantageous to replace a
461  // variable X by a TIGHT lower bound of the form "coeff * binary + lb". This
462  // will not change "covers" but can only result in more violation by the
463  // current LP solution.
464  ImpliedBoundsProcessor implied_bounds_processor(
465  vars, integer_trail, model->GetOrCreate<ImpliedBounds>());
466 
467  // TODO(user): do not add generator if there are no knapsack constraints.
468  result.generate_cuts = [implied_bounds_processor, knapsack_constraints, vars,
469  model, integer_trail](
471  lp_values,
472  LinearConstraintManager* manager) mutable {
473  // TODO(user): When we use implied-bound substitution, we might still infer
474  // an interesting cut even if all variables are integer. See if we still
475  // want to skip all such constraints.
476  if (AllVarsTakeIntegerValue(vars, lp_values)) return;
477 
478  KnapsackSolverForCuts knapsack_solver(
479  "Knapsack on demand cover cut generator");
480  int64_t skipped_constraints = 0;
481  LinearConstraint mutable_constraint;
482 
483  // Iterate through all knapsack constraints.
484  implied_bounds_processor.ClearCache();
485  for (const LinearConstraint& constraint : knapsack_constraints) {
486  if (model->GetOrCreate<TimeLimit>()->LimitReached()) break;
487  VLOG(2) << "Processing constraint: " << constraint.DebugString();
488 
489  mutable_constraint = constraint;
490  implied_bounds_processor.ProcessUpperBoundedConstraint(
491  lp_values, &mutable_constraint);
492  MakeAllCoefficientsPositive(&mutable_constraint);
493 
494  const LinearConstraint preprocessed_constraint =
495  GetPreprocessedLinearConstraint(mutable_constraint, lp_values,
496  *integer_trail);
497  if (preprocessed_constraint.vars.empty()) continue;
498 
499  if (!CanFormValidKnapsackCover(preprocessed_constraint, lp_values,
500  *integer_trail)) {
501  skipped_constraints++;
502  continue;
503  }
504 
505  // Profits are (upper_bounds[i] - lp_values[i]) for knapsack variables.
506  std::vector<double> profits;
507  profits.reserve(preprocessed_constraint.vars.size());
508 
509  // Weights are (coeffs[i] * (upper_bound[i] - lower_bound[i])).
510  std::vector<double> weights;
511  weights.reserve(preprocessed_constraint.vars.size());
512 
513  double capacity = -preprocessed_constraint.ub.value() - 1.0;
514 
515  // Compute and store the sum of variable profits. This is the constant
516  // part of the objective of the problem we are trying to solve. Hence
517  // this part is not supplied to the knapsack_solver and is subtracted
518  // when we receive the knapsack solution.
519  double sum_variable_profit = 0;
520 
521  // Compute the profits, the weights and the capacity for the knapsack
522  // instance.
523  for (int i = 0; i < preprocessed_constraint.vars.size(); ++i) {
524  const IntegerVariable var = preprocessed_constraint.vars[i];
525  const double coefficient = preprocessed_constraint.coeffs[i].value();
526  const double var_ub = ToDouble(integer_trail->LevelZeroUpperBound(var));
527  const double var_lb = ToDouble(integer_trail->LevelZeroLowerBound(var));
528  const double variable_profit = var_ub - lp_values[var];
529  profits.push_back(variable_profit);
530 
531  sum_variable_profit += variable_profit;
532 
533  const double weight = coefficient * (var_ub - var_lb);
534  weights.push_back(weight);
535  capacity += weight + coefficient * var_lb;
536  }
537  if (capacity < 0.0) continue;
538 
539  std::vector<IntegerVariable> cut_vars;
540  std::vector<IntegerValue> cut_vars_original_coefficients;
541 
542  VLOG(2) << "Knapsack size: " << profits.size();
543  knapsack_solver.Init(profits, weights, capacity);
544 
545  // Set the time limit for the knapsack solver.
546  const double time_limit_for_knapsack_solver =
547  model->GetOrCreate<TimeLimit>()->GetTimeLeft();
548 
549  // Solve the instance and subtract the constant part to compute the
550  // sum_of_distance_to_ub_for_vars_in_cover.
551  // TODO(user): Consider solving the instance approximately.
552  bool is_solution_optimal = false;
553  knapsack_solver.set_solution_upper_bound_threshold(
554  sum_variable_profit - 1.0 + kMinCutViolation);
555  // TODO(user): Consider providing lower bound threshold as
556  // sum_variable_profit - 1.0 + kMinCutViolation.
557  // TODO(user): Set node limit for knapsack solver.
558  auto time_limit_for_solver =
559  absl::make_unique<TimeLimit>(time_limit_for_knapsack_solver);
560  const double sum_of_distance_to_ub_for_vars_in_cover =
561  sum_variable_profit -
562  knapsack_solver.Solve(time_limit_for_solver.get(),
563  &is_solution_optimal);
564  if (is_solution_optimal) {
565  VLOG(2) << "Knapsack Optimal solution found yay !";
566  }
567  if (time_limit_for_solver->LimitReached()) {
568  VLOG(1) << "Knapsack Solver run out of time limit.";
569  }
570  if (sum_of_distance_to_ub_for_vars_in_cover < 1.0 - kMinCutViolation) {
571  // Constraint is eligible for the cover.
572 
573  IntegerValue constraint_ub_for_cut = preprocessed_constraint.ub;
574  std::set<IntegerVariable> vars_in_cut;
575  for (int i = 0; i < preprocessed_constraint.vars.size(); ++i) {
576  const IntegerVariable var = preprocessed_constraint.vars[i];
577  const IntegerValue coefficient = preprocessed_constraint.coeffs[i];
578  if (!knapsack_solver.best_solution(i)) {
579  cut_vars.push_back(var);
580  cut_vars_original_coefficients.push_back(coefficient);
581  vars_in_cut.insert(var);
582  } else {
583  const IntegerValue var_lb = integer_trail->LevelZeroLowerBound(var);
584  constraint_ub_for_cut -= coefficient * var_lb;
585  }
586  }
587  LinearConstraint cut = GenerateKnapsackCutForCover(
588  cut_vars, cut_vars_original_coefficients, constraint_ub_for_cut,
589  *integer_trail);
590 
591  // Check if the constraint has only binary variables.
592  bool is_lifted = false;
593  if (ConstraintIsEligibleForLifting(cut, *integer_trail)) {
594  if (LiftKnapsackCut(mutable_constraint, lp_values,
595  cut_vars_original_coefficients, *integer_trail,
596  model->GetOrCreate<TimeLimit>(), &cut)) {
597  is_lifted = true;
598  }
599  }
600 
601  CHECK(!SolutionSatisfiesConstraint(cut, lp_values));
602  manager->AddCut(cut, is_lifted ? "LiftedKnapsack" : "Knapsack",
603  lp_values);
604  }
605  }
606  if (skipped_constraints > 0) {
607  VLOG(2) << "Skipped constraints: " << skipped_constraints;
608  }
609  };
610 
611  return result;
612 }
613 
614 // Compute the larger t <= max_t such that t * rhs_remainder >= divisor / 2.
615 //
616 // This is just a separate function as it is slightly faster to compute the
617 // result only once.
618 IntegerValue GetFactorT(IntegerValue rhs_remainder, IntegerValue divisor,
619  IntegerValue max_t) {
620  DCHECK_GE(max_t, 1);
621  return rhs_remainder == 0
622  ? max_t
623  : std::min(max_t, CeilRatio(divisor / 2, rhs_remainder));
624 }
625 
626 std::function<IntegerValue(IntegerValue)> GetSuperAdditiveRoundingFunction(
627  IntegerValue rhs_remainder, IntegerValue divisor, IntegerValue t,
628  IntegerValue max_scaling) {
629  DCHECK_GE(max_scaling, 1);
630 
631  // Adjust after the multiplication by t.
632  rhs_remainder *= t;
633  DCHECK_LT(rhs_remainder, divisor);
634 
635  // Make sure we don't have an integer overflow below. Note that we assume that
636  // divisor and the maximum coeff magnitude are not too different (maybe a
637  // factor 1000 at most) so that the final result will never overflow.
638  max_scaling =
639  std::min(max_scaling, std::numeric_limits<int64_t>::max() / divisor);
640 
641  const IntegerValue size = divisor - rhs_remainder;
642  if (max_scaling == 1 || size == 1) {
643  // TODO(user): Use everywhere a two step computation to avoid overflow?
644  // First divide by divisor, then multiply by t. For now, we limit t so that
645  // we never have an overflow instead.
646  return [t, divisor](IntegerValue coeff) {
647  return FloorRatio(t * coeff, divisor);
648  };
649  } else if (size <= max_scaling) {
650  return [size, rhs_remainder, t, divisor](IntegerValue coeff) {
651  const IntegerValue ratio = FloorRatio(t * coeff, divisor);
652  const IntegerValue remainder = t * coeff - ratio * divisor;
653  const IntegerValue diff = remainder - rhs_remainder;
654  return size * ratio + std::max(IntegerValue(0), diff);
655  };
656  } else if (max_scaling.value() * rhs_remainder.value() < divisor) {
657  // Because of our max_t limitation, the rhs_remainder might stay small.
658  //
659  // If it is "too small" we cannot use the code below because it will not be
660  // valid. So we just divide divisor into max_scaling bucket. The
661  // rhs_remainder will be in the bucket 0.
662  //
663  // Note(user): This seems the same as just increasing t, modulo integer
664  // overflows. Maybe we should just always do the computation like this so
665  // that we can use larger t even if coeff is close to kint64max.
666  return [t, divisor, max_scaling](IntegerValue coeff) {
667  const IntegerValue ratio = FloorRatio(t * coeff, divisor);
668  const IntegerValue remainder = t * coeff - ratio * divisor;
669  const IntegerValue bucket = FloorRatio(remainder * max_scaling, divisor);
670  return max_scaling * ratio + bucket;
671  };
672  } else {
673  // We divide (size = divisor - rhs_remainder) into (max_scaling - 1) buckets
674  // and increase the function by 1 / max_scaling for each of them.
675  //
676  // Note that for different values of max_scaling, we get a family of
677  // functions that do not dominate each others. So potentially, a max scaling
678  // as low as 2 could lead to the better cut (this is exactly the Letchford &
679  // Lodi function).
680  //
681  // Another intersting fact, is that if we want to compute the maximum alpha
682  // for a constraint with 2 terms like:
683  // divisor * Y + (ratio * divisor + remainder) * X
684  // <= rhs_ratio * divisor + rhs_remainder
685  // so that we have the cut:
686  // Y + (ratio + alpha) * X <= rhs_ratio
687  // This is the same as computing the maximum alpha such that for all integer
688  // X > 0 we have CeilRatio(alpha * divisor * X, divisor)
689  // <= CeilRatio(remainder * X - rhs_remainder, divisor).
690  // We can prove that this alpha is of the form (n - 1) / n, and it will
691  // be reached by such function for a max_scaling of n.
692  //
693  // TODO(user): This function is not always maximal when
694  // size % (max_scaling - 1) == 0. Improve?
695  return [size, rhs_remainder, t, divisor, max_scaling](IntegerValue coeff) {
696  const IntegerValue ratio = FloorRatio(t * coeff, divisor);
697  const IntegerValue remainder = t * coeff - ratio * divisor;
698  const IntegerValue diff = remainder - rhs_remainder;
699  const IntegerValue bucket =
700  diff > 0 ? CeilRatio(diff * (max_scaling - 1), size)
701  : IntegerValue(0);
702  return max_scaling * ratio + bucket;
703  };
704  }
705 }
706 
707 // TODO(user): This has been optimized a bit, but we can probably do even better
708 // as it still takes around 25% percent of the run time when all the cuts are on
709 // for the opm*mps.gz problems and others.
711  RoundingOptions options, const std::vector<double>& lp_values,
712  const std::vector<IntegerValue>& lower_bounds,
713  const std::vector<IntegerValue>& upper_bounds,
714  ImpliedBoundsProcessor* ib_processor, LinearConstraint* cut) {
715  const int size = lp_values.size();
716  if (size == 0) return;
717  CHECK_EQ(lower_bounds.size(), size);
718  CHECK_EQ(upper_bounds.size(), size);
719  CHECK_EQ(cut->vars.size(), size);
720  CHECK_EQ(cut->coeffs.size(), size);
722 
723  // To optimize the computation of the best divisor below, we only need to
724  // look at the indices with a shifted lp value that is not close to zero.
725  //
726  // TODO(user): sort by decreasing lp_values so that our early abort test in
727  // the critical loop below has more chance of returning early? I tried but it
728  // didn't seems to change much though.
729  relevant_indices_.clear();
730  relevant_lp_values_.clear();
731  relevant_coeffs_.clear();
732  relevant_bound_diffs_.clear();
733  divisors_.clear();
734  adjusted_coeffs_.clear();
735 
736  // Compute the maximum magnitude for non-fixed variables.
737  IntegerValue max_magnitude(0);
738  for (int i = 0; i < size; ++i) {
739  if (lower_bounds[i] == upper_bounds[i]) continue;
740  const IntegerValue magnitude = IntTypeAbs(cut->coeffs[i]);
741  max_magnitude = std::max(max_magnitude, magnitude);
742  }
743 
744  // Shift each variable using its lower/upper bound so that no variable can
745  // change sign. We eventually do a change of variable to its negation so
746  // that all variable are non-negative.
747  bool overflow = false;
748  change_sign_at_postprocessing_.assign(size, false);
749  for (int i = 0; i < size; ++i) {
750  if (cut->coeffs[i] == 0) continue;
751  const IntegerValue magnitude = IntTypeAbs(cut->coeffs[i]);
752 
753  // We might change them below.
754  IntegerValue lb = lower_bounds[i];
755  double lp_value = lp_values[i];
756 
757  const IntegerValue ub = upper_bounds[i];
758  const IntegerValue bound_diff =
759  IntegerValue(CapSub(ub.value(), lb.value()));
760 
761  // Note that since we use ToDouble() this code works fine with lb/ub at
762  // min/max integer value.
763  //
764  // TODO(user): Experiments with different heuristics. Other solver also
765  // seems to try a bunch of possibilities in a "postprocess" phase once
766  // the divisor is chosen. Try that.
767  {
768  // when the magnitude of the entry become smaller and smaller we bias
769  // towards a positive coefficient. This is because after rounding this
770  // will likely become zero instead of -divisor and we need the lp value
771  // to be really close to its bound to compensate.
772  const double lb_dist = std::abs(lp_value - ToDouble(lb));
773  const double ub_dist = std::abs(lp_value - ToDouble(ub));
774  const double bias =
775  std::max(1.0, 0.1 * ToDouble(max_magnitude) / ToDouble(magnitude));
776  if ((bias * lb_dist > ub_dist && cut->coeffs[i] < 0) ||
777  (lb_dist > bias * ub_dist && cut->coeffs[i] > 0)) {
778  change_sign_at_postprocessing_[i] = true;
779  cut->coeffs[i] = -cut->coeffs[i];
780  lp_value = -lp_value;
781  lb = -ub;
782  }
783  }
784 
785  // Always shift to lb.
786  // coeff * X = coeff * (X - shift) + coeff * shift.
787  lp_value -= ToDouble(lb);
788  if (!AddProductTo(-cut->coeffs[i], lb, &cut->ub)) {
789  overflow = true;
790  break;
791  }
792 
793  // Deal with fixed variable, no need to shift back in this case, we can
794  // just remove the term.
795  if (bound_diff == 0) {
796  cut->coeffs[i] = IntegerValue(0);
797  lp_value = 0.0;
798  }
799 
800  if (std::abs(lp_value) > 1e-2) {
801  relevant_coeffs_.push_back(cut->coeffs[i]);
802  relevant_indices_.push_back(i);
803  relevant_lp_values_.push_back(lp_value);
804  relevant_bound_diffs_.push_back(bound_diff);
805  divisors_.push_back(magnitude);
806  }
807  }
808 
809  // TODO(user): Maybe this shouldn't be called on such constraint.
810  if (relevant_coeffs_.empty()) {
811  VLOG(2) << "Issue, nothing to cut.";
812  *cut = LinearConstraint(IntegerValue(0), IntegerValue(0));
813  return;
814  }
815  CHECK_NE(max_magnitude, 0);
816 
817  // Our heuristic will try to generate a few different cuts, and we will keep
818  // the most violated one scaled by the l2 norm of the relevant position.
819  //
820  // TODO(user): Experiment for the best value of this initial violation
821  // threshold. Note also that we use the l2 norm on the restricted position
822  // here. Maybe we should change that? On that note, the L2 norm usage seems a
823  // bit weird to me since it grows with the number of term in the cut. And
824  // often, we already have a good cut, and we make it stronger by adding extra
825  // terms that do not change its activity.
826  //
827  // The discussion above only concern the best_scaled_violation initial value.
828  // The remainder_threshold allows to not consider cuts for which the final
829  // efficacity is clearly lower than 1e-3 (it is a bound, so we could generate
830  // cuts with a lower efficacity than this).
831  double best_scaled_violation = 0.01;
832  const IntegerValue remainder_threshold(max_magnitude / 1000);
833 
834  // The cut->ub might have grown quite a bit with the bound substitution, so
835  // we need to include it too since we will apply the rounding function on it.
836  max_magnitude = std::max(max_magnitude, IntTypeAbs(cut->ub));
837 
838  // Make sure that when we multiply the rhs or the coefficient by a factor t,
839  // we do not have an integer overflow. Actually, we need a bit more room
840  // because we might round down a value to the next multiple of
841  // max_magnitude.
842  const IntegerValue threshold = kMaxIntegerValue / 2;
843  if (overflow || max_magnitude >= threshold) {
844  VLOG(2) << "Issue, overflow.";
845  *cut = LinearConstraint(IntegerValue(0), IntegerValue(0));
846  return;
847  }
848  const IntegerValue max_t = threshold / max_magnitude;
849 
850  // There is no point trying twice the same divisor or a divisor that is too
851  // small. Note that we use a higher threshold than the remainder_threshold
852  // because we can boost the remainder thanks to our adjusting heuristic below
853  // and also because this allows to have cuts with a small range of
854  // coefficients.
855  //
856  // TODO(user): Note that the std::sort() is visible in some cpu profile.
857  {
858  int new_size = 0;
859  const IntegerValue divisor_threshold = max_magnitude / 10;
860  for (int i = 0; i < divisors_.size(); ++i) {
861  if (divisors_[i] <= divisor_threshold) continue;
862  divisors_[new_size++] = divisors_[i];
863  }
864  divisors_.resize(new_size);
865  }
866  gtl::STLSortAndRemoveDuplicates(&divisors_, std::greater<IntegerValue>());
867 
868  // TODO(user): Avoid quadratic algorithm? Note that we are quadratic in
869  // relevant_indices not the full cut->coeffs.size(), but this is still too
870  // much on some problems.
871  IntegerValue best_divisor(0);
872  for (const IntegerValue divisor : divisors_) {
873  // Skip if we don't have the potential to generate a good enough cut.
874  const IntegerValue initial_rhs_remainder =
875  cut->ub - FloorRatio(cut->ub, divisor) * divisor;
876  if (initial_rhs_remainder <= remainder_threshold) continue;
877 
878  IntegerValue temp_ub = cut->ub;
879  adjusted_coeffs_.clear();
880 
881  // We will adjust coefficient that are just under an exact multiple of
882  // divisor to an exact multiple. This is meant to get rid of small errors
883  // that appears due to rounding error in our exact computation of the
884  // initial constraint given to this class.
885  //
886  // Each adjustement will cause the initial_rhs_remainder to increase, and we
887  // do not want to increase it above divisor. Our threshold below guarantees
888  // this. Note that the higher the rhs_remainder becomes, the more the
889  // function f() has a chance to reduce the violation, so it is not always a
890  // good idea to use all the slack we have between initial_rhs_remainder and
891  // divisor.
892  //
893  // TODO(user): If possible, it might be better to complement these
894  // variables. Even if the adjusted lp_values end up larger, if we loose less
895  // when taking f(), then we will have a better violation.
896  const IntegerValue adjust_threshold =
897  (divisor - initial_rhs_remainder - 1) / IntegerValue(size);
898  if (adjust_threshold > 0) {
899  // Even before we finish the adjust, we can have a lower bound on the
900  // activily loss using this divisor, and so we can abort early. This is
901  // similar to what is done below in the function.
902  bool early_abort = false;
903  double loss_lb = 0.0;
904  const double threshold = ToDouble(initial_rhs_remainder);
905 
906  for (int i = 0; i < relevant_coeffs_.size(); ++i) {
907  // Compute the difference of coeff with the next multiple of divisor.
908  const IntegerValue coeff = relevant_coeffs_[i];
909  const IntegerValue remainder =
910  CeilRatio(coeff, divisor) * divisor - coeff;
911 
912  if (divisor - remainder <= initial_rhs_remainder) {
913  // We do not know exactly f() yet, but it will always round to the
914  // floor of the division by divisor in this case.
915  loss_lb += ToDouble(divisor - remainder) * relevant_lp_values_[i];
916  if (loss_lb >= threshold) {
917  early_abort = true;
918  break;
919  }
920  }
921 
922  // Adjust coeff of the form k * divisor - epsilon.
923  const IntegerValue diff = relevant_bound_diffs_[i];
924  if (remainder > 0 && remainder <= adjust_threshold &&
925  CapProd(diff.value(), remainder.value()) <= adjust_threshold) {
926  temp_ub += remainder * diff;
927  adjusted_coeffs_.push_back({i, coeff + remainder});
928  }
929  }
930 
931  if (early_abort) continue;
932  }
933 
934  // Create the super-additive function f().
935  const IntegerValue rhs_remainder =
936  temp_ub - FloorRatio(temp_ub, divisor) * divisor;
937  if (rhs_remainder == 0) continue;
938 
939  const auto f = GetSuperAdditiveRoundingFunction(
940  rhs_remainder, divisor, GetFactorT(rhs_remainder, divisor, max_t),
941  options.max_scaling);
942 
943  // As we round coefficients, we will compute the loss compared to the
944  // current scaled constraint activity. As soon as this loss crosses the
945  // slack, then we known that there is no violation and we can abort early.
946  //
947  // TODO(user): modulo the scaling, we could compute the exact threshold
948  // using our current best cut. Note that we also have to account the change
949  // in slack due to the adjust code above.
950  const double scaling = ToDouble(f(divisor)) / ToDouble(divisor);
951  const double threshold = scaling * ToDouble(rhs_remainder);
952  double loss = 0.0;
953 
954  // Apply f() to the cut and compute the cut violation. Note that it is
955  // okay to just look at the relevant indices since the other have a lp
956  // value which is almost zero. Doing it like this is faster, and even if
957  // the max_magnitude might be off it should still be relevant enough.
958  double violation = -ToDouble(f(temp_ub));
959  double l2_norm = 0.0;
960  bool early_abort = false;
961  int adjusted_coeffs_index = 0;
962  for (int i = 0; i < relevant_coeffs_.size(); ++i) {
963  IntegerValue coeff = relevant_coeffs_[i];
964 
965  // Adjust coeff according to our previous computation if needed.
966  if (adjusted_coeffs_index < adjusted_coeffs_.size() &&
967  adjusted_coeffs_[adjusted_coeffs_index].first == i) {
968  coeff = adjusted_coeffs_[adjusted_coeffs_index].second;
969  adjusted_coeffs_index++;
970  }
971 
972  if (coeff == 0) continue;
973  const IntegerValue new_coeff = f(coeff);
974  const double new_coeff_double = ToDouble(new_coeff);
975  const double lp_value = relevant_lp_values_[i];
976 
977  l2_norm += new_coeff_double * new_coeff_double;
978  violation += new_coeff_double * lp_value;
979  loss += (scaling * ToDouble(coeff) - new_coeff_double) * lp_value;
980  if (loss >= threshold) {
981  early_abort = true;
982  break;
983  }
984  }
985  if (early_abort) continue;
986 
987  // Here we scale by the L2 norm over the "relevant" positions. This seems
988  // to work slighly better in practice.
989  violation /= sqrt(l2_norm);
990  if (violation > best_scaled_violation) {
991  best_scaled_violation = violation;
992  best_divisor = divisor;
993  }
994  }
995 
996  if (best_divisor == 0) {
997  *cut = LinearConstraint(IntegerValue(0), IntegerValue(0));
998  return;
999  }
1000 
1001  // Adjust coefficients.
1002  //
1003  // TODO(user): It might make sense to also adjust the one with a small LP
1004  // value, but then the cut will be slighlty different than the one we computed
1005  // above. Try with and without maybe?
1006  const IntegerValue initial_rhs_remainder =
1007  cut->ub - FloorRatio(cut->ub, best_divisor) * best_divisor;
1008  const IntegerValue adjust_threshold =
1009  (best_divisor - initial_rhs_remainder - 1) / IntegerValue(size);
1010  if (adjust_threshold > 0) {
1011  for (int i = 0; i < relevant_indices_.size(); ++i) {
1012  const int index = relevant_indices_[i];
1013  const IntegerValue diff = relevant_bound_diffs_[i];
1014  if (diff > adjust_threshold) continue;
1015 
1016  // Adjust coeff of the form k * best_divisor - epsilon.
1017  const IntegerValue coeff = cut->coeffs[index];
1018  const IntegerValue remainder =
1019  CeilRatio(coeff, best_divisor) * best_divisor - coeff;
1020  if (CapProd(diff.value(), remainder.value()) <= adjust_threshold) {
1021  cut->ub += remainder * diff;
1022  cut->coeffs[index] += remainder;
1023  }
1024  }
1025  }
1026 
1027  // Create the super-additive function f().
1028  //
1029  // TODO(user): Try out different rounding function and keep the best. We can
1030  // change max_t and max_scaling. It might not be easy to choose which cut is
1031  // the best, but we can at least know for sure if one dominate the other
1032  // completely. That is, if for all coeff f(coeff)/f(divisor) is greater than
1033  // or equal to the same value for another function f.
1034  const IntegerValue rhs_remainder =
1035  cut->ub - FloorRatio(cut->ub, best_divisor) * best_divisor;
1036  IntegerValue factor_t = GetFactorT(rhs_remainder, best_divisor, max_t);
1037  auto f = GetSuperAdditiveRoundingFunction(rhs_remainder, best_divisor,
1038  factor_t, options.max_scaling);
1039 
1040  // Look amongst all our possible function f() for one that dominate greedily
1041  // our current best one. Note that we prefer lower scaling factor since that
1042  // result in a cut with lower coefficients.
1043  remainders_.clear();
1044  for (int i = 0; i < size; ++i) {
1045  const IntegerValue coeff = cut->coeffs[i];
1046  const IntegerValue r =
1047  coeff - FloorRatio(coeff, best_divisor) * best_divisor;
1048  if (r > rhs_remainder) remainders_.push_back(r);
1049  }
1050  gtl::STLSortAndRemoveDuplicates(&remainders_);
1051  if (remainders_.size() <= 100) {
1052  best_rs_.clear();
1053  for (const IntegerValue r : remainders_) {
1054  best_rs_.push_back(f(r));
1055  }
1056  IntegerValue best_d = f(best_divisor);
1057 
1058  // Note that the complexity seems high 100 * 2 * options.max_scaling, but
1059  // this only run on cuts that are already efficient and the inner loop tend
1060  // to abort quickly. I didn't see this code in the cpu profile so far.
1061  for (const IntegerValue t :
1062  {IntegerValue(1), GetFactorT(rhs_remainder, best_divisor, max_t)}) {
1063  for (IntegerValue s(2); s <= options.max_scaling; ++s) {
1064  const auto g =
1065  GetSuperAdditiveRoundingFunction(rhs_remainder, best_divisor, t, s);
1066  int num_strictly_better = 0;
1067  rs_.clear();
1068  const IntegerValue d = g(best_divisor);
1069  for (int i = 0; i < best_rs_.size(); ++i) {
1070  const IntegerValue temp = g(remainders_[i]);
1071  if (temp * best_d < best_rs_[i] * d) break;
1072  if (temp * best_d > best_rs_[i] * d) num_strictly_better++;
1073  rs_.push_back(temp);
1074  }
1075  if (rs_.size() == best_rs_.size() && num_strictly_better > 0) {
1076  f = g;
1077  factor_t = t;
1078  best_rs_ = rs_;
1079  best_d = d;
1080  }
1081  }
1082  }
1083  }
1084 
1085  // Starts to apply f() to the cut. We only apply it to the rhs here, the
1086  // coefficient will be done after the potential lifting of some Booleans.
1087  cut->ub = f(cut->ub);
1088  tmp_terms_.clear();
1089 
1090  // Lift some implied bounds Booleans. Note that we will add them after
1091  // "size" so they will be ignored in the second loop below.
1092  num_lifted_booleans_ = 0;
1093  if (ib_processor != nullptr) {
1094  for (int i = 0; i < size; ++i) {
1095  const IntegerValue coeff = cut->coeffs[i];
1096  if (coeff == 0) continue;
1097 
1098  IntegerVariable var = cut->vars[i];
1099  if (change_sign_at_postprocessing_[i]) {
1100  var = NegationOf(var);
1101  }
1102 
1104  ib_processor->GetCachedImpliedBoundInfo(var);
1105 
1106  // Avoid overflow.
1107  if (CapProd(CapProd(std::abs(coeff.value()), factor_t.value()),
1108  info.bound_diff.value()) ==
1110  continue;
1111  }
1112 
1113  // Because X = bound_diff * B + S
1114  // We can replace coeff * X by the expression before applying f:
1115  // = f(coeff * bound_diff) * B + f(coeff) * [X - bound_diff * B]
1116  // = f(coeff) * X + (f(coeff * bound_diff) - f(coeff) * bound_diff] B
1117  // So we can "lift" B into the cut.
1118  const IntegerValue coeff_b =
1119  f(coeff * info.bound_diff) - f(coeff) * info.bound_diff;
1120  CHECK_GE(coeff_b, 0);
1121  if (coeff_b == 0) continue;
1122 
1123  ++num_lifted_booleans_;
1124  if (info.is_positive) {
1125  tmp_terms_.push_back({info.bool_var, coeff_b});
1126  } else {
1127  tmp_terms_.push_back({info.bool_var, -coeff_b});
1128  cut->ub = CapAdd(-coeff_b.value(), cut->ub.value());
1129  }
1130  }
1131  }
1132 
1133  // Apply f() to the cut.
1134  //
1135  // Remove the bound shifts so the constraint is expressed in the original
1136  // variables.
1137  for (int i = 0; i < size; ++i) {
1138  IntegerValue coeff = cut->coeffs[i];
1139  if (coeff == 0) continue;
1140  coeff = f(coeff);
1141  if (coeff == 0) continue;
1142  if (change_sign_at_postprocessing_[i]) {
1143  cut->ub = IntegerValue(
1144  CapAdd((coeff * -upper_bounds[i]).value(), cut->ub.value()));
1145  tmp_terms_.push_back({cut->vars[i], -coeff});
1146  } else {
1147  cut->ub = IntegerValue(
1148  CapAdd((coeff * lower_bounds[i]).value(), cut->ub.value()));
1149  tmp_terms_.push_back({cut->vars[i], coeff});
1150  }
1151  }
1152 
1153  // Basic post-processing.
1154  CleanTermsAndFillConstraint(&tmp_terms_, cut);
1155  RemoveZeroTerms(cut);
1156  DivideByGCD(cut);
1157 }
1158 
1160  const LinearConstraint base_ct, const std::vector<double>& lp_values,
1161  const std::vector<IntegerValue>& lower_bounds,
1162  const std::vector<IntegerValue>& upper_bounds) {
1163  const int base_size = lp_values.size();
1164 
1165  // Fill terms with a rewrite of the base constraint where all coeffs &
1166  // variables are positive by using either (X - LB) or (UB - X) as new
1167  // variables.
1168  terms_.clear();
1169  IntegerValue rhs = base_ct.ub;
1170  IntegerValue sum_of_diff(0);
1171  IntegerValue max_base_magnitude(0);
1172  for (int i = 0; i < base_size; ++i) {
1173  const IntegerValue coeff = base_ct.coeffs[i];
1174  const IntegerValue positive_coeff = IntTypeAbs(coeff);
1175  max_base_magnitude = std::max(max_base_magnitude, positive_coeff);
1176  const IntegerValue bound_diff = upper_bounds[i] - lower_bounds[i];
1177  if (!AddProductTo(positive_coeff, bound_diff, &sum_of_diff)) {
1178  return false;
1179  }
1180  const IntegerValue diff = positive_coeff * bound_diff;
1181  if (coeff > 0) {
1182  if (!AddProductTo(-coeff, lower_bounds[i], &rhs)) return false;
1183  terms_.push_back(
1184  {i, ToDouble(upper_bounds[i]) - lp_values[i], positive_coeff, diff});
1185  } else {
1186  if (!AddProductTo(-coeff, upper_bounds[i], &rhs)) return false;
1187  terms_.push_back(
1188  {i, lp_values[i] - ToDouble(lower_bounds[i]), positive_coeff, diff});
1189  }
1190  }
1191 
1192  // Try a simple cover heuristic.
1193  // Look for violated CUT of the form: sum (UB - X) or (X - LB) >= 1.
1194  double activity = 0.0;
1195  int new_size = 0;
1196  std::sort(terms_.begin(), terms_.end(), [](const Term& a, const Term& b) {
1197  if (a.dist_to_max_value == b.dist_to_max_value) {
1198  // Prefer low coefficients if the distance is the same.
1199  return a.positive_coeff < b.positive_coeff;
1200  }
1201  return a.dist_to_max_value < b.dist_to_max_value;
1202  });
1203  for (int i = 0; i < terms_.size(); ++i) {
1204  const Term& term = terms_[i];
1205  activity += term.dist_to_max_value;
1206 
1207  // As an heuristic we select all the term so that the sum of distance
1208  // to the upper bound is <= 1.0. If the corresponding rhs is negative, then
1209  // we will have a cut of violation at least 0.0. Note that this violation
1210  // can be improved by the lifting.
1211  //
1212  // TODO(user): experiment with different threshold (even greater than one).
1213  // Or come up with an algo that incorporate the lifting into the heuristic.
1214  if (activity > 1.0) {
1215  new_size = i; // before this entry.
1216  break;
1217  }
1218 
1219  rhs -= term.diff;
1220  }
1221 
1222  // If the rhs is now negative, we have a cut.
1223  //
1224  // Note(user): past this point, now that a given "base" cover has been chosen,
1225  // we basically compute the cut (of the form sum X <= bound) with the maximum
1226  // possible violation. Note also that we lift as much as possible, so we don't
1227  // necessarilly optimize for the cut efficacity though. But we do get a
1228  // stronger cut.
1229  if (rhs >= 0) return false;
1230  if (new_size == 0) return false;
1231 
1232  // Transform to a minimal cover. We want to greedily remove the largest coeff
1233  // first, so we have more chance for the "lifting" below which can increase
1234  // the cut violation. If the coeff are the same, we prefer to remove high
1235  // distance from upper bound first.
1236  //
1237  // We compute the cut at the same time.
1238  terms_.resize(new_size);
1239  std::sort(terms_.begin(), terms_.end(), [](const Term& a, const Term& b) {
1240  if (a.positive_coeff == b.positive_coeff) {
1241  return a.dist_to_max_value > b.dist_to_max_value;
1242  }
1243  return a.positive_coeff > b.positive_coeff;
1244  });
1245  in_cut_.assign(base_ct.vars.size(), false);
1246  cut_.ClearTerms();
1247  cut_.lb = kMinIntegerValue;
1248  cut_.ub = IntegerValue(-1);
1249  IntegerValue max_coeff(0);
1250  for (const Term term : terms_) {
1251  if (term.diff + rhs < 0) {
1252  rhs += term.diff;
1253  continue;
1254  }
1255  in_cut_[term.index] = true;
1256  max_coeff = std::max(max_coeff, term.positive_coeff);
1257  cut_.vars.push_back(base_ct.vars[term.index]);
1258  if (base_ct.coeffs[term.index] > 0) {
1259  cut_.coeffs.push_back(IntegerValue(1));
1260  cut_.ub += upper_bounds[term.index];
1261  } else {
1262  cut_.coeffs.push_back(IntegerValue(-1));
1263  cut_.ub -= lower_bounds[term.index];
1264  }
1265  }
1266 
1267  // In case the max_coeff variable is not binary, it might be possible to
1268  // tighten the cut a bit more.
1269  //
1270  // Note(user): I never observed this on the miplib so far.
1271  if (max_coeff == 0) return true;
1272  if (max_coeff < -rhs) {
1273  const IntegerValue m = FloorRatio(-rhs - 1, max_coeff);
1274  rhs += max_coeff * m;
1275  cut_.ub -= m;
1276  }
1277  CHECK_LT(rhs, 0);
1278 
1279  // Lift all at once the variables not used in the cover.
1280  //
1281  // We have a cut of the form sum_i X_i <= b that we will lift into
1282  // sum_i scaling X_i + sum f(base_coeff_j) X_j <= b * scaling.
1283  //
1284  // Using the super additivity of f() and how we construct it,
1285  // we know that: sum_j base_coeff_j X_j <= N * max_coeff + (max_coeff - slack)
1286  // implies that: sum_j f(base_coeff_j) X_j <= N * scaling.
1287  //
1288  // 1/ cut > b -(N+1) => original sum + (N+1) * max_coeff >= rhs + slack
1289  // 2/ rewrite 1/ as : scaling * cut >= scaling * b - scaling * N => ...
1290  // 3/ lift > N * scaling => lift_sum > N * max_coeff + (max_coeff - slack)
1291  // And adding 2/ + 3/ we prove what we want:
1292  // cut * scaling + lift > b * scaling => original_sum + lift_sum > rhs.
1293  const IntegerValue slack = -rhs;
1294  const IntegerValue remainder = max_coeff - slack;
1295  max_base_magnitude = std::max(max_base_magnitude, IntTypeAbs(cut_.ub));
1296  const IntegerValue max_scaling(std::min(
1297  IntegerValue(60), FloorRatio(kMaxIntegerValue, max_base_magnitude)));
1298  const auto f = GetSuperAdditiveRoundingFunction(remainder, max_coeff,
1299  IntegerValue(1), max_scaling);
1300 
1301  const IntegerValue scaling = f(max_coeff);
1302  if (scaling > 1) {
1303  for (int i = 0; i < cut_.coeffs.size(); ++i) cut_.coeffs[i] *= scaling;
1304  cut_.ub *= scaling;
1305  }
1306 
1307  num_lifting_ = 0;
1308  for (int i = 0; i < base_size; ++i) {
1309  if (in_cut_[i]) continue;
1310  const IntegerValue positive_coeff = IntTypeAbs(base_ct.coeffs[i]);
1311  const IntegerValue new_coeff = f(positive_coeff);
1312  if (new_coeff == 0) continue;
1313 
1314  ++num_lifting_;
1315  if (base_ct.coeffs[i] > 0) {
1316  // Add new_coeff * (X - LB)
1317  cut_.coeffs.push_back(new_coeff);
1318  cut_.vars.push_back(base_ct.vars[i]);
1319  cut_.ub += lower_bounds[i] * new_coeff;
1320  } else {
1321  // Add new_coeff * (UB - X)
1322  cut_.coeffs.push_back(-new_coeff);
1323  cut_.vars.push_back(base_ct.vars[i]);
1324  cut_.ub -= upper_bounds[i] * new_coeff;
1325  }
1326  }
1327 
1328  if (scaling > 1) DivideByGCD(&cut_);
1329  return true;
1330 }
1331 
1333  IntegerVariable x,
1334  IntegerVariable y,
1335  Model* model) {
1336  CutGenerator result;
1337  result.vars = {z, x, y};
1338 
1339  IntegerTrail* const integer_trail = model->GetOrCreate<IntegerTrail>();
1340  result.generate_cuts =
1341  [z, x, y, integer_trail](
1343  LinearConstraintManager* manager) {
1344  const int64_t x_lb = integer_trail->LevelZeroLowerBound(x).value();
1345  const int64_t x_ub = integer_trail->LevelZeroUpperBound(x).value();
1346  const int64_t y_lb = integer_trail->LevelZeroLowerBound(y).value();
1347  const int64_t y_ub = integer_trail->LevelZeroUpperBound(y).value();
1348 
1349  // TODO(user): Compute a better bound (int_max / 4 ?).
1350  const int64_t kMaxSafeInteger = (int64_t{1} << 53) - 1;
1351 
1352  if (CapProd(x_ub, y_ub) >= kMaxSafeInteger) {
1353  VLOG(3) << "Potential overflow in PositiveMultiplicationCutGenerator";
1354  return;
1355  }
1356 
1357  const double x_lp_value = lp_values[x];
1358  const double y_lp_value = lp_values[y];
1359  const double z_lp_value = lp_values[z];
1360 
1361  // TODO(user): As the bounds change monotonically, these cuts
1362  // dominate any previous one. try to keep a reference to the cut and
1363  // replace it. Alternatively, add an API for a level-zero bound change
1364  // callback.
1365 
1366  // Cut -z + x_coeff * x + y_coeff* y <= rhs
1367  auto try_add_above_cut =
1368  [manager, z_lp_value, x_lp_value, y_lp_value, x, y, z, &lp_values](
1369  int64_t x_coeff, int64_t y_coeff, int64_t rhs) {
1370  if (-z_lp_value + x_lp_value * x_coeff + y_lp_value * y_coeff >=
1371  rhs + kMinCutViolation) {
1372  LinearConstraint cut;
1373  cut.vars.push_back(z);
1374  cut.coeffs.push_back(IntegerValue(-1));
1375  if (x_coeff != 0) {
1376  cut.vars.push_back(x);
1377  cut.coeffs.push_back(IntegerValue(x_coeff));
1378  }
1379  if (y_coeff != 0) {
1380  cut.vars.push_back(y);
1381  cut.coeffs.push_back(IntegerValue(y_coeff));
1382  }
1383  cut.lb = kMinIntegerValue;
1384  cut.ub = IntegerValue(rhs);
1385  manager->AddCut(cut, "PositiveProduct", lp_values);
1386  }
1387  };
1388 
1389  // Cut -z + x_coeff * x + y_coeff* y >= rhs
1390  auto try_add_below_cut =
1391  [manager, z_lp_value, x_lp_value, y_lp_value, x, y, z, &lp_values](
1392  int64_t x_coeff, int64_t y_coeff, int64_t rhs) {
1393  if (-z_lp_value + x_lp_value * x_coeff + y_lp_value * y_coeff <=
1394  rhs - kMinCutViolation) {
1395  LinearConstraint cut;
1396  cut.vars.push_back(z);
1397  cut.coeffs.push_back(IntegerValue(-1));
1398  if (x_coeff != 0) {
1399  cut.vars.push_back(x);
1400  cut.coeffs.push_back(IntegerValue(x_coeff));
1401  }
1402  if (y_coeff != 0) {
1403  cut.vars.push_back(y);
1404  cut.coeffs.push_back(IntegerValue(y_coeff));
1405  }
1406  cut.lb = IntegerValue(rhs);
1407  cut.ub = kMaxIntegerValue;
1408  manager->AddCut(cut, "PositiveProduct", lp_values);
1409  }
1410  };
1411 
1412  // McCormick relaxation of bilinear constraints. These 4 cuts are the
1413  // exact facets of the x * y polyhedron for a bounded x and y.
1414  //
1415  // Each cut correspond to plane that contains two of the line
1416  // (x=x_lb), (x=x_ub), (y=y_lb), (y=y_ub). The easiest to
1417  // understand them is to draw the x*y curves and see the 4
1418  // planes that correspond to the convex hull of the graph.
1419  try_add_above_cut(y_lb, x_lb, x_lb * y_lb);
1420  try_add_above_cut(y_ub, x_ub, x_ub * y_ub);
1421  try_add_below_cut(y_ub, x_lb, x_lb * y_ub);
1422  try_add_below_cut(y_lb, x_ub, x_ub * y_lb);
1423  };
1424 
1425  return result;
1426 }
1427 
1428 CutGenerator CreateSquareCutGenerator(IntegerVariable y, IntegerVariable x,
1429  Model* model) {
1430  CutGenerator result;
1431  result.vars = {y, x};
1432 
1433  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
1434  result.generate_cuts =
1435  [y, x, integer_trail](
1437  LinearConstraintManager* manager) {
1438  const int64_t x_ub = integer_trail->LevelZeroUpperBound(x).value();
1439  const int64_t x_lb = integer_trail->LevelZeroLowerBound(x).value();
1440 
1441  if (x_lb == x_ub) return;
1442 
1443  // Check for potential overflows.
1444  if (x_ub > (int64_t{1} << 31)) return;
1445  DCHECK_GE(x_lb, 0);
1446 
1447  const double y_lp_value = lp_values[y];
1448  const double x_lp_value = lp_values[x];
1449 
1450  // First cut: target should be below the line:
1451  // (x_lb, x_lb ^ 2) to (x_ub, x_ub ^ 2).
1452  // The slope of that line is (ub^2 - lb^2) / (ub - lb) = ub + lb.
1453  const int64_t y_lb = x_lb * x_lb;
1454  const int64_t above_slope = x_ub + x_lb;
1455  const double max_lp_y = y_lb + above_slope * (x_lp_value - x_lb);
1456  if (y_lp_value >= max_lp_y + kMinCutViolation) {
1457  // cut: y <= (x_lb + x_ub) * x - x_lb * x_ub
1458  LinearConstraint above_cut;
1459  above_cut.vars.push_back(y);
1460  above_cut.coeffs.push_back(IntegerValue(1));
1461  above_cut.vars.push_back(x);
1462  above_cut.coeffs.push_back(IntegerValue(-above_slope));
1463  above_cut.lb = kMinIntegerValue;
1464  above_cut.ub = IntegerValue(-x_lb * x_ub);
1465  manager->AddCut(above_cut, "SquareUpper", lp_values);
1466  }
1467 
1468  // Second cut: target should be above all the lines
1469  // (value, value ^ 2) to (value + 1, (value + 1) ^ 2)
1470  // The slope of that line is 2 * value + 1
1471  //
1472  // Note that we only add one of these cuts. The one for x_lp_value in
1473  // [value, value + 1].
1474  const int64_t x_floor = static_cast<int64_t>(std::floor(x_lp_value));
1475  const int64_t below_slope = 2 * x_floor + 1;
1476  const double min_lp_y =
1477  below_slope * x_lp_value - x_floor - x_floor * x_floor;
1478  if (min_lp_y >= y_lp_value + kMinCutViolation) {
1479  // cut: y >= below_slope * (x - x_floor) + x_floor ^ 2
1480  // : y >= below_slope * x - x_floor ^ 2 - x_floor
1481  LinearConstraint below_cut;
1482  below_cut.vars.push_back(y);
1483  below_cut.coeffs.push_back(IntegerValue(1));
1484  below_cut.vars.push_back(x);
1485  below_cut.coeffs.push_back(-IntegerValue(below_slope));
1486  below_cut.lb = IntegerValue(-x_floor - x_floor * x_floor);
1487  below_cut.ub = kMaxIntegerValue;
1488  manager->AddCut(below_cut, "SquareLower", lp_values);
1489  }
1490  };
1491 
1492  return result;
1493 }
1494 
1495 void ImpliedBoundsProcessor::ProcessUpperBoundedConstraint(
1497  LinearConstraint* cut) {
1498  ProcessUpperBoundedConstraintWithSlackCreation(
1499  /*substitute_only_inner_variables=*/false, IntegerVariable(0), lp_values,
1500  cut, nullptr);
1501 }
1502 
1504 ImpliedBoundsProcessor::GetCachedImpliedBoundInfo(IntegerVariable var) {
1505  auto it = cache_.find(var);
1506  if (it != cache_.end()) return it->second;
1507  return BestImpliedBoundInfo();
1508 }
1509 
1511 ImpliedBoundsProcessor::ComputeBestImpliedBound(
1512  IntegerVariable var,
1514  auto it = cache_.find(var);
1515  if (it != cache_.end()) return it->second;
1516  BestImpliedBoundInfo result;
1517  const IntegerValue lb = integer_trail_->LevelZeroLowerBound(var);
1518  for (const ImpliedBoundEntry& entry :
1519  implied_bounds_->GetImpliedBounds(var)) {
1520  // Only process entries with a Boolean variable currently part of the LP
1521  // we are considering for this cut.
1522  //
1523  // TODO(user): the more we use cuts, the less it make sense to have a
1524  // lot of small independent LPs.
1525  if (!lp_vars_.contains(PositiveVariable(entry.literal_view))) {
1526  continue;
1527  }
1528 
1529  // The equation is X = lb + diff * Bool + Slack where Bool is in [0, 1]
1530  // and slack in [0, ub - lb].
1531  const IntegerValue diff = entry.lower_bound - lb;
1532  CHECK_GE(diff, 0);
1533  const double bool_lp_value = entry.is_positive
1534  ? lp_values[entry.literal_view]
1535  : 1.0 - lp_values[entry.literal_view];
1536  const double slack_lp_value =
1537  lp_values[var] - ToDouble(lb) - bool_lp_value * ToDouble(diff);
1538 
1539  // If the implied bound equation is not respected, we just add it
1540  // to implied_bound_cuts, and skip the entry for now.
1541  if (slack_lp_value < -1e-4) {
1542  LinearConstraint ib_cut;
1543  ib_cut.lb = kMinIntegerValue;
1544  std::vector<std::pair<IntegerVariable, IntegerValue>> terms;
1545  if (entry.is_positive) {
1546  // X >= Indicator * (bound - lb) + lb
1547  terms.push_back({entry.literal_view, diff});
1548  terms.push_back({var, IntegerValue(-1)});
1549  ib_cut.ub = -lb;
1550  } else {
1551  // X >= -Indicator * (bound - lb) + bound
1552  terms.push_back({entry.literal_view, -diff});
1553  terms.push_back({var, IntegerValue(-1)});
1554  ib_cut.ub = -entry.lower_bound;
1555  }
1556  CleanTermsAndFillConstraint(&terms, &ib_cut);
1557  ib_cut_pool_.AddCut(std::move(ib_cut), "IB", lp_values);
1558  continue;
1559  }
1560 
1561  // We look for tight implied bounds, and amongst the tightest one, we
1562  // prefer larger coefficient in front of the Boolean.
1563  if (slack_lp_value + 1e-4 < result.slack_lp_value ||
1564  (slack_lp_value < result.slack_lp_value + 1e-4 &&
1565  diff > result.bound_diff)) {
1566  result.bool_lp_value = bool_lp_value;
1567  result.slack_lp_value = slack_lp_value;
1568 
1569  result.bound_diff = diff;
1570  result.is_positive = entry.is_positive;
1571  result.bool_var = entry.literal_view;
1572  }
1573  }
1574  cache_[var] = result;
1575  return result;
1576 }
1577 
1578 // TODO(user): restrict to a subset of the variables to not spend too much time.
1579 void ImpliedBoundsProcessor::SeparateSomeImpliedBoundCuts(
1581  for (const IntegerVariable var :
1582  implied_bounds_->VariablesWithImpliedBounds()) {
1583  if (!lp_vars_.contains(PositiveVariable(var))) continue;
1584  ComputeBestImpliedBound(var, lp_values);
1585  }
1586 }
1587 
1588 void ImpliedBoundsProcessor::ProcessUpperBoundedConstraintWithSlackCreation(
1589  bool substitute_only_inner_variables, IntegerVariable first_slack,
1591  LinearConstraint* cut, std::vector<SlackInfo>* slack_infos) {
1592  tmp_terms_.clear();
1593  IntegerValue new_ub = cut->ub;
1594  bool changed = false;
1595 
1596  // TODO(user): we could relax a bit this test.
1597  int64_t overflow_detection = 0;
1598 
1599  const int size = cut->vars.size();
1600  for (int i = 0; i < size; ++i) {
1601  IntegerVariable var = cut->vars[i];
1602  IntegerValue coeff = cut->coeffs[i];
1603 
1604  // Starts by positive coefficient.
1605  // TODO(user): Not clear this is best.
1606  if (coeff < 0) {
1607  coeff = -coeff;
1608  var = NegationOf(var);
1609  }
1610 
1611  // Find the best implied bound to use.
1612  // TODO(user): We could also use implied upper bound, that is try with
1613  // NegationOf(var).
1614  const BestImpliedBoundInfo info = ComputeBestImpliedBound(var, lp_values);
1615  {
1616  // This make sure the implied bound for NegationOf(var) is "cached" so
1617  // that GetCachedImpliedBoundInfo() will work. It will also add any
1618  // relevant implied bound cut.
1619  //
1620  // TODO(user): this is a bit hacky. Find a cleaner way.
1621  ComputeBestImpliedBound(NegationOf(var), lp_values);
1622  }
1623 
1624  const int old_size = tmp_terms_.size();
1625 
1626  // Shall we keep the original term ?
1627  bool keep_term = false;
1628  if (info.bool_var == kNoIntegerVariable) keep_term = true;
1629  if (CapProd(std::abs(coeff.value()), info.bound_diff.value()) ==
1631  keep_term = true;
1632  }
1633 
1634  // TODO(user): On some problem, not replacing the variable at their bound
1635  // by an implied bounds seems beneficial. This is especially the case on
1636  // g200x740.mps.gz
1637  //
1638  // Note that in ComputeCut() the variable with an LP value at the bound do
1639  // not contribute to the cut efficacity (no loss) but do contribute to the
1640  // various heuristic based on the coefficient magnitude.
1641  if (substitute_only_inner_variables) {
1642  const IntegerValue lb = integer_trail_->LevelZeroLowerBound(var);
1643  const IntegerValue ub = integer_trail_->LevelZeroUpperBound(var);
1644  if (lp_values[var] - ToDouble(lb) < 1e-2) keep_term = true;
1645  if (ToDouble(ub) - lp_values[var] < 1e-2) keep_term = true;
1646  }
1647 
1648  // This is when we do not add slack.
1649  if (slack_infos == nullptr) {
1650  // We do not want to loose anything, so we only replace if the slack lp is
1651  // zero.
1652  if (info.slack_lp_value > 1e-6) keep_term = true;
1653  }
1654 
1655  if (keep_term) {
1656  tmp_terms_.push_back({var, coeff});
1657  } else {
1658  // Substitute.
1659  const IntegerValue lb = integer_trail_->LevelZeroLowerBound(var);
1660  const IntegerValue ub = integer_trail_->LevelZeroUpperBound(var);
1661 
1662  SlackInfo slack_info;
1663  slack_info.lp_value = info.slack_lp_value;
1664  slack_info.lb = 0;
1665  slack_info.ub = ub - lb;
1666 
1667  if (info.is_positive) {
1668  // X = Indicator * diff + lb + Slack
1669  tmp_terms_.push_back({info.bool_var, coeff * info.bound_diff});
1670  if (!AddProductTo(-coeff, lb, &new_ub)) {
1671  VLOG(2) << "Overflow";
1672  return;
1673  }
1674  if (slack_infos != nullptr) {
1675  tmp_terms_.push_back({first_slack, coeff});
1676  first_slack += 2;
1677 
1678  // slack = X - Indicator * info.bound_diff - lb;
1679  slack_info.terms.push_back({var, IntegerValue(1)});
1680  slack_info.terms.push_back({info.bool_var, -info.bound_diff});
1681  slack_info.offset = -lb;
1682  slack_infos->push_back(slack_info);
1683  }
1684  } else {
1685  // X = (1 - Indicator) * (diff) + lb + Slack
1686  // X = -Indicator * (diff) + lb + diff + Slack
1687  tmp_terms_.push_back({info.bool_var, -coeff * info.bound_diff});
1688  if (!AddProductTo(-coeff, lb + info.bound_diff, &new_ub)) {
1689  VLOG(2) << "Overflow";
1690  return;
1691  }
1692  if (slack_infos != nullptr) {
1693  tmp_terms_.push_back({first_slack, coeff});
1694  first_slack += 2;
1695 
1696  // slack = X + Indicator * info.bound_diff - lb - diff;
1697  slack_info.terms.push_back({var, IntegerValue(1)});
1698  slack_info.terms.push_back({info.bool_var, +info.bound_diff});
1699  slack_info.offset = -lb - info.bound_diff;
1700  slack_infos->push_back(slack_info);
1701  }
1702  }
1703  changed = true;
1704  }
1705 
1706  // Add all the new terms coefficient to the overflow detection to avoid
1707  // issue when merging terms refering to the same variable.
1708  for (int i = old_size; i < tmp_terms_.size(); ++i) {
1709  overflow_detection =
1710  CapAdd(overflow_detection, std::abs(tmp_terms_[i].second.value()));
1711  }
1712  }
1713 
1714  if (overflow_detection >= kMaxIntegerValue) {
1715  VLOG(2) << "Overflow";
1716  return;
1717  }
1718  if (!changed) return;
1719 
1720  // Update the cut.
1721  //
1722  // Note that because of our overflow_detection variable, there should be
1723  // no integer overflow when we merge identical terms.
1724  cut->lb = kMinIntegerValue; // Not relevant.
1725  cut->ub = new_ub;
1726  CleanTermsAndFillConstraint(&tmp_terms_, cut);
1727 }
1728 
1729 bool ImpliedBoundsProcessor::DebugSlack(IntegerVariable first_slack,
1730  const LinearConstraint& initial_cut,
1731  const LinearConstraint& cut,
1732  const std::vector<SlackInfo>& info) {
1733  tmp_terms_.clear();
1734  IntegerValue new_ub = cut.ub;
1735  for (int i = 0; i < cut.vars.size(); ++i) {
1736  // Simple copy for non-slack variables.
1737  if (cut.vars[i] < first_slack) {
1738  tmp_terms_.push_back({cut.vars[i], cut.coeffs[i]});
1739  continue;
1740  }
1741 
1742  // Replace slack by its definition.
1743  const IntegerValue multiplier = cut.coeffs[i];
1744  const int index = (cut.vars[i].value() - first_slack.value()) / 2;
1745  for (const std::pair<IntegerVariable, IntegerValue>& term :
1746  info[index].terms) {
1747  tmp_terms_.push_back({term.first, term.second * multiplier});
1748  }
1749  new_ub -= multiplier * info[index].offset;
1750  }
1751 
1752  LinearConstraint tmp_cut;
1753  tmp_cut.lb = kMinIntegerValue; // Not relevant.
1754  tmp_cut.ub = new_ub;
1755  CleanTermsAndFillConstraint(&tmp_terms_, &tmp_cut);
1756  MakeAllVariablesPositive(&tmp_cut);
1757 
1758  // We need to canonicalize the initial_cut too for comparison. Note that we
1759  // only use this for debug, so we don't care too much about the memory and
1760  // extra time.
1761  // TODO(user): Expose CanonicalizeConstraint() from the manager.
1762  LinearConstraint tmp_copy;
1763  tmp_terms_.clear();
1764  for (int i = 0; i < initial_cut.vars.size(); ++i) {
1765  tmp_terms_.push_back({initial_cut.vars[i], initial_cut.coeffs[i]});
1766  }
1767  tmp_copy.lb = kMinIntegerValue; // Not relevant.
1768  tmp_copy.ub = new_ub;
1769  CleanTermsAndFillConstraint(&tmp_terms_, &tmp_copy);
1770  MakeAllVariablesPositive(&tmp_copy);
1771 
1772  if (tmp_cut == tmp_copy) return true;
1773 
1774  LOG(INFO) << first_slack;
1775  LOG(INFO) << tmp_copy.DebugString();
1776  LOG(INFO) << cut.DebugString();
1777  LOG(INFO) << tmp_cut.DebugString();
1778  return false;
1779 }
1780 
1781 namespace {
1782 
1783 void TryToGenerateAllDiffCut(
1784  const std::vector<std::pair<double, IntegerVariable>>& sorted_vars_lp,
1785  const IntegerTrail& integer_trail,
1787  LinearConstraintManager* manager) {
1788  Domain current_union;
1789  std::vector<IntegerVariable> current_set_vars;
1790  double sum = 0.0;
1791  for (auto value_var : sorted_vars_lp) {
1792  sum += value_var.first;
1793  const IntegerVariable var = value_var.second;
1794  // TODO(user): The union of the domain of the variable being considered
1795  // does not give the tightest bounds, try to get better bounds.
1796  current_union =
1797  current_union.UnionWith(integer_trail.InitialVariableDomain(var));
1798  current_set_vars.push_back(var);
1799  const int64_t required_min_sum =
1800  SumOfKMinValueInDomain(current_union, current_set_vars.size());
1801  const int64_t required_max_sum =
1802  SumOfKMaxValueInDomain(current_union, current_set_vars.size());
1803  if (sum < required_min_sum || sum > required_max_sum) {
1804  LinearConstraint cut;
1805  for (IntegerVariable var : current_set_vars) {
1806  cut.AddTerm(var, IntegerValue(1));
1807  }
1808  cut.lb = IntegerValue(required_min_sum);
1809  cut.ub = IntegerValue(required_max_sum);
1810  manager->AddCut(cut, "all_diff", lp_values);
1811  // NOTE: We can extend the current set but it is more helpful to generate
1812  // the cut on a different set of variables so we reset the counters.
1813  sum = 0.0;
1814  current_set_vars.clear();
1815  current_union = Domain();
1816  }
1817  }
1818 }
1819 
1820 } // namespace
1821 
1823  const std::vector<IntegerVariable>& vars, Model* model) {
1824  CutGenerator result;
1825  result.vars = vars;
1826  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
1827  Trail* trail = model->GetOrCreate<Trail>();
1828  result.generate_cuts =
1829  [vars, integer_trail, trail](
1831  LinearConstraintManager* manager) {
1832  // These cuts work at all levels but the generator adds too many cuts on
1833  // some instances and degrade the performance so we only use it at level
1834  // 0.
1835  if (trail->CurrentDecisionLevel() > 0) return;
1836  std::vector<std::pair<double, IntegerVariable>> sorted_vars;
1837  for (const IntegerVariable var : vars) {
1838  if (integer_trail->LevelZeroLowerBound(var) ==
1839  integer_trail->LevelZeroUpperBound(var)) {
1840  continue;
1841  }
1842  sorted_vars.push_back(std::make_pair(lp_values[var], var));
1843  }
1844  std::sort(sorted_vars.begin(), sorted_vars.end());
1845  TryToGenerateAllDiffCut(sorted_vars, *integer_trail, lp_values,
1846  manager);
1847  // Other direction.
1848  std::reverse(sorted_vars.begin(), sorted_vars.end());
1849  TryToGenerateAllDiffCut(sorted_vars, *integer_trail, lp_values,
1850  manager);
1851  };
1852  VLOG(1) << "Created all_diff cut generator of size: " << vars.size();
1853  return result;
1854 }
1855 
1856 namespace {
1857 // Returns max((w2i - w1i)*Li, (w2i - w1i)*Ui).
1858 IntegerValue MaxCornerDifference(const IntegerVariable var,
1859  const IntegerValue w1_i,
1860  const IntegerValue w2_i,
1861  const IntegerTrail& integer_trail) {
1862  const IntegerValue lb = integer_trail.LevelZeroLowerBound(var);
1863  const IntegerValue ub = integer_trail.LevelZeroUpperBound(var);
1864  return std::max((w2_i - w1_i) * lb, (w2_i - w1_i) * ub);
1865 }
1866 
1867 // This is the coefficient of zk in the cut, where k = max_index.
1868 // MPlusCoefficient_ki = max((wki - wI(i)i) * Li,
1869 // (wki - wI(i)i) * Ui)
1870 // = max corner difference for variable i,
1871 // target expr I(i), max expr k.
1872 // The coefficient of zk is Sum(i=1..n)(MPlusCoefficient_ki) + bk
1873 IntegerValue MPlusCoefficient(
1874  const std::vector<IntegerVariable>& x_vars,
1875  const std::vector<LinearExpression>& exprs,
1876  const absl::StrongVector<IntegerVariable, int>& variable_partition,
1877  const int max_index, const IntegerTrail& integer_trail) {
1878  IntegerValue coeff = exprs[max_index].offset;
1879  // TODO(user): This algo is quadratic since GetCoefficientOfPositiveVar()
1880  // is linear. This can be optimized (better complexity) if needed.
1881  for (const IntegerVariable var : x_vars) {
1882  const int target_index = variable_partition[var];
1883  if (max_index != target_index) {
1884  coeff += MaxCornerDifference(
1885  var, GetCoefficientOfPositiveVar(var, exprs[target_index]),
1886  GetCoefficientOfPositiveVar(var, exprs[max_index]), integer_trail);
1887  }
1888  }
1889  return coeff;
1890 }
1891 
1892 // Compute the value of
1893 // rhs = wI(i)i * xi + Sum(k=1..d)(MPlusCoefficient_ki * zk)
1894 // for variable xi for given target index I(i).
1895 double ComputeContribution(
1896  const IntegerVariable xi_var, const std::vector<IntegerVariable>& z_vars,
1897  const std::vector<LinearExpression>& exprs,
1899  const IntegerTrail& integer_trail, const int target_index) {
1900  CHECK_GE(target_index, 0);
1901  CHECK_LT(target_index, exprs.size());
1902  const LinearExpression& target_expr = exprs[target_index];
1903  const double xi_value = lp_values[xi_var];
1904  const IntegerValue wt_i = GetCoefficientOfPositiveVar(xi_var, target_expr);
1905  double contrib = wt_i.value() * xi_value;
1906  for (int expr_index = 0; expr_index < exprs.size(); ++expr_index) {
1907  if (expr_index == target_index) continue;
1908  const LinearExpression& max_expr = exprs[expr_index];
1909  const double z_max_value = lp_values[z_vars[expr_index]];
1910  const IntegerValue corner_value = MaxCornerDifference(
1911  xi_var, wt_i, GetCoefficientOfPositiveVar(xi_var, max_expr),
1912  integer_trail);
1913  contrib += corner_value.value() * z_max_value;
1914  }
1915  return contrib;
1916 }
1917 } // namespace
1918 
1920  const IntegerVariable target, const std::vector<LinearExpression>& exprs,
1921  const std::vector<IntegerVariable>& z_vars, Model* model) {
1922  CutGenerator result;
1923  std::vector<IntegerVariable> x_vars;
1924  result.vars = {target};
1925  const int num_exprs = exprs.size();
1926  for (int i = 0; i < num_exprs; ++i) {
1927  result.vars.push_back(z_vars[i]);
1928  x_vars.insert(x_vars.end(), exprs[i].vars.begin(), exprs[i].vars.end());
1929  }
1931  // All expressions should only contain positive variables.
1932  DCHECK(std::all_of(x_vars.begin(), x_vars.end(), [](IntegerVariable var) {
1933  return VariableIsPositive(var);
1934  }));
1935  result.vars.insert(result.vars.end(), x_vars.begin(), x_vars.end());
1936 
1937  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
1938  result.generate_cuts =
1939  [x_vars, z_vars, target, num_exprs, exprs, integer_trail, model](
1941  LinearConstraintManager* manager) {
1942  absl::StrongVector<IntegerVariable, int> variable_partition(
1943  lp_values.size(), -1);
1944  absl::StrongVector<IntegerVariable, double> variable_partition_contrib(
1945  lp_values.size(), std::numeric_limits<double>::infinity());
1946  for (int expr_index = 0; expr_index < num_exprs; ++expr_index) {
1947  for (const IntegerVariable var : x_vars) {
1948  const double contribution = ComputeContribution(
1949  var, z_vars, exprs, lp_values, *integer_trail, expr_index);
1950  const double prev_contribution = variable_partition_contrib[var];
1951  if (contribution < prev_contribution) {
1952  variable_partition[var] = expr_index;
1953  variable_partition_contrib[var] = contribution;
1954  }
1955  }
1956  }
1957 
1958  LinearConstraintBuilder cut(model, /*lb=*/IntegerValue(0),
1959  /*ub=*/kMaxIntegerValue);
1960  double violation = lp_values[target];
1961  cut.AddTerm(target, IntegerValue(-1));
1962 
1963  for (const IntegerVariable xi_var : x_vars) {
1964  const int input_index = variable_partition[xi_var];
1965  const LinearExpression& expr = exprs[input_index];
1966  const IntegerValue coeff = GetCoefficientOfPositiveVar(xi_var, expr);
1967  if (coeff != IntegerValue(0)) {
1968  cut.AddTerm(xi_var, coeff);
1969  }
1970  violation -= coeff.value() * lp_values[xi_var];
1971  }
1972  for (int expr_index = 0; expr_index < num_exprs; ++expr_index) {
1973  const IntegerVariable z_var = z_vars[expr_index];
1974  const IntegerValue z_coeff = MPlusCoefficient(
1975  x_vars, exprs, variable_partition, expr_index, *integer_trail);
1976  if (z_coeff != IntegerValue(0)) {
1977  cut.AddTerm(z_var, z_coeff);
1978  }
1979  violation -= z_coeff.value() * lp_values[z_var];
1980  }
1981  if (violation > 1e-2) {
1982  manager->AddCut(cut.Build(), "LinMax", lp_values);
1983  }
1984  };
1985  return result;
1986 }
1987 
1989  Model* model,
1990  std::vector<IntegerVariable>* vars) {
1991  IntegerEncoder* encoder = model->GetOrCreate<IntegerEncoder>();
1992  for (int t = 0; t < helper->NumTasks(); ++t) {
1993  if (helper->Starts()[t].var != kNoIntegerVariable) {
1994  vars->push_back(helper->Starts()[t].var);
1995  }
1996  if (helper->Sizes()[t].var != kNoIntegerVariable) {
1997  vars->push_back(helper->Sizes()[t].var);
1998  }
1999  if (helper->Ends()[t].var != kNoIntegerVariable) {
2000  vars->push_back(helper->Ends()[t].var);
2001  }
2002  if (helper->IsOptional(t) && !helper->IsAbsent(t) &&
2003  !helper->IsPresent(t)) {
2004  const Literal l = helper->PresenceLiteral(t);
2005  if (encoder->GetLiteralView(l) == kNoIntegerVariable &&
2006  encoder->GetLiteralView(l.Negated()) == kNoIntegerVariable) {
2008  }
2009  const IntegerVariable direct_view = encoder->GetLiteralView(l);
2010  if (direct_view != kNoIntegerVariable) {
2011  vars->push_back(direct_view);
2012  } else {
2013  vars->push_back(encoder->GetLiteralView(l.Negated()));
2014  DCHECK_NE(vars->back(), kNoIntegerVariable);
2015  }
2016  }
2017  }
2019 }
2020 
2021 std::function<void(const absl::StrongVector<IntegerVariable, double>&,
2022  LinearConstraintManager*)>
2023 GenerateCumulativeCut(const std::string& cut_name,
2025  const std::vector<IntegerVariable>& demands,
2027  Trail* trail = model->GetOrCreate<Trail>();
2028  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
2029  IntegerEncoder* encoder = model->GetOrCreate<IntegerEncoder>();
2030 
2031  return [capacity, demands, trail, integer_trail, helper, model, cut_name,
2032  encoder](const absl::StrongVector<IntegerVariable, double>& lp_values,
2033  LinearConstraintManager* manager) {
2034  if (trail->CurrentDecisionLevel() > 0) return;
2035 
2036  const auto demand_is_fixed = [integer_trail, &demands](int i) {
2037  return demands.empty() || integer_trail->IsFixed(demands[i]);
2038  };
2039  const auto demand_min = [integer_trail, &demands](int i) {
2040  return demands.empty() ? IntegerValue(1)
2041  : integer_trail->LowerBound(demands[i]);
2042  };
2043  const auto demand_max = [integer_trail, &demands](int i) {
2044  return demands.empty() ? IntegerValue(1)
2045  : integer_trail->UpperBound(demands[i]);
2046  };
2047 
2048  std::vector<int> active_intervals;
2049  for (int i = 0; i < helper->NumTasks(); ++i) {
2050  if (!helper->IsAbsent(i) && demand_max(i) > 0 && helper->SizeMin(i) > 0) {
2051  active_intervals.push_back(i);
2052  }
2053  }
2054 
2055  if (active_intervals.size() < 2) return;
2056 
2057  std::sort(active_intervals.begin(), active_intervals.end(),
2058  [helper](int a, int b) {
2059  return helper->StartMin(a) < helper->StartMin(b) ||
2060  (helper->StartMin(a) == helper->StartMin(b) &&
2061  helper->EndMax(a) < helper->EndMax(b));
2062  });
2063 
2064  const IntegerValue capacity_max = integer_trail->UpperBound(capacity);
2065  IntegerValue processed_start = kMinIntegerValue;
2066  for (int i1 = 0; i1 + 1 < active_intervals.size(); ++i1) {
2067  const int start_index = active_intervals[i1];
2068  DCHECK(!helper->IsAbsent(start_index));
2069 
2070  // We want maximal cuts. For any start_min value, we only need to create
2071  // cuts starting from the first interval having this start_min value.
2072  if (helper->StartMin(start_index) == processed_start) {
2073  continue;
2074  } else {
2075  processed_start = helper->StartMin(start_index);
2076  }
2077 
2078  // For each start time, we will keep the most violated cut generated while
2079  // scanning the residual tasks.
2080  int end_index_of_max_violation = -1;
2081  double max_relative_violation = 1.01;
2082  IntegerValue span_of_max_violation(0);
2083 
2084  // Accumulate intervals and check for potential cuts.
2085  double energy_lp = 0.0;
2086  IntegerValue min_of_starts = kMaxIntegerValue;
2087  IntegerValue max_of_ends = kMinIntegerValue;
2088 
2089  // We sort all tasks (start_min(task) >= start_min(start_index) by
2090  // increasing end max.
2091  std::vector<int> residual_tasks(active_intervals.begin() + i1,
2092  active_intervals.end());
2093  std::sort(
2094  residual_tasks.begin(), residual_tasks.end(),
2095  [&](int a, int b) { return helper->EndMax(a) < helper->EndMax(b); });
2096 
2097  // Let's process residual tasks and evaluate the cut violation of the cut
2098  // at each step. We follow the same structure as the cut creation code
2099  // below.
2100  for (int i2 = 0; i2 < residual_tasks.size(); ++i2) {
2101  const int t = residual_tasks[i2];
2102  if (helper->IsPresent(t)) {
2103  if (demand_is_fixed(t)) {
2104  if (helper->SizeIsFixed(t)) {
2105  energy_lp += ToDouble(helper->SizeMin(t) * demand_min(t));
2106  } else {
2107  energy_lp += ToDouble(demand_min(t)) *
2108  helper->Sizes()[t].LpValue(lp_values);
2109  }
2110  } else if (helper->SizeIsFixed(t)) {
2111  DCHECK(!demands.empty());
2112  energy_lp += lp_values[demands[t]] * ToDouble(helper->SizeMin(t));
2113  } else { // demand and size are not fixed.
2114  DCHECK(!demands.empty());
2115  energy_lp +=
2116  ToDouble(demand_min(t)) * helper->Sizes()[t].LpValue(lp_values);
2117  energy_lp += lp_values[demands[t]] * ToDouble(helper->SizeMin(t));
2118  energy_lp -= ToDouble(demand_min(t) * helper->SizeMin(t));
2119  }
2120  } else {
2121  energy_lp += GetLiteralLpValue(helper->PresenceLiteral(t), lp_values,
2122  encoder) *
2123  ToDouble(helper->SizeMin(t) * demand_min(t));
2124  }
2125 
2126  min_of_starts = std::min(min_of_starts, helper->StartMin(t));
2127  max_of_ends = std::max(max_of_ends, helper->EndMax(t));
2128 
2129  // Compute the violation of the potential cut.
2130  const double relative_violation =
2131  energy_lp / ToDouble((max_of_ends - min_of_starts) * capacity_max);
2132  if (relative_violation > max_relative_violation) {
2133  end_index_of_max_violation = i2;
2134  max_relative_violation = relative_violation;
2135  span_of_max_violation = max_of_ends - min_of_starts;
2136  }
2137  }
2138 
2139  if (end_index_of_max_violation == -1) continue;
2140 
2141  // A maximal violated cut has been found.
2142  bool cut_generated = true;
2143  bool has_opt_cuts = false;
2144  bool has_quadratic_cuts = false;
2145 
2146  LinearConstraintBuilder cut(model, kMinIntegerValue, IntegerValue(0));
2147 
2148  // Build the cut.
2149  cut.AddTerm(capacity, -span_of_max_violation);
2150  for (int i2 = 0; i2 <= end_index_of_max_violation; ++i2) {
2151  const int t = residual_tasks[i2];
2152  if (helper->IsPresent(t)) {
2153  if (demand_is_fixed(t)) {
2154  if (helper->SizeIsFixed(t)) {
2155  cut.AddConstant(helper->SizeMin(t) * demand_min(t));
2156  } else {
2157  cut.AddTerm(helper->Sizes()[t], demand_min(t));
2158  }
2159  } else if (helper->SizeIsFixed(t)) {
2160  DCHECK(!demands.empty());
2161  cut.AddTerm(demands[t], helper->SizeMin(t));
2162  } else { // demand and size are not fixed.
2163  DCHECK(!demands.empty());
2164  // We use McCormick equation.
2165  // demand * size = (demand_min + delta_d) * (min_size +
2166  // delta_s) =
2167  // demand_min * min_size + delta_d * min_size +
2168  // delta_s * demand_min + delta_s * delta_d
2169  // which is >= (by ignoring the quatratic term)
2170  // demand_min * size + min_size * demand - demand_min *
2171  // min_size
2172  cut.AddTerm(helper->Sizes()[t], demand_min(t));
2173  cut.AddTerm(demands[t], helper->SizeMin(t));
2174  // Substract the energy counted twice.
2175  cut.AddConstant(-helper->SizeMin(t) * demand_min(t));
2176  has_quadratic_cuts = true;
2177  }
2178  } else {
2179  has_opt_cuts = true;
2180  if (!helper->SizeIsFixed(t) || !demand_is_fixed(t)) {
2181  has_quadratic_cuts = true;
2182  }
2183  if (!cut.AddLiteralTerm(helper->PresenceLiteral(t),
2184  helper->SizeMin(t) * demand_min(t))) {
2185  cut_generated = false;
2186  break;
2187  }
2188  }
2189  }
2190 
2191  if (cut_generated) {
2192  std::string full_name = cut_name;
2193  if (has_opt_cuts) full_name.append("_opt");
2194  if (has_quadratic_cuts) full_name.append("_quad");
2195 
2196  manager->AddCut(cut.Build(), cut_name, lp_values);
2197  }
2198  }
2199  };
2200 }
2201 
2203  const std::vector<IntervalVariable>& intervals,
2204  const IntegerVariable capacity, const std::vector<IntegerVariable>& demands,
2205  Model* model) {
2206  CutGenerator result;
2207 
2208  SchedulingConstraintHelper* helper =
2209  new SchedulingConstraintHelper(intervals, model);
2210  model->TakeOwnership(helper);
2211 
2212  result.vars = demands;
2213  result.vars.push_back(capacity);
2214  AddIntegerVariableFromIntervals(helper, model, &result.vars);
2215 
2217  "CumulativeEnergy", helper, demands, AffineExpression(capacity), model);
2218  return result;
2219 }
2220 
2222  const std::vector<IntervalVariable>& intervals,
2223  const IntegerVariable capacity, const std::vector<IntegerVariable>& demands,
2224  Model* model) {
2225  CutGenerator result;
2226 
2227  SchedulingConstraintHelper* helper =
2228  new SchedulingConstraintHelper(intervals, model);
2229  model->TakeOwnership(helper);
2230 
2231  result.vars = demands;
2232  result.vars.push_back(capacity);
2233  AddIntegerVariableFromIntervals(helper, model, &result.vars);
2234 
2235  struct Event {
2236  int interval_index;
2237  IntegerValue time;
2238  bool positive;
2239  IntegerVariable demand;
2240  };
2241 
2242  Trail* trail = model->GetOrCreate<Trail>();
2243  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
2244 
2245  result.generate_cuts =
2246  [helper, capacity, demands, trail, integer_trail, model](
2248  LinearConstraintManager* manager) {
2249  if (trail->CurrentDecisionLevel() > 0) return;
2250 
2251  std::vector<Event> events;
2252  // Iterate through the intervals. If start_max < end_min, the demand
2253  // is mandatory.
2254  for (int i = 0; i < helper->NumTasks(); ++i) {
2255  if (helper->IsAbsent(i)) continue;
2256 
2257  const IntegerValue start_max = helper->StartMax(i);
2258  const IntegerValue end_min = helper->EndMin(i);
2259 
2260  if (start_max >= end_min) continue;
2261 
2262  Event e1;
2263  e1.interval_index = i;
2264  e1.time = start_max;
2265  e1.demand = demands[i];
2266  e1.positive = true;
2267 
2268  Event e2 = e1;
2269  e2.time = end_min;
2270  e2.positive = false;
2271  events.push_back(e1);
2272  events.push_back(e2);
2273  }
2274 
2275  // Sort events by time.
2276  // It is also important that all positive event with the same time as
2277  // negative events appear after for the correctness of the algo below.
2278  std::sort(events.begin(), events.end(),
2279  [](const Event i, const Event j) {
2280  if (i.time == j.time) {
2281  if (i.positive == j.positive) {
2282  return i.interval_index < j.interval_index;
2283  }
2284  return !i.positive;
2285  }
2286  return i.time < j.time;
2287  });
2288 
2289  std::vector<Event> cut_events;
2290  bool added_positive_event = false;
2291  for (const Event& e : events) {
2292  if (e.positive) {
2293  added_positive_event = true;
2294  cut_events.push_back(e);
2295  continue;
2296  }
2297  if (added_positive_event && cut_events.size() > 1) {
2298  // Create cut.
2299  bool cut_generated = true;
2301  IntegerValue(0));
2302  cut.AddTerm(capacity, IntegerValue(-1));
2303  for (const Event& cut_event : cut_events) {
2304  if (helper->IsPresent(cut_event.interval_index)) {
2305  cut.AddTerm(cut_event.demand, IntegerValue(1));
2306  } else {
2307  cut_generated &= cut.AddLiteralTerm(
2308  helper->PresenceLiteral(cut_event.interval_index),
2309  integer_trail->LowerBound(cut_event.demand));
2310  if (!cut_generated) break;
2311  }
2312  }
2313  if (cut_generated) {
2314  // Violation of the cut is checked by AddCut so we don't check
2315  // it here.
2316  manager->AddCut(cut.Build(), "Cumulative", lp_values);
2317  }
2318  }
2319  // Remove the event.
2320  int new_size = 0;
2321  for (int i = 0; i < cut_events.size(); ++i) {
2322  if (cut_events[i].interval_index == e.interval_index) {
2323  continue;
2324  }
2325  cut_events[new_size] = cut_events[i];
2326  new_size++;
2327  }
2328  cut_events.resize(new_size);
2329  added_positive_event = false;
2330  }
2331  };
2332  return result;
2333 }
2334 
2336  const std::vector<IntervalVariable>& intervals, Model* model) {
2337  CutGenerator result;
2338 
2339  SchedulingConstraintHelper* helper =
2340  new SchedulingConstraintHelper(intervals, model);
2341  model->TakeOwnership(helper);
2342 
2343  AddIntegerVariableFromIntervals(helper, model, &result.vars);
2344 
2346  "NoOverlapEnergy", helper,
2347  /*demands=*/{},
2348  /*capacity=*/AffineExpression(IntegerValue(1)), model);
2349  return result;
2350 }
2351 
2353  const std::vector<IntervalVariable>& intervals, Model* model) {
2354  CutGenerator result;
2355 
2356  SchedulingConstraintHelper* helper =
2357  new SchedulingConstraintHelper(intervals, model);
2358  model->TakeOwnership(helper);
2359 
2360  AddIntegerVariableFromIntervals(helper, model, &result.vars);
2361 
2362  Trail* trail = model->GetOrCreate<Trail>();
2363 
2364  result.generate_cuts =
2365  [trail, helper, model](
2367  LinearConstraintManager* manager) {
2368  if (trail->CurrentDecisionLevel() > 0) return;
2369 
2370  // TODO(user): We can do much better in term of complexity:
2371  // Sort all tasks by min start time, loop other them 1 by 1,
2372  // start scanning their successors and stop when the start time of the
2373  // successor is >= duration min of the task.
2374 
2375  // TODO(user): each time we go back to level zero, we will generate
2376  // the same cuts over and over again. It is okay because AddCut() will
2377  // not add duplicate cuts, but it might not be the most efficient way.
2378  for (int index1 = 0; index1 < helper->NumTasks(); ++index1) {
2379  if (!helper->IsPresent(index1)) continue;
2380  for (int index2 = index1 + 1; index2 < helper->NumTasks(); ++index2) {
2381  if (!helper->IsPresent(index2)) continue;
2382 
2383  // Encode only the interesting pairs.
2384  if (helper->EndMax(index1) <= helper->StartMin(index2) ||
2385  helper->EndMax(index2) <= helper->StartMin(index1)) {
2386  continue;
2387  }
2388 
2389  const bool interval_1_can_precede_2 =
2390  helper->EndMin(index1) <= helper->StartMax(index2);
2391  const bool interval_2_can_precede_1 =
2392  helper->EndMin(index2) <= helper->StartMax(index1);
2393 
2394  if (interval_1_can_precede_2 && !interval_2_can_precede_1) {
2395  // interval1.end <= interval2.start
2397  IntegerValue(0));
2398  cut.AddTerm(helper->Ends()[index1], IntegerValue(1));
2399  cut.AddTerm(helper->Starts()[index2], IntegerValue(-1));
2400  manager->AddCut(cut.Build(), "NoOverlapPrecedence", lp_values);
2401  } else if (interval_2_can_precede_1 && !interval_1_can_precede_2) {
2402  // interval2.end <= interval1.start
2404  IntegerValue(0));
2405  cut.AddTerm(helper->Ends()[index2], IntegerValue(1));
2406  cut.AddTerm(helper->Starts()[index1], IntegerValue(-1));
2407  manager->AddCut(cut.Build(), "NoOverlapPrecedence", lp_values);
2408  }
2409  }
2410  }
2411  };
2412 
2413  return result;
2414 }
2415 
2417  const std::vector<IntervalVariable>& intervals, Model* model) {
2418  CutGenerator result;
2419 
2420  SchedulingConstraintHelper* helper =
2421  new SchedulingConstraintHelper(intervals, model);
2422  model->TakeOwnership(helper);
2423 
2424  AddIntegerVariableFromIntervals(helper, model, &result.vars);
2425 
2426  Trail* trail = model->GetOrCreate<Trail>();
2427 
2428  result.generate_cuts = [trail, helper, model](
2430  lp_values,
2431  LinearConstraintManager* manager) {
2432  if (trail->CurrentDecisionLevel() > 0) return;
2433 
2434  struct Event {
2435  AffineExpression end;
2436  IntegerValue start_min;
2437  IntegerValue size_min;
2438  double lp_end;
2439  int index;
2440  };
2441  std::vector<Event> events;
2442 
2443  for (int index1 = 0; index1 < helper->NumTasks(); ++index1) {
2444  if (!helper->IsPresent(index1)) continue;
2445  const IntegerValue size_min = helper->SizeMin(index1);
2446  if (size_min > 0) {
2447  const AffineExpression end_expr = helper->Ends()[index1];
2448  events.push_back({end_expr, helper->StartMin(index1), size_min,
2449  end_expr.LpValue(lp_values), index1});
2450  }
2451  }
2452 
2453  // We generate the cut from:
2454  // E. Balas, On the facial structure of scheduling polyhedra,
2455  // Mathematical Programming Essays in Honor of George B. Dantzig
2456  // Part I, Mathematical Programming Studies, vol. 24,
2457  // Springer, 1985, pp. 179–218.
2458  //
2459  // The original cut is:
2460  // sum(end_min_i * duration_min_i) >=
2461  // (sum(duration_min_i^2) + sum(duration_min_i)^2) / 2
2462  // We streghten this cuts by noticing that if all tasks starts after S,
2463  // then replacing end_min_i by (end_min_i - S) is still valid.
2464  //
2465  // A second difference is that we look at a set of intervals starting
2466  // after a given start_min, sorted by relative
2467  // (end_lp - S) / duration_min.
2468  TopNCuts top_n_cuts(15);
2469 
2470  // Sort by start min to bucketize by start_min.
2471  std::sort(events.begin(), events.end(),
2472  [](const Event& e1, const Event& e2) {
2473  return e1.start_min < e2.start_min;
2474  });
2475  for (int start = 0; start + 1 < events.size(); ++start) {
2476  // Skip to the next start_min value.
2477  if (start > 0 && events[start].start_min == events[start - 1].start_min) {
2478  continue;
2479  }
2480  const IntegerValue sequence_start_min = events[start].start_min;
2481  std::vector<Event> residual_tasks(events.begin() + start,
2482  events.end());
2483  std::sort(residual_tasks.begin(), residual_tasks.end(),
2484  [sequence_start_min](const Event& e1, const Event& e2) {
2485  return ((e1.lp_end - sequence_start_min) / e1.size_min) <
2486  ((e2.lp_end - sequence_start_min) / e2.size_min);
2487  });
2488  int best_end = -1;
2489  double best_efficacy = 0.01;
2490  IntegerValue best_min_contrib(0);
2491 
2492  IntegerValue sum_duration(0);
2493  IntegerValue sum_square_duration(0);
2494  double lp_contrib = 0;
2495  IntegerValue current_start_min(kMaxIntegerValue);
2496  for (int i = 0; i < residual_tasks.size(); ++i) {
2497  DCHECK_GE(residual_tasks[i].start_min, sequence_start_min);
2498  const IntegerValue duration = residual_tasks[i].size_min;
2499  sum_duration += duration;
2500  sum_square_duration += duration * duration;
2501  lp_contrib +=
2502  residual_tasks[i].lp_end * residual_tasks[i].size_min.value();
2503  current_start_min =
2504  std::min(current_start_min, residual_tasks[i].start_min);
2505 
2506  const IntegerValue min_contrib =
2507  (sum_duration * sum_duration + sum_square_duration) / 2 +
2508  current_start_min * sum_duration;
2509  const double efficacy = (min_contrib.value() - lp_contrib) /
2510  std::sqrt(sum_square_duration.value());
2511  if (efficacy > best_efficacy) {
2512  best_efficacy = efficacy;
2513  best_end = i;
2514  best_min_contrib = min_contrib;
2515  }
2516  }
2517  if (best_end != -1) {
2518  LinearConstraintBuilder cut(model, best_min_contrib,
2520  for (int i = 0; i <= best_end; ++i) {
2521  cut.AddTerm(residual_tasks[i].end, residual_tasks[i].size_min);
2522  }
2523  top_n_cuts.AddCut(cut.Build(), "NoOverlapBalasArea", lp_values);
2524  }
2525  }
2526  top_n_cuts.TransferToManager(lp_values, manager);
2527  };
2528  return result;
2529 }
2530 
2532  const std::vector<IntegerVariable>& base_variables, Model* model) {
2533  // Filter base_variables to only keep the one with a literal view, and
2534  // do the conversion.
2535  std::vector<IntegerVariable> variables;
2536  std::vector<Literal> literals;
2537  absl::flat_hash_map<LiteralIndex, IntegerVariable> positive_map;
2538  absl::flat_hash_map<LiteralIndex, IntegerVariable> negative_map;
2539  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
2540  auto* encoder = model->GetOrCreate<IntegerEncoder>();
2541  for (const IntegerVariable var : base_variables) {
2542  if (integer_trail->LowerBound(var) != IntegerValue(0)) continue;
2543  if (integer_trail->UpperBound(var) != IntegerValue(1)) continue;
2544  const LiteralIndex literal_index = encoder->GetAssociatedLiteral(
2545  IntegerLiteral::GreaterOrEqual(var, IntegerValue(1)));
2546  if (literal_index != kNoLiteralIndex) {
2547  variables.push_back(var);
2548  literals.push_back(Literal(literal_index));
2549  positive_map[literal_index] = var;
2550  negative_map[Literal(literal_index).NegatedIndex()] = var;
2551  }
2552  }
2553  CutGenerator result;
2554  result.vars = variables;
2555  auto* implication_graph = model->GetOrCreate<BinaryImplicationGraph>();
2556  result.generate_cuts =
2557  [variables, literals, implication_graph, positive_map, negative_map,
2559  LinearConstraintManager* manager) {
2560  std::vector<double> packed_values;
2561  for (int i = 0; i < literals.size(); ++i) {
2562  packed_values.push_back(lp_values[variables[i]]);
2563  }
2564  const std::vector<std::vector<Literal>> at_most_ones =
2565  implication_graph->GenerateAtMostOnesWithLargeWeight(literals,
2566  packed_values);
2567 
2568  for (const std::vector<Literal>& at_most_one : at_most_ones) {
2569  // We need to express such "at most one" in term of the initial
2570  // variables, so we do not use the
2571  // LinearConstraintBuilder::AddLiteralTerm() here.
2572  LinearConstraintBuilder builder(
2573  model, IntegerValue(std::numeric_limits<int64_t>::min()),
2574  IntegerValue(1));
2575  for (const Literal l : at_most_one) {
2576  if (ContainsKey(positive_map, l.Index())) {
2577  builder.AddTerm(positive_map.at(l.Index()), IntegerValue(1));
2578  } else {
2579  // Add 1 - X to the linear constraint.
2580  builder.AddTerm(negative_map.at(l.Index()), IntegerValue(-1));
2581  builder.AddConstant(IntegerValue(1));
2582  }
2583  }
2584 
2585  manager->AddCut(builder.Build(), "clique", lp_values);
2586  }
2587  };
2588  return result;
2589 }
2590 
2591 } // namespace sat
2592 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define CHECK(condition)
Definition: base/logging.h:498
#define DCHECK_NE(val1, val2)
Definition: base/logging.h:894
#define CHECK_LT(val1, val2)
Definition: base/logging.h:708
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:705
#define CHECK_GE(val1, val2)
Definition: base/logging.h:709
#define CHECK_GT(val1, val2)
Definition: base/logging.h:710
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:897
#define CHECK_NE(val1, val2)
Definition: base/logging.h:706
#define DCHECK_LT(val1, val2)
Definition: base/logging.h:896
#define LOG(severity)
Definition: base/logging.h:423
#define DCHECK(condition)
Definition: base/logging.h:892
#define VLOG(verboselevel)
Definition: base/logging.h:986
We call domain any subset of Int64 = [kint64min, kint64max].
Domain UnionWith(const Domain &domain) const
Returns the union of D and domain.
void Init(const std::vector< double > &profits, const std::vector< double > &weights, const double capacity)
double Solve(TimeLimit *time_limit, bool *is_solution_optimal)
void set_solution_upper_bound_threshold(const double solution_upper_bound_threshold)
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:105
bool LimitReached()
Returns true when the external limit is true, or the deterministic time is over the deterministic lim...
Definition: time_limit.h:532
bool TrySimpleKnapsack(const LinearConstraint base_ct, const std::vector< double > &lp_values, const std::vector< IntegerValue > &lower_bounds, const std::vector< IntegerValue > &upper_bounds)
Definition: cuts.cc:1159
void ProcessUpperBoundedConstraint(const absl::StrongVector< IntegerVariable, double > &lp_values, LinearConstraint *cut)
Definition: cuts.cc:1495
BestImpliedBoundInfo GetCachedImpliedBoundInfo(IntegerVariable var)
Definition: cuts.cc:1504
const IntegerVariable GetLiteralView(Literal lit) const
Definition: integer.h:425
void ComputeCut(RoundingOptions options, const std::vector< double > &lp_values, const std::vector< IntegerValue > &lower_bounds, const std::vector< IntegerValue > &upper_bounds, ImpliedBoundsProcessor *ib_processor, LinearConstraint *cut)
Definition: cuts.cc:710
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
Definition: integer.h:1360
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Definition: integer.h:1355
const Domain & InitialVariableDomain(IntegerVariable var) const
Definition: integer.cc:647
ABSL_MUST_USE_RESULT bool AddLiteralTerm(Literal lit, IntegerValue coeff)
void AddTerm(IntegerVariable var, IntegerValue coeff)
bool AddCut(LinearConstraint ct, std::string type_name, const absl::StrongVector< IntegerVariable, double > &lp_solution, std::string extra_info="")
LiteralIndex NegatedIndex() const
Definition: sat_base.h:86
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:38
const std::vector< AffineExpression > & Starts() const
Definition: intervals.h:320
const std::vector< AffineExpression > & Sizes() const
Definition: intervals.h:322
const std::vector< AffineExpression > & Ends() const
Definition: intervals.h:321
void AddCut(LinearConstraint ct, const std::string &name, const absl::StrongVector< IntegerVariable, double > &lp_solution)
void TransferToManager(const absl::StrongVector< IntegerVariable, double > &lp_solution, LinearConstraintManager *manager)
int64_t b
int64_t a
SharedTimeLimit * time_limit
int64_t value
IntVar * var
Definition: expr_array.cc:1874
double upper_bound
GRBmodel * model
const int INFO
Definition: log_severity.h:31
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition: stl_util.h:58
bool ContainsKey(const Collection &collection, const Key &key)
Definition: map_util.h:200
static double ToDouble(double f)
Definition: lp_types.h:69
void ConvertToKnapsackForm(const LinearConstraint &constraint, std::vector< LinearConstraint > *knapsack_constraints, IntegerTrail *integer_trail)
Definition: cuts.cc:390
IntegerValue FloorRatio(IntegerValue dividend, IntegerValue positive_divisor)
Definition: integer.h:91
bool AddProductTo(IntegerValue a, IntegerValue b, IntegerValue *result)
Definition: integer.h:111
std::function< void(Model *)> GreaterOrEqual(IntegerVariable v, int64_t lb)
Definition: integer.h:1500
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
LinearConstraint GetPreprocessedLinearConstraint(const LinearConstraint &constraint, const absl::StrongVector< IntegerVariable, double > &lp_values, const IntegerTrail &integer_trail)
Definition: cuts.cc:252
IntType IntTypeAbs(IntType t)
Definition: integer.h:78
CutGenerator CreateNoOverlapPrecedenceCutGenerator(const std::vector< IntervalVariable > &intervals, Model *model)
Definition: cuts.cc:2352
IntegerValue CeilRatio(IntegerValue dividend, IntegerValue positive_divisor)
Definition: integer.h:82
const LiteralIndex kNoLiteralIndex(-1)
bool CanFormValidKnapsackCover(const LinearConstraint &preprocessed_constraint, const absl::StrongVector< IntegerVariable, double > &lp_values, const IntegerTrail &integer_trail)
Definition: cuts.cc:372
CutGenerator CreateCumulativeCutGenerator(const std::vector< IntervalVariable > &intervals, const IntegerVariable capacity, const std::vector< IntegerVariable > &demands, Model *model)
Definition: cuts.cc:2202
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue)
std::function< void(const absl::StrongVector< IntegerVariable, double > &, LinearConstraintManager *)> GenerateCumulativeCut(const std::string &cut_name, SchedulingConstraintHelper *helper, const std::vector< IntegerVariable > &demands, AffineExpression capacity, Model *model)
Definition: cuts.cc:2023
void RemoveZeroTerms(LinearConstraint *constraint)
IntegerValue GetFactorT(IntegerValue rhs_remainder, IntegerValue divisor, IntegerValue max_t)
Definition: cuts.cc:618
double GetKnapsackUpperBound(std::vector< KnapsackItem > items, const double capacity)
Definition: cuts.cc:320
CutGenerator CreateOverlappingCumulativeCutGenerator(const std::vector< IntervalVariable > &intervals, const IntegerVariable capacity, const std::vector< IntegerVariable > &demands, Model *model)
Definition: cuts.cc:2221
CutGenerator CreateSquareCutGenerator(IntegerVariable y, IntegerVariable x, Model *model)
Definition: cuts.cc:1428
bool CanBeFilteredUsingCutLowerBound(const LinearConstraint &preprocessed_constraint, const absl::StrongVector< IntegerVariable, double > &lp_values, const IntegerTrail &integer_trail)
Definition: cuts.cc:292
const IntegerVariable kNoIntegerVariable(-1)
void MakeAllCoefficientsPositive(LinearConstraint *constraint)
std::function< IntegerVariable(Model *)> NewIntegerVariableFromLiteral(Literal lit)
Definition: integer.h:1449
IntegerVariable PositiveVariable(IntegerVariable i)
Definition: integer.h:139
CutGenerator CreateLinMaxCutGenerator(const IntegerVariable target, const std::vector< LinearExpression > &exprs, const std::vector< IntegerVariable > &z_vars, Model *model)
Definition: cuts.cc:1919
CutGenerator CreateAllDifferentCutGenerator(const std::vector< IntegerVariable > &vars, Model *model)
Definition: cuts.cc:1822
bool CanBeFilteredUsingKnapsackUpperBound(const LinearConstraint &constraint, const absl::StrongVector< IntegerVariable, double > &lp_values, const IntegerTrail &integer_trail)
Definition: cuts.cc:338
std::function< IntegerValue(IntegerValue)> GetSuperAdditiveRoundingFunction(IntegerValue rhs_remainder, IntegerValue divisor, IntegerValue t, IntegerValue max_scaling)
Definition: cuts.cc:626
CutGenerator CreateNoOverlapBalasCutGenerator(const std::vector< IntervalVariable > &intervals, Model *model)
Definition: cuts.cc:2416
void MakeAllVariablesPositive(LinearConstraint *constraint)
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Definition: integer.cc:29
CutGenerator CreateNoOverlapEnergyCutGenerator(const std::vector< IntervalVariable > &intervals, Model *model)
Definition: cuts.cc:2335
IntegerValue GetCoefficientOfPositiveVar(const IntegerVariable var, const LinearExpression &expr)
CutGenerator CreateKnapsackCoverCutGenerator(const std::vector< LinearConstraint > &base_constraints, const std::vector< IntegerVariable > &vars, Model *model)
Definition: cuts.cc:439
void AddIntegerVariableFromIntervals(SchedulingConstraintHelper *helper, Model *model, std::vector< IntegerVariable > *vars)
Definition: cuts.cc:1988
bool ConstraintIsTriviallyTrue(const LinearConstraint &constraint, const IntegerTrail &integer_trail)
Definition: cuts.cc:276
void CleanTermsAndFillConstraint(std::vector< std::pair< IntegerVariable, IntegerValue >> *terms, LinearConstraint *constraint)
bool LiftKnapsackCut(const LinearConstraint &constraint, const absl::StrongVector< IntegerVariable, double > &lp_values, const std::vector< IntegerValue > &cut_vars_original_coefficients, const IntegerTrail &integer_trail, TimeLimit *time_limit, LinearConstraint *cut)
Definition: cuts.cc:174
CutGenerator CreatePositiveMultiplicationCutGenerator(IntegerVariable z, IntegerVariable x, IntegerVariable y, Model *model)
Definition: cuts.cc:1332
CutGenerator CreateCliqueCutGenerator(const std::vector< IntegerVariable > &base_variables, Model *model)
Definition: cuts.cc:2531
void DivideByGCD(LinearConstraint *constraint)
double ComputeActivity(const LinearConstraint &constraint, const absl::StrongVector< IntegerVariable, double > &values)
double ToDouble(IntegerValue value)
Definition: integer.h:70
Collection of objects used to extend the Constraint Solver library.
int64_t SumOfKMinValueInDomain(const Domain &domain, int k)
int64_t CapAdd(int64_t x, int64_t y)
int64_t FloorRatio(int64_t value, int64_t positive_coeff)
int64_t CapSub(int64_t x, int64_t y)
int64_t SumOfKMaxValueInDomain(const Domain &domain, int k)
int64_t CapProd(int64_t x, int64_t y)
int64_t weight
Definition: pack.cc:510
int index
Definition: pack.cc:509
int64_t demand
Definition: resource.cc:125
int64_t time
Definition: resource.cc:1691
Fractional ratio
int64_t coefficient
int64_t capacity
std::vector< double > lower_bounds
std::vector< double > upper_bounds
Rev< int64_t > start_max
Rev< int64_t > start_min
Rev< int64_t > end_min
double LpValue(const absl::StrongVector< IntegerVariable, double > &lp_values) const
Definition: integer.h:238
std::vector< IntegerVariable > vars
Definition: cuts.h:42
std::function< void(const absl::StrongVector< IntegerVariable, double > &lp_values, LinearConstraintManager *manager)> generate_cuts
Definition: cuts.h:46
std::vector< std::pair< IntegerVariable, IntegerValue > > terms
Definition: cuts.h:80
void AddTerm(IntegerVariable var, IntegerValue coeff)