OR-Tools  8.0
cuts.cc
Go to the documentation of this file.
1 // Copyright 2010-2018 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 <functional>
19 #include <memory>
20 #include <utility>
21 #include <vector>
22 
25 #include "ortools/base/stl_util.h"
26 #include "ortools/sat/integer.h"
27 #include "ortools/sat/intervals.h"
29 #include "ortools/sat/sat_base.h"
31 
32 namespace operations_research {
33 namespace sat {
34 
35 namespace {
36 
37 // Minimum amount of violation of the cut constraint by the solution. This
38 // is needed to avoid numerical issues and adding cuts with minor effect.
39 const double kMinCutViolation = 1e-4;
40 
41 // Returns a constraint that disallow all given variables to be at their current
42 // upper bound. The arguments must form a non-trival constraint of the form
43 // sum terms (coeff * var) <= upper_bound.
44 LinearConstraint GenerateKnapsackCutForCover(
45  const std::vector<IntegerVariable>& vars,
46  const std::vector<IntegerValue>& coeffs, const IntegerValue upper_bound,
47  const IntegerTrail& integer_trail) {
48  CHECK_EQ(vars.size(), coeffs.size());
49  CHECK_GT(vars.size(), 0);
50  LinearConstraint cut;
51  IntegerValue cut_upper_bound = IntegerValue(0);
52  IntegerValue max_coeff = coeffs[0];
53  // slack = \sum_{i}(coeffs[i] * upper_bound[i]) - upper_bound.
54  IntegerValue slack = -upper_bound;
55  for (int i = 0; i < vars.size(); ++i) {
56  const IntegerValue var_upper_bound =
57  integer_trail.LevelZeroUpperBound(vars[i]);
58  cut_upper_bound += var_upper_bound;
59  cut.vars.push_back(vars[i]);
60  cut.coeffs.push_back(IntegerValue(1));
61  max_coeff = std::max(max_coeff, coeffs[i]);
62  slack += coeffs[i] * var_upper_bound;
63  }
64  CHECK_GT(slack, 0.0) << "Invalid cover for knapsack cut.";
65  cut_upper_bound -= CeilRatio(slack, max_coeff);
66  cut.lb = kMinIntegerValue;
67  cut.ub = cut_upper_bound;
68  VLOG(2) << "Generated Knapsack Constraint:" << cut.DebugString();
69  return cut;
70 }
71 
72 bool SolutionSatisfiesConstraint(
73  const LinearConstraint& constraint,
74  const gtl::ITIVector<IntegerVariable, double>& lp_values) {
75  const double activity = ComputeActivity(constraint, lp_values);
76  const double tolerance = 1e-6;
77  return (activity <= constraint.ub.value() + tolerance &&
78  activity >= constraint.lb.value() - tolerance)
79  ? true
80  : false;
81 }
82 
83 bool SmallRangeAndAllCoefficientsMagnitudeAreTheSame(
84  const LinearConstraint& constraint, IntegerTrail* integer_trail) {
85  if (constraint.vars.empty()) return true;
86 
87  const int64 magnitude = std::abs(constraint.coeffs[0].value());
88  for (int i = 1; i < constraint.coeffs.size(); ++i) {
89  const IntegerVariable var = constraint.vars[i];
90  if (integer_trail->LevelZeroUpperBound(var) -
91  integer_trail->LevelZeroLowerBound(var) >
92  1) {
93  return false;
94  }
95  if (std::abs(constraint.coeffs[i].value()) != magnitude) {
96  return false;
97  }
98  }
99  return true;
100 }
101 
102 bool AllVarsTakeIntegerValue(
103  const std::vector<IntegerVariable> vars,
104  const gtl::ITIVector<IntegerVariable, double>& lp_values) {
105  for (IntegerVariable var : vars) {
106  if (std::abs(lp_values[var] - std::round(lp_values[var])) > 1e-6) {
107  return false;
108  }
109  }
110  return true;
111 }
112 
113 // Returns smallest cover size for the given constraint taking into account
114 // level zero bounds. Smallest Cover size is computed as follows.
115 // 1. Compute the upper bound if all variables are shifted to have zero lower
116 // bound.
117 // 2. Sort all terms (coefficient * shifted upper bound) in non decreasing
118 // order.
119 // 3. Add terms in cover until term sum is smaller or equal to upper bound.
120 // 4. Add the last item which violates the upper bound. This forms the smallest
121 // cover. Return the size of this cover.
122 int GetSmallestCoverSize(const LinearConstraint& constraint,
123  const IntegerTrail& integer_trail) {
124  IntegerValue ub = constraint.ub;
125  std::vector<IntegerValue> sorted_terms;
126  for (int i = 0; i < constraint.vars.size(); ++i) {
127  const IntegerValue coeff = constraint.coeffs[i];
128  const IntegerVariable var = constraint.vars[i];
129  const IntegerValue var_ub = integer_trail.LevelZeroUpperBound(var);
130  const IntegerValue var_lb = integer_trail.LevelZeroLowerBound(var);
131  ub -= var_lb * coeff;
132  sorted_terms.push_back(coeff * (var_ub - var_lb));
133  }
134  std::sort(sorted_terms.begin(), sorted_terms.end(),
135  std::greater<IntegerValue>());
136  int smallest_cover_size = 0;
137  IntegerValue sorted_term_sum = IntegerValue(0);
138  while (sorted_term_sum <= ub &&
139  smallest_cover_size < constraint.vars.size()) {
140  sorted_term_sum += sorted_terms[smallest_cover_size++];
141  }
142  return smallest_cover_size;
143 }
144 
145 bool ConstraintIsEligibleForLifting(const LinearConstraint& constraint,
146  const IntegerTrail& integer_trail) {
147  for (const IntegerVariable var : constraint.vars) {
148  if (integer_trail.LevelZeroLowerBound(var) != IntegerValue(0) ||
149  integer_trail.LevelZeroUpperBound(var) != IntegerValue(1)) {
150  return false;
151  }
152  }
153  return true;
154 }
155 } // namespace
156 
158  const LinearConstraint& constraint,
160  const std::vector<IntegerValue>& cut_vars_original_coefficients,
161  const IntegerTrail& integer_trail, TimeLimit* time_limit,
162  LinearConstraint* cut) {
163  std::set<IntegerVariable> vars_in_cut;
164  for (IntegerVariable var : cut->vars) {
165  vars_in_cut.insert(var);
166  }
167 
168  std::vector<std::pair<IntegerValue, IntegerVariable>> non_zero_vars;
169  std::vector<std::pair<IntegerValue, IntegerVariable>> zero_vars;
170  for (int i = 0; i < constraint.vars.size(); ++i) {
171  const IntegerVariable var = constraint.vars[i];
172  if (integer_trail.LevelZeroLowerBound(var) != IntegerValue(0) ||
173  integer_trail.LevelZeroUpperBound(var) != IntegerValue(1)) {
174  continue;
175  }
176  if (vars_in_cut.find(var) != vars_in_cut.end()) continue;
177  const IntegerValue coeff = constraint.coeffs[i];
178  if (lp_values[var] <= 1e-6) {
179  zero_vars.push_back({coeff, var});
180  } else {
181  non_zero_vars.push_back({coeff, var});
182  }
183  }
184 
185  // Decide lifting sequence (nonzeros, zeros in nonincreasing order
186  // of coefficient ).
187  std::sort(non_zero_vars.rbegin(), non_zero_vars.rend());
188  std::sort(zero_vars.rbegin(), zero_vars.rend());
189 
190  std::vector<std::pair<IntegerValue, IntegerVariable>> lifting_sequence(
191  std::move(non_zero_vars));
192 
193  lifting_sequence.insert(lifting_sequence.end(), zero_vars.begin(),
194  zero_vars.end());
195 
196  // Form Knapsack.
197  std::vector<double> lifting_profits;
198  std::vector<double> lifting_weights;
199  for (int i = 0; i < cut->vars.size(); ++i) {
200  lifting_profits.push_back(cut->coeffs[i].value());
201  lifting_weights.push_back(cut_vars_original_coefficients[i].value());
202  }
203 
204  // Lift the cut.
205  bool is_lifted = false;
206  bool is_solution_optimal = false;
207  KnapsackSolverForCuts knapsack_solver("Knapsack cut lifter");
208  for (auto entry : lifting_sequence) {
209  is_solution_optimal = false;
210  const IntegerValue var_original_coeff = entry.first;
211  const IntegerVariable var = entry.second;
212  const IntegerValue lifting_capacity = constraint.ub - entry.first;
213  if (lifting_capacity <= IntegerValue(0)) continue;
214  knapsack_solver.Init(lifting_profits, lifting_weights,
215  lifting_capacity.value());
216  knapsack_solver.set_node_limit(100);
217  // NOTE: Since all profits and weights are integer, solution of
218  // knapsack is also integer.
219  // TODO(user): Use an integer solver or heuristic.
220  knapsack_solver.Solve(time_limit, &is_solution_optimal);
221  const double knapsack_upper_bound =
222  std::round(knapsack_solver.GetUpperBound());
223  const IntegerValue cut_coeff = cut->ub - knapsack_upper_bound;
224  if (cut_coeff > IntegerValue(0)) {
225  is_lifted = true;
226  cut->vars.push_back(var);
227  cut->coeffs.push_back(cut_coeff);
228  lifting_profits.push_back(cut_coeff.value());
229  lifting_weights.push_back(var_original_coeff.value());
230  }
231  }
232  return is_lifted;
233 }
234 
236  const LinearConstraint& constraint,
238  const IntegerTrail& integer_trail) {
239  IntegerValue ub = constraint.ub;
240  LinearConstraint constraint_with_left_vars;
241  for (int i = 0; i < constraint.vars.size(); ++i) {
242  const IntegerVariable var = constraint.vars[i];
243  const IntegerValue var_ub = integer_trail.LevelZeroUpperBound(var);
244  const IntegerValue coeff = constraint.coeffs[i];
245  if (var_ub.value() - lp_values[var] <= 1.0 - kMinCutViolation) {
246  constraint_with_left_vars.vars.push_back(var);
247  constraint_with_left_vars.coeffs.push_back(coeff);
248  } else {
249  // Variable not in cut
250  const IntegerValue var_lb = integer_trail.LevelZeroLowerBound(var);
251  ub -= coeff * var_lb;
252  }
253  }
254  constraint_with_left_vars.ub = ub;
255  constraint_with_left_vars.lb = constraint.lb;
256  return constraint_with_left_vars;
257 }
258 
260  const IntegerTrail& integer_trail) {
261  IntegerValue term_sum = IntegerValue(0);
262  for (int i = 0; i < constraint.vars.size(); ++i) {
263  const IntegerVariable var = constraint.vars[i];
264  const IntegerValue var_ub = integer_trail.LevelZeroUpperBound(var);
265  const IntegerValue coeff = constraint.coeffs[i];
266  term_sum += coeff * var_ub;
267  }
268  if (term_sum <= constraint.ub) {
269  VLOG(2) << "Filtered by cover filter";
270  return true;
271  }
272  return false;
273 }
274 
276  const LinearConstraint& preprocessed_constraint,
278  const IntegerTrail& integer_trail) {
279  std::vector<double> variable_upper_bound_distances;
280  for (const IntegerVariable var : preprocessed_constraint.vars) {
281  const IntegerValue var_ub = integer_trail.LevelZeroUpperBound(var);
282  variable_upper_bound_distances.push_back(var_ub.value() - lp_values[var]);
283  }
284  // Compute the min cover size.
285  const int smallest_cover_size =
286  GetSmallestCoverSize(preprocessed_constraint, integer_trail);
287 
288  std::nth_element(
289  variable_upper_bound_distances.begin(),
290  variable_upper_bound_distances.begin() + smallest_cover_size - 1,
291  variable_upper_bound_distances.end());
292  double cut_lower_bound = 0.0;
293  for (int i = 0; i < smallest_cover_size; ++i) {
294  cut_lower_bound += variable_upper_bound_distances[i];
295  }
296  if (cut_lower_bound >= 1.0 - kMinCutViolation) {
297  VLOG(2) << "Filtered by kappa heuristic";
298  return true;
299  }
300  return false;
301 }
302 
303 double GetKnapsackUpperBound(std::vector<KnapsackItem> items,
304  const double capacity) {
305  // Sort items by value by weight ratio.
306  std::sort(items.begin(), items.end(), std::greater<KnapsackItem>());
307  double left_capacity = capacity;
308  double profit = 0.0;
309  for (const KnapsackItem item : items) {
310  if (item.weight <= left_capacity) {
311  profit += item.profit;
312  left_capacity -= item.weight;
313  } else {
314  profit += (left_capacity / item.weight) * item.profit;
315  break;
316  }
317  }
318  return profit;
319 }
320 
322  const LinearConstraint& constraint,
324  const IntegerTrail& integer_trail) {
325  std::vector<KnapsackItem> items;
326  double capacity = -constraint.ub.value() - 1.0;
327  double sum_variable_profit = 0;
328  for (int i = 0; i < constraint.vars.size(); ++i) {
329  const IntegerVariable var = constraint.vars[i];
330  const IntegerValue var_ub = integer_trail.LevelZeroUpperBound(var);
331  const IntegerValue var_lb = integer_trail.LevelZeroLowerBound(var);
332  const IntegerValue coeff = constraint.coeffs[i];
333  KnapsackItem item;
334  item.profit = var_ub.value() - lp_values[var];
335  item.weight = (coeff * (var_ub - var_lb)).value();
336  items.push_back(item);
337  capacity += (coeff * var_ub).value();
338  sum_variable_profit += item.profit;
339  }
340 
341  // Return early if the required upper bound is negative since all the profits
342  // are non negative.
343  if (sum_variable_profit - 1.0 + kMinCutViolation < 0.0) return false;
344 
345  // Get the knapsack upper bound.
346  const double knapsack_upper_bound =
347  GetKnapsackUpperBound(std::move(items), capacity);
348  if (knapsack_upper_bound < sum_variable_profit - 1.0 + kMinCutViolation) {
349  VLOG(2) << "Filtered by knapsack upper bound";
350  return true;
351  }
352  return false;
353 }
354 
356  const LinearConstraint& preprocessed_constraint,
358  const IntegerTrail& integer_trail) {
359  if (ConstraintIsTriviallyTrue(preprocessed_constraint, integer_trail)) {
360  return false;
361  }
362  if (CanBeFilteredUsingCutLowerBound(preprocessed_constraint, lp_values,
363  integer_trail)) {
364  return false;
365  }
366  if (CanBeFilteredUsingKnapsackUpperBound(preprocessed_constraint, lp_values,
367  integer_trail)) {
368  return false;
369  }
370  return true;
371 }
372 
374  std::vector<LinearConstraint>* knapsack_constraints,
375  IntegerTrail* integer_trail) {
376  // If all coefficient are the same, the generated knapsack cuts cannot be
377  // stronger than the constraint itself. However, when we substitute variables
378  // using the implication graph, this is not longer true. So we only skip
379  // constraints with same coeff and no substitutions.
380  if (SmallRangeAndAllCoefficientsMagnitudeAreTheSame(constraint,
381  integer_trail)) {
382  return;
383  }
384  if (constraint.ub < kMaxIntegerValue) {
385  LinearConstraint canonical_knapsack_form;
386 
387  // Negate the variables with negative coefficients.
388  for (int i = 0; i < constraint.vars.size(); ++i) {
389  const IntegerVariable var = constraint.vars[i];
390  const IntegerValue coeff = constraint.coeffs[i];
391  if (coeff > IntegerValue(0)) {
392  canonical_knapsack_form.AddTerm(var, coeff);
393  } else {
394  canonical_knapsack_form.AddTerm(NegationOf(var), -coeff);
395  }
396  }
397  canonical_knapsack_form.ub = constraint.ub;
398  canonical_knapsack_form.lb = kMinIntegerValue;
399  knapsack_constraints->push_back(canonical_knapsack_form);
400  }
401 
402  if (constraint.lb > kMinIntegerValue) {
403  LinearConstraint canonical_knapsack_form;
404 
405  // Negate the variables with positive coefficients.
406  for (int i = 0; i < constraint.vars.size(); ++i) {
407  const IntegerVariable var = constraint.vars[i];
408  const IntegerValue coeff = constraint.coeffs[i];
409  if (coeff > IntegerValue(0)) {
410  canonical_knapsack_form.AddTerm(NegationOf(var), coeff);
411  } else {
412  canonical_knapsack_form.AddTerm(var, -coeff);
413  }
414  }
415  canonical_knapsack_form.ub = -constraint.lb;
416  canonical_knapsack_form.lb = kMinIntegerValue;
417  knapsack_constraints->push_back(canonical_knapsack_form);
418  }
419 }
420 
421 // TODO(user): Move the cut generator into a class and reuse variables.
423  const std::vector<LinearConstraint>& base_constraints,
424  const std::vector<IntegerVariable>& vars, Model* model) {
425  CutGenerator result;
426  result.vars = vars;
427 
428  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
429  std::vector<LinearConstraint> knapsack_constraints;
430  for (const LinearConstraint& constraint : base_constraints) {
431  // There is often a lot of small linear base constraints and it doesn't seem
432  // super useful to generate cuts for constraints of size 2. Any valid cut
433  // of size 1 should be already infered by the propagation.
434  //
435  // TODO(user): The case of size 2 is a bit less clear. investigate more if
436  // it is useful.
437  if (constraint.vars.size() <= 2) continue;
438 
439  ConvertToKnapsackForm(constraint, &knapsack_constraints, integer_trail);
440  }
441  VLOG(1) << "#knapsack constraints: " << knapsack_constraints.size();
442 
443  // Note(user): for Knapsack cuts, it seems always advantageous to replace a
444  // variable X by a TIGHT lower bound of the form "coeff * binary + lb". This
445  // will not change "covers" but can only result in more violation by the
446  // current LP solution.
447  ImpliedBoundsProcessor implied_bounds_processor(
448  vars, integer_trail, model->GetOrCreate<ImpliedBounds>());
449 
450  // TODO(user): do not add generator if there are no knapsack constraints.
451  result.generate_cuts = [implied_bounds_processor, knapsack_constraints, vars,
452  model, integer_trail](
454  lp_values,
455  LinearConstraintManager* manager) {
456  // TODO(user): When we use implied-bound substitution, we might still infer
457  // an interesting cut even if all variables are integer. See if we still
458  // want to skip all such constraints.
459  if (AllVarsTakeIntegerValue(vars, lp_values)) return;
460 
461  KnapsackSolverForCuts knapsack_solver(
462  "Knapsack on demand cover cut generator");
463  int64 skipped_constraints = 0;
464  LinearConstraint mutable_constraint;
465 
466  // Iterate through all knapsack constraints.
467  implied_bounds_processor.ClearCache();
468  for (const LinearConstraint& constraint : knapsack_constraints) {
469  if (model->GetOrCreate<TimeLimit>()->LimitReached()) break;
470  VLOG(2) << "Processing constraint: " << constraint.DebugString();
471 
472  mutable_constraint = constraint;
473  implied_bounds_processor.ProcessUpperBoundedConstraint(
474  lp_values, &mutable_constraint);
475  MakeAllCoefficientsPositive(&mutable_constraint);
476 
477  const LinearConstraint preprocessed_constraint =
478  GetPreprocessedLinearConstraint(mutable_constraint, lp_values,
479  *integer_trail);
480  if (preprocessed_constraint.vars.empty()) continue;
481 
482  if (!CanFormValidKnapsackCover(preprocessed_constraint, lp_values,
483  *integer_trail)) {
484  skipped_constraints++;
485  continue;
486  }
487 
488  // Profits are (upper_bounds[i] - lp_values[i]) for knapsack variables.
489  std::vector<double> profits;
490  profits.reserve(preprocessed_constraint.vars.size());
491 
492  // Weights are (coeffs[i] * (upper_bound[i] - lower_bound[i])).
493  std::vector<double> weights;
494  weights.reserve(preprocessed_constraint.vars.size());
495 
496  double capacity = -preprocessed_constraint.ub.value() - 1.0;
497 
498  // Compute and store the sum of variable profits. This is the constant
499  // part of the objective of the problem we are trying to solve. Hence
500  // this part is not supplied to the knapsack_solver and is subtracted
501  // when we receive the knapsack solution.
502  double sum_variable_profit = 0;
503 
504  // Compute the profits, the weights and the capacity for the knapsack
505  // instance.
506  for (int i = 0; i < preprocessed_constraint.vars.size(); ++i) {
507  const IntegerVariable var = preprocessed_constraint.vars[i];
508  const double coefficient = preprocessed_constraint.coeffs[i].value();
509  const double var_ub = ToDouble(integer_trail->LevelZeroUpperBound(var));
510  const double var_lb = ToDouble(integer_trail->LevelZeroLowerBound(var));
511  const double variable_profit = var_ub - lp_values[var];
512  profits.push_back(variable_profit);
513 
514  sum_variable_profit += variable_profit;
515 
516  const double weight = coefficient * (var_ub - var_lb);
517  weights.push_back(weight);
518  capacity += weight + coefficient * var_lb;
519  }
520  if (capacity < 0.0) continue;
521 
522  std::vector<IntegerVariable> cut_vars;
523  std::vector<IntegerValue> cut_vars_original_coefficients;
524 
525  VLOG(2) << "Knapsack size: " << profits.size();
526  knapsack_solver.Init(profits, weights, capacity);
527 
528  // Set the time limit for the knapsack solver.
529  const double time_limit_for_knapsack_solver =
530  model->GetOrCreate<TimeLimit>()->GetTimeLeft();
531 
532  // Solve the instance and subtract the constant part to compute the
533  // sum_of_distance_to_ub_for_vars_in_cover.
534  // TODO(user): Consider solving the instance approximately.
535  bool is_solution_optimal = false;
536  knapsack_solver.set_solution_upper_bound_threshold(
537  sum_variable_profit - 1.0 + kMinCutViolation);
538  // TODO(user): Consider providing lower bound threshold as
539  // sum_variable_profit - 1.0 + kMinCutViolation.
540  // TODO(user): Set node limit for knapsack solver.
541  auto time_limit_for_solver =
542  absl::make_unique<TimeLimit>(time_limit_for_knapsack_solver);
543  const double sum_of_distance_to_ub_for_vars_in_cover =
544  sum_variable_profit -
545  knapsack_solver.Solve(time_limit_for_solver.get(),
546  &is_solution_optimal);
547  if (is_solution_optimal) {
548  VLOG(2) << "Knapsack Optimal solution found yay !";
549  }
550  if (time_limit_for_solver->LimitReached()) {
551  VLOG(1) << "Knapsack Solver run out of time limit.";
552  }
553  if (sum_of_distance_to_ub_for_vars_in_cover < 1.0 - kMinCutViolation) {
554  // Constraint is eligible for the cover.
555 
556  IntegerValue constraint_ub_for_cut = preprocessed_constraint.ub;
557  std::set<IntegerVariable> vars_in_cut;
558  for (int i = 0; i < preprocessed_constraint.vars.size(); ++i) {
559  const IntegerVariable var = preprocessed_constraint.vars[i];
560  const IntegerValue coefficient = preprocessed_constraint.coeffs[i];
561  if (!knapsack_solver.best_solution(i)) {
562  cut_vars.push_back(var);
563  cut_vars_original_coefficients.push_back(coefficient);
564  vars_in_cut.insert(var);
565  } else {
566  const IntegerValue var_lb = integer_trail->LevelZeroLowerBound(var);
567  constraint_ub_for_cut -= coefficient * var_lb;
568  }
569  }
570  LinearConstraint cut = GenerateKnapsackCutForCover(
571  cut_vars, cut_vars_original_coefficients, constraint_ub_for_cut,
572  *integer_trail);
573 
574  // Check if the constraint has only binary variables.
575  bool is_lifted = false;
576  if (ConstraintIsEligibleForLifting(cut, *integer_trail)) {
577  if (LiftKnapsackCut(mutable_constraint, lp_values,
578  cut_vars_original_coefficients, *integer_trail,
579  model->GetOrCreate<TimeLimit>(), &cut)) {
580  is_lifted = true;
581  }
582  }
583 
584  CHECK(!SolutionSatisfiesConstraint(cut, lp_values));
585  manager->AddCut(cut, is_lifted ? "LiftedKnapsack" : "Knapsack",
586  lp_values);
587  }
588  }
589  if (skipped_constraints > 0) {
590  VLOG(2) << "Skipped constraints: " << skipped_constraints;
591  }
592  };
593 
594  return result;
595 }
596 
597 // Compute the larger t <= max_t such that t * rhs_remainder >= divisor / 2.
598 //
599 // This is just a separate function as it is slightly faster to compute the
600 // result only once.
601 IntegerValue GetFactorT(IntegerValue rhs_remainder, IntegerValue divisor,
602  IntegerValue max_t) {
603  DCHECK_GE(max_t, 1);
604  return rhs_remainder == 0
605  ? max_t
606  : std::min(max_t, CeilRatio(divisor / 2, rhs_remainder));
607 }
608 
609 std::function<IntegerValue(IntegerValue)> GetSuperAdditiveRoundingFunction(
610  IntegerValue rhs_remainder, IntegerValue divisor, IntegerValue t,
611  IntegerValue max_scaling) {
612  DCHECK_GE(max_scaling, 1);
613 
614  // Adjust after the multiplication by t.
615  rhs_remainder *= t;
616  DCHECK_LT(rhs_remainder, divisor);
617 
618  // Make sure we don't have an integer overflow below. Note that we assume that
619  // divisor and the maximum coeff magnitude are not too different (maybe a
620  // factor 1000 at most) so that the final result will never overflow.
621  max_scaling = std::min(max_scaling, kint64max / divisor);
622 
623  const IntegerValue size = divisor - rhs_remainder;
624  if (max_scaling == 1 || size == 1) {
625  // TODO(user): Use everywhere a two step computation to avoid overflow?
626  // First divide by divisor, then multiply by t. For now, we limit t so that
627  // we never have an overflow instead.
628  return [t, divisor](IntegerValue coeff) {
629  return FloorRatio(t * coeff, divisor);
630  };
631  } else if (size <= max_scaling) {
632  return [size, rhs_remainder, t, divisor](IntegerValue coeff) {
633  const IntegerValue ratio = FloorRatio(t * coeff, divisor);
634  const IntegerValue remainder = t * coeff - ratio * divisor;
635  const IntegerValue diff = remainder - rhs_remainder;
636  return size * ratio + std::max(IntegerValue(0), diff);
637  };
638  } else if (max_scaling.value() * rhs_remainder.value() < divisor) {
639  // Because of our max_t limitation, the rhs_remainder might stay small.
640  //
641  // If it is "too small" we cannot use the code below because it will not be
642  // valid. So we just divide divisor into max_scaling bucket. The
643  // rhs_remainder will be in the bucket 0.
644  //
645  // Note(user): This seems the same as just increasing t, modulo integer
646  // overflows. Maybe we should just always do the computation like this so
647  // that we can use larger t even if coeff is close to kint64max.
648  return [t, divisor, max_scaling](IntegerValue coeff) {
649  const IntegerValue ratio = FloorRatio(t * coeff, divisor);
650  const IntegerValue remainder = t * coeff - ratio * divisor;
651  const IntegerValue bucket = FloorRatio(remainder * max_scaling, divisor);
652  return max_scaling * ratio + bucket;
653  };
654  } else {
655  // We divide (size = divisor - rhs_remainder) into (max_scaling - 1) buckets
656  // and increase the function by 1 / max_scaling for each of them.
657  //
658  // Note that for different values of max_scaling, we get a family of
659  // functions that do not dominate each others. So potentially, a max scaling
660  // as low as 2 could lead to the better cut (this is exactly the Letchford &
661  // Lodi function).
662  //
663  // Another intersting fact, is that if we want to compute the maximum alpha
664  // for a constraint with 2 terms like:
665  // divisor * Y + (ratio * divisor + remainder) * X
666  // <= rhs_ratio * divisor + rhs_remainder
667  // so that we have the cut:
668  // Y + (ratio + alpha) * X <= rhs_ratio
669  // This is the same as computing the maximum alpha such that for all integer
670  // X > 0 we have CeilRatio(alpha * divisor * X, divisor)
671  // <= CeilRatio(remainder * X - rhs_remainder, divisor).
672  // We can prove that this alpha is of the form (n - 1) / n, and it will
673  // be reached by such function for a max_scaling of n.
674  //
675  // TODO(user): This function is not always maximal when
676  // size % (max_scaling - 1) == 0. Improve?
677  return [size, rhs_remainder, t, divisor, max_scaling](IntegerValue coeff) {
678  const IntegerValue ratio = FloorRatio(t * coeff, divisor);
679  const IntegerValue remainder = t * coeff - ratio * divisor;
680  const IntegerValue diff = remainder - rhs_remainder;
681  const IntegerValue bucket =
682  diff > 0 ? CeilRatio(diff * (max_scaling - 1), size)
683  : IntegerValue(0);
684  return max_scaling * ratio + bucket;
685  };
686  }
687 }
688 
689 // TODO(user): This has been optimized a bit, but we can probably do even better
690 // as it still takes around 25% percent of the run time when all the cuts are on
691 // for the opm*mps.gz problems and others.
693  RoundingOptions options, const std::vector<double>& lp_values,
694  const std::vector<IntegerValue>& lower_bounds,
695  const std::vector<IntegerValue>& upper_bounds,
696  ImpliedBoundsProcessor* ib_processor, LinearConstraint* cut) {
697  const int size = lp_values.size();
698  if (size == 0) return;
699  CHECK_EQ(lower_bounds.size(), size);
700  CHECK_EQ(upper_bounds.size(), size);
701  CHECK_EQ(cut->vars.size(), size);
702  CHECK_EQ(cut->coeffs.size(), size);
703  CHECK_EQ(cut->lb, kMinIntegerValue);
704 
705  // To optimize the computation of the best divisor below, we only need to
706  // look at the indices with a shifted lp value that is not close to zero.
707  //
708  // TODO(user): sort by decreasing lp_values so that our early abort test in
709  // the critical loop below has more chance of returning early? I tried but it
710  // didn't seems to change much though.
711  relevant_indices_.clear();
712  relevant_lp_values_.clear();
713  relevant_coeffs_.clear();
714  relevant_bound_diffs_.clear();
715  divisors_.clear();
716  adjusted_coeffs_.clear();
717 
718  // Compute the maximum magnitude for non-fixed variables.
719  IntegerValue max_magnitude(0);
720  for (int i = 0; i < size; ++i) {
721  if (lower_bounds[i] == upper_bounds[i]) continue;
722  const IntegerValue magnitude = IntTypeAbs(cut->coeffs[i]);
723  max_magnitude = std::max(max_magnitude, magnitude);
724  }
725 
726  // Shift each variable using its lower/upper bound so that no variable can
727  // change sign. We eventually do a change of variable to its negation so
728  // that all variable are non-negative.
729  bool overflow = false;
730  change_sign_at_postprocessing_.assign(size, false);
731  for (int i = 0; i < size; ++i) {
732  if (cut->coeffs[i] == 0) continue;
733  const IntegerValue magnitude = IntTypeAbs(cut->coeffs[i]);
734 
735  // We might change them below.
736  IntegerValue lb = lower_bounds[i];
737  double lp_value = lp_values[i];
738 
739  const IntegerValue ub = upper_bounds[i];
740  const IntegerValue bound_diff =
741  IntegerValue(CapSub(ub.value(), lb.value()));
742 
743  // Note that since we use ToDouble() this code works fine with lb/ub at
744  // min/max integer value.
745  //
746  // TODO(user): Experiments with different heuristics. Other solver also
747  // seems to try a bunch of possibilities in a "postprocess" phase once
748  // the divisor is chosen. Try that.
749  {
750  // when the magnitude of the entry become smaller and smaller we bias
751  // towards a positive coefficient. This is because after rounding this
752  // will likely become zero instead of -divisor and we need the lp value
753  // to be really close to its bound to compensate.
754  const double lb_dist = std::abs(lp_value - ToDouble(lb));
755  const double ub_dist = std::abs(lp_value - ToDouble(ub));
756  const double bias =
757  std::max(1.0, 0.1 * ToDouble(max_magnitude) / ToDouble(magnitude));
758  if ((bias * lb_dist > ub_dist && cut->coeffs[i] < 0) ||
759  (lb_dist > bias * ub_dist && cut->coeffs[i] > 0)) {
760  change_sign_at_postprocessing_[i] = true;
761  cut->coeffs[i] = -cut->coeffs[i];
762  lp_value = -lp_value;
763  lb = -ub;
764  }
765  }
766 
767  // Always shift to lb.
768  // coeff * X = coeff * (X - shift) + coeff * shift.
769  lp_value -= ToDouble(lb);
770  if (!AddProductTo(-cut->coeffs[i], lb, &cut->ub)) {
771  overflow = true;
772  break;
773  }
774 
775  // Deal with fixed variable, no need to shift back in this case, we can
776  // just remove the term.
777  if (bound_diff == 0) {
778  cut->coeffs[i] = IntegerValue(0);
779  lp_value = 0.0;
780  }
781 
782  if (std::abs(lp_value) > 1e-2) {
783  relevant_coeffs_.push_back(cut->coeffs[i]);
784  relevant_indices_.push_back(i);
785  relevant_lp_values_.push_back(lp_value);
786  relevant_bound_diffs_.push_back(bound_diff);
787  divisors_.push_back(magnitude);
788  }
789  }
790 
791  // TODO(user): Maybe this shouldn't be called on such constraint.
792  if (relevant_coeffs_.empty()) {
793  VLOG(2) << "Issue, nothing to cut.";
794  *cut = LinearConstraint(IntegerValue(0), IntegerValue(0));
795  return;
796  }
797  CHECK_NE(max_magnitude, 0);
798 
799  // Our heuristic will try to generate a few different cuts, and we will keep
800  // the most violated one scaled by the l2 norm of the relevant position.
801  //
802  // TODO(user): Experiment for the best value of this initial violation
803  // threshold. Note also that we use the l2 norm on the restricted position
804  // here. Maybe we should change that? On that note, the L2 norm usage seems a
805  // bit weird to me since it grows with the number of term in the cut. And
806  // often, we already have a good cut, and we make it stronger by adding extra
807  // terms that do not change its activity.
808  //
809  // The discussion above only concern the best_scaled_violation initial value.
810  // The remainder_threshold allows to not consider cuts for which the final
811  // efficacity is clearly lower than 1e-3 (it is a bound, so we could generate
812  // cuts with a lower efficacity than this).
813  double best_scaled_violation = 0.01;
814  const IntegerValue remainder_threshold(max_magnitude / 1000);
815 
816  // The cut->ub might have grown quite a bit with the bound substitution, so
817  // we need to include it too since we will apply the rounding function on it.
818  max_magnitude = std::max(max_magnitude, IntTypeAbs(cut->ub));
819 
820  // Make sure that when we multiply the rhs or the coefficient by a factor t,
821  // we do not have an integer overflow. Actually, we need a bit more room
822  // because we might round down a value to the next multiple of
823  // max_magnitude.
824  const IntegerValue threshold = kMaxIntegerValue / 2;
825  if (overflow || max_magnitude >= threshold) {
826  VLOG(2) << "Issue, overflow.";
827  *cut = LinearConstraint(IntegerValue(0), IntegerValue(0));
828  return;
829  }
830  const IntegerValue max_t = threshold / max_magnitude;
831 
832  // There is no point trying twice the same divisor or a divisor that is too
833  // small. Note that we use a higher threshold than the remainder_threshold
834  // because we can boost the remainder thanks to our adjusting heuristic below
835  // and also because this allows to have cuts with a small range of
836  // coefficients.
837  //
838  // TODO(user): Note that the std::sort() is visible in some cpu profile.
839  {
840  int new_size = 0;
841  const IntegerValue divisor_threshold = max_magnitude / 10;
842  for (int i = 0; i < divisors_.size(); ++i) {
843  if (divisors_[i] <= divisor_threshold) continue;
844  divisors_[new_size++] = divisors_[i];
845  }
846  divisors_.resize(new_size);
847  }
848  gtl::STLSortAndRemoveDuplicates(&divisors_, std::greater<IntegerValue>());
849 
850  // TODO(user): Avoid quadratic algorithm? Note that we are quadratic in
851  // relevant_indices not the full cut->coeffs.size(), but this is still too
852  // much on some problems.
853  IntegerValue best_divisor(0);
854  for (const IntegerValue divisor : divisors_) {
855  // Skip if we don't have the potential to generate a good enough cut.
856  const IntegerValue initial_rhs_remainder =
857  cut->ub - FloorRatio(cut->ub, divisor) * divisor;
858  if (initial_rhs_remainder <= remainder_threshold) continue;
859 
860  IntegerValue temp_ub = cut->ub;
861  adjusted_coeffs_.clear();
862 
863  // We will adjust coefficient that are just under an exact multiple of
864  // divisor to an exact multiple. This is meant to get rid of small errors
865  // that appears due to rounding error in our exact computation of the
866  // initial constraint given to this class.
867  //
868  // Each adjustement will cause the initial_rhs_remainder to increase, and we
869  // do not want to increase it above divisor. Our threshold below guarantees
870  // this. Note that the higher the rhs_remainder becomes, the more the
871  // function f() has a chance to reduce the violation, so it is not always a
872  // good idea to use all the slack we have between initial_rhs_remainder and
873  // divisor.
874  //
875  // TODO(user): If possible, it might be better to complement these
876  // variables. Even if the adjusted lp_values end up larger, if we loose less
877  // when taking f(), then we will have a better violation.
878  const IntegerValue adjust_threshold =
879  (divisor - initial_rhs_remainder - 1) / IntegerValue(size);
880  if (adjust_threshold > 0) {
881  // Even before we finish the adjust, we can have a lower bound on the
882  // activily loss using this divisor, and so we can abort early. This is
883  // similar to what is done below in the function.
884  bool early_abort = false;
885  double loss_lb = 0.0;
886  const double threshold = ToDouble(initial_rhs_remainder);
887 
888  for (int i = 0; i < relevant_coeffs_.size(); ++i) {
889  // Compute the difference of coeff with the next multiple of divisor.
890  const IntegerValue coeff = relevant_coeffs_[i];
891  const IntegerValue remainder =
892  CeilRatio(coeff, divisor) * divisor - coeff;
893 
894  if (divisor - remainder <= initial_rhs_remainder) {
895  // We do not know exactly f() yet, but it will always round to the
896  // floor of the division by divisor in this case.
897  loss_lb += ToDouble(divisor - remainder) * relevant_lp_values_[i];
898  if (loss_lb >= threshold) {
899  early_abort = true;
900  break;
901  }
902  }
903 
904  // Adjust coeff of the form k * divisor - epsilon.
905  const IntegerValue diff = relevant_bound_diffs_[i];
906  if (remainder > 0 && remainder <= adjust_threshold &&
907  CapProd(diff.value(), remainder.value()) <= adjust_threshold) {
908  temp_ub += remainder * diff;
909  adjusted_coeffs_.push_back({i, coeff + remainder});
910  }
911  }
912 
913  if (early_abort) continue;
914  }
915 
916  // Create the super-additive function f().
917  const IntegerValue rhs_remainder =
918  temp_ub - FloorRatio(temp_ub, divisor) * divisor;
919  if (rhs_remainder == 0) continue;
920 
921  const auto f = GetSuperAdditiveRoundingFunction(
922  rhs_remainder, divisor, GetFactorT(rhs_remainder, divisor, max_t),
923  options.max_scaling);
924 
925  // As we round coefficients, we will compute the loss compared to the
926  // current scaled constraint activity. As soon as this loss crosses the
927  // slack, then we known that there is no violation and we can abort early.
928  //
929  // TODO(user): modulo the scaling, we could compute the exact threshold
930  // using our current best cut. Note that we also have to account the change
931  // in slack due to the adjust code above.
932  const double scaling = ToDouble(f(divisor)) / ToDouble(divisor);
933  const double threshold = scaling * ToDouble(rhs_remainder);
934  double loss = 0.0;
935 
936  // Apply f() to the cut and compute the cut violation. Note that it is
937  // okay to just look at the relevant indices since the other have a lp
938  // value which is almost zero. Doing it like this is faster, and even if
939  // the max_magnitude might be off it should still be relevant enough.
940  double violation = -ToDouble(f(temp_ub));
941  double l2_norm = 0.0;
942  bool early_abort = false;
943  int adjusted_coeffs_index = 0;
944  for (int i = 0; i < relevant_coeffs_.size(); ++i) {
945  IntegerValue coeff = relevant_coeffs_[i];
946 
947  // Adjust coeff according to our previous computation if needed.
948  if (adjusted_coeffs_index < adjusted_coeffs_.size() &&
949  adjusted_coeffs_[adjusted_coeffs_index].first == i) {
950  coeff = adjusted_coeffs_[adjusted_coeffs_index].second;
951  adjusted_coeffs_index++;
952  }
953 
954  if (coeff == 0) continue;
955  const IntegerValue new_coeff = f(coeff);
956  const double new_coeff_double = ToDouble(new_coeff);
957  const double lp_value = relevant_lp_values_[i];
958 
959  l2_norm += new_coeff_double * new_coeff_double;
960  violation += new_coeff_double * lp_value;
961  loss += (scaling * ToDouble(coeff) - new_coeff_double) * lp_value;
962  if (loss >= threshold) {
963  early_abort = true;
964  break;
965  }
966  }
967  if (early_abort) continue;
968 
969  // Here we scale by the L2 norm over the "relevant" positions. This seems
970  // to work slighly better in practice.
971  violation /= sqrt(l2_norm);
972  if (violation > best_scaled_violation) {
973  best_scaled_violation = violation;
974  best_divisor = divisor;
975  }
976  }
977 
978  if (best_divisor == 0) {
979  *cut = LinearConstraint(IntegerValue(0), IntegerValue(0));
980  return;
981  }
982 
983  // Adjust coefficients.
984  //
985  // TODO(user): It might make sense to also adjust the one with a small LP
986  // value, but then the cut will be slighlty different than the one we computed
987  // above. Try with and without maybe?
988  const IntegerValue initial_rhs_remainder =
989  cut->ub - FloorRatio(cut->ub, best_divisor) * best_divisor;
990  const IntegerValue adjust_threshold =
991  (best_divisor - initial_rhs_remainder - 1) / IntegerValue(size);
992  if (adjust_threshold > 0) {
993  for (int i = 0; i < relevant_indices_.size(); ++i) {
994  const int index = relevant_indices_[i];
995  const IntegerValue diff = relevant_bound_diffs_[i];
996  if (diff > adjust_threshold) continue;
997 
998  // Adjust coeff of the form k * best_divisor - epsilon.
999  const IntegerValue coeff = cut->coeffs[index];
1000  const IntegerValue remainder =
1001  CeilRatio(coeff, best_divisor) * best_divisor - coeff;
1002  if (CapProd(diff.value(), remainder.value()) <= adjust_threshold) {
1003  cut->ub += remainder * diff;
1004  cut->coeffs[index] += remainder;
1005  }
1006  }
1007  }
1008 
1009  // Create the super-additive function f().
1010  //
1011  // TODO(user): Try out different rounding function and keep the best. We can
1012  // change max_t and max_scaling. It might not be easy to choose which cut is
1013  // the best, but we can at least know for sure if one dominate the other
1014  // completely. That is, if for all coeff f(coeff)/f(divisor) is greater than
1015  // or equal to the same value for another function f.
1016  const IntegerValue rhs_remainder =
1017  cut->ub - FloorRatio(cut->ub, best_divisor) * best_divisor;
1018  IntegerValue factor_t = GetFactorT(rhs_remainder, best_divisor, max_t);
1019  auto f = GetSuperAdditiveRoundingFunction(rhs_remainder, best_divisor,
1020  factor_t, options.max_scaling);
1021 
1022  // Look amongst all our possible function f() for one that dominate greedily
1023  // our current best one. Note that we prefer lower scaling factor since that
1024  // result in a cut with lower coefficients.
1025  remainders_.clear();
1026  for (int i = 0; i < size; ++i) {
1027  const IntegerValue coeff = cut->coeffs[i];
1028  const IntegerValue r =
1029  coeff - FloorRatio(coeff, best_divisor) * best_divisor;
1030  if (r > rhs_remainder) remainders_.push_back(r);
1031  }
1032  gtl::STLSortAndRemoveDuplicates(&remainders_);
1033  if (remainders_.size() <= 100) {
1034  best_rs_.clear();
1035  for (const IntegerValue r : remainders_) {
1036  best_rs_.push_back(f(r));
1037  }
1038  IntegerValue best_d = f(best_divisor);
1039 
1040  // Note that the complexity seems high 100 * 2 * options.max_scaling, but
1041  // this only run on cuts that are already efficient and the inner loop tend
1042  // to abort quickly. I didn't see this code in the cpu profile so far.
1043  for (const IntegerValue t :
1044  {IntegerValue(1), GetFactorT(rhs_remainder, best_divisor, max_t)}) {
1045  for (IntegerValue s(2); s <= options.max_scaling; ++s) {
1046  const auto g =
1047  GetSuperAdditiveRoundingFunction(rhs_remainder, best_divisor, t, s);
1048  int num_strictly_better = 0;
1049  rs_.clear();
1050  const IntegerValue d = g(best_divisor);
1051  for (int i = 0; i < best_rs_.size(); ++i) {
1052  const IntegerValue temp = g(remainders_[i]);
1053  if (temp * best_d < best_rs_[i] * d) break;
1054  if (temp * best_d > best_rs_[i] * d) num_strictly_better++;
1055  rs_.push_back(temp);
1056  }
1057  if (rs_.size() == best_rs_.size() && num_strictly_better > 0) {
1058  f = g;
1059  factor_t = t;
1060  best_rs_ = rs_;
1061  best_d = d;
1062  }
1063  }
1064  }
1065  }
1066 
1067  // Starts to apply f() to the cut. We only apply it to the rhs here, the
1068  // coefficient will be done after the potential lifting of some Booleans.
1069  cut->ub = f(cut->ub);
1070  tmp_terms_.clear();
1071 
1072  // Lift some implied bounds Booleans. Note that we will add them after
1073  // "size" so they will be ignored in the second loop below.
1074  num_lifted_booleans_ = 0;
1075  if (ib_processor != nullptr) {
1076  for (int i = 0; i < size; ++i) {
1077  const IntegerValue coeff = cut->coeffs[i];
1078  if (coeff == 0) continue;
1079 
1080  IntegerVariable var = cut->vars[i];
1081  if (change_sign_at_postprocessing_[i]) {
1082  var = NegationOf(var);
1083  }
1084 
1086  ib_processor->GetCachedImpliedBoundInfo(var);
1087 
1088  // Avoid overflow.
1089  if (CapProd(CapProd(std::abs(coeff.value()), factor_t.value()),
1090  info.bound_diff.value()) == kint64max) {
1091  continue;
1092  }
1093 
1094  // Because X = bound_diff * B + S
1095  // We can replace coeff * X by the expression before applying f:
1096  // = f(coeff * bound_diff) * B + f(coeff) * [X - bound_diff * B]
1097  // = f(coeff) * X + (f(coeff * bound_diff) - f(coeff) * bound_diff] B
1098  // So we can "lift" B into the cut.
1099  const IntegerValue coeff_b =
1100  f(coeff * info.bound_diff) - f(coeff) * info.bound_diff;
1101  CHECK_GE(coeff_b, 0);
1102  if (coeff_b == 0) continue;
1103 
1104  ++num_lifted_booleans_;
1105  if (info.is_positive) {
1106  tmp_terms_.push_back({info.bool_var, coeff_b});
1107  } else {
1108  tmp_terms_.push_back({info.bool_var, -coeff_b});
1109  cut->ub = CapAdd(-coeff_b.value(), cut->ub.value());
1110  }
1111  }
1112  }
1113 
1114  // Apply f() to the cut.
1115  //
1116  // Remove the bound shifts so the constraint is expressed in the original
1117  // variables.
1118  for (int i = 0; i < size; ++i) {
1119  IntegerValue coeff = cut->coeffs[i];
1120  if (coeff == 0) continue;
1121  coeff = f(coeff);
1122  if (coeff == 0) continue;
1123  if (change_sign_at_postprocessing_[i]) {
1124  cut->ub = IntegerValue(
1125  CapAdd((coeff * -upper_bounds[i]).value(), cut->ub.value()));
1126  tmp_terms_.push_back({cut->vars[i], -coeff});
1127  } else {
1128  cut->ub = IntegerValue(
1129  CapAdd((coeff * lower_bounds[i]).value(), cut->ub.value()));
1130  tmp_terms_.push_back({cut->vars[i], coeff});
1131  }
1132  }
1133 
1134  // Basic post-processing.
1135  CleanTermsAndFillConstraint(&tmp_terms_, cut);
1136  RemoveZeroTerms(cut);
1137  DivideByGCD(cut);
1138 }
1139 
1141  IntegerVariable x,
1142  IntegerVariable y,
1143  Model* model) {
1144  CutGenerator result;
1145  result.vars = {z, x, y};
1146 
1147  IntegerTrail* const integer_trail = model->GetOrCreate<IntegerTrail>();
1148  result.generate_cuts =
1149  [z, x, y, integer_trail](
1150  const gtl::ITIVector<IntegerVariable, double>& lp_values,
1151  LinearConstraintManager* manager) {
1152  const int64 x_lb = integer_trail->LevelZeroLowerBound(x).value();
1153  const int64 x_ub = integer_trail->LevelZeroUpperBound(x).value();
1154  const int64 y_lb = integer_trail->LevelZeroLowerBound(y).value();
1155  const int64 y_ub = integer_trail->LevelZeroUpperBound(y).value();
1156 
1157  // TODO(user): Compute a better bound (int_max / 4 ?).
1158  const int64 kMaxSafeInteger = (int64{1} << 53) - 1;
1159 
1160  if (CapProd(x_ub, y_ub) >= kMaxSafeInteger) {
1161  VLOG(3) << "Potential overflow in PositiveMultiplicationCutGenerator";
1162  return;
1163  }
1164 
1165  const double x_lp_value = lp_values[x];
1166  const double y_lp_value = lp_values[y];
1167  const double z_lp_value = lp_values[z];
1168 
1169  // TODO(user): As the bounds change monotonically, these cuts
1170  // dominate any previous one. try to keep a reference to the cut and
1171  // replace it. Alternatively, add an API for a level-zero bound change
1172  // callback.
1173 
1174  // Cut -z + x_coeff * x + y_coeff* y <= rhs
1175  auto try_add_above_cut = [manager, z_lp_value, x_lp_value, y_lp_value,
1176  x, y, z, &lp_values](
1177  int64 x_coeff, int64 y_coeff, int64 rhs) {
1178  if (-z_lp_value + x_lp_value * x_coeff + y_lp_value * y_coeff >=
1179  rhs + kMinCutViolation) {
1180  LinearConstraint cut;
1181  cut.vars.push_back(z);
1182  cut.coeffs.push_back(IntegerValue(-1));
1183  if (x_coeff != 0) {
1184  cut.vars.push_back(x);
1185  cut.coeffs.push_back(IntegerValue(x_coeff));
1186  }
1187  if (y_coeff != 0) {
1188  cut.vars.push_back(y);
1189  cut.coeffs.push_back(IntegerValue(y_coeff));
1190  }
1191  cut.lb = kMinIntegerValue;
1192  cut.ub = IntegerValue(rhs);
1193  manager->AddCut(cut, "PositiveProduct", lp_values);
1194  }
1195  };
1196 
1197  // Cut -z + x_coeff * x + y_coeff* y >= rhs
1198  auto try_add_below_cut = [manager, z_lp_value, x_lp_value, y_lp_value,
1199  x, y, z, &lp_values](
1200  int64 x_coeff, int64 y_coeff, int64 rhs) {
1201  if (-z_lp_value + x_lp_value * x_coeff + y_lp_value * y_coeff <=
1202  rhs - kMinCutViolation) {
1203  LinearConstraint cut;
1204  cut.vars.push_back(z);
1205  cut.coeffs.push_back(IntegerValue(-1));
1206  if (x_coeff != 0) {
1207  cut.vars.push_back(x);
1208  cut.coeffs.push_back(IntegerValue(x_coeff));
1209  }
1210  if (y_coeff != 0) {
1211  cut.vars.push_back(y);
1212  cut.coeffs.push_back(IntegerValue(y_coeff));
1213  }
1214  cut.lb = IntegerValue(rhs);
1215  cut.ub = kMaxIntegerValue;
1216  manager->AddCut(cut, "PositiveProduct", lp_values);
1217  }
1218  };
1219 
1220  // McCormick relaxation of bilinear constraints. These 4 cuts are the
1221  // exact facets of the x * y polyhedron for a bounded x and y.
1222  //
1223  // Each cut correspond to plane that contains two of the line
1224  // (x=x_lb), (x=x_ub), (y=y_lb), (y=y_ub). The easiest to
1225  // understand them is to draw the x*y curves and see the 4
1226  // planes that correspond to the convex hull of the graph.
1227  try_add_above_cut(y_lb, x_lb, x_lb * y_lb);
1228  try_add_above_cut(y_ub, x_ub, x_ub * y_ub);
1229  try_add_below_cut(y_ub, x_lb, x_lb * y_ub);
1230  try_add_below_cut(y_lb, x_ub, x_ub * y_lb);
1231  };
1232 
1233  return result;
1234 }
1235 
1236 CutGenerator CreateSquareCutGenerator(IntegerVariable y, IntegerVariable x,
1237  Model* model) {
1238  CutGenerator result;
1239  result.vars = {y, x};
1240 
1241  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
1242  result.generate_cuts =
1243  [y, x, integer_trail](
1244  const gtl::ITIVector<IntegerVariable, double>& lp_values,
1245  LinearConstraintManager* manager) {
1246  const int64 x_ub = integer_trail->LevelZeroUpperBound(x).value();
1247  const int64 x_lb = integer_trail->LevelZeroLowerBound(x).value();
1248 
1249  if (x_lb == x_ub) return;
1250 
1251  // Check for potential overflows.
1252  if (x_ub > (int64{1} << 31)) return;
1253  DCHECK_GE(x_lb, 0);
1254 
1255  const double y_lp_value = lp_values[y];
1256  const double x_lp_value = lp_values[x];
1257 
1258  // First cut: target should be below the line:
1259  // (x_lb, x_lb ^ 2) to (x_ub, x_ub ^ 2).
1260  // The slope of that line is (ub^2 - lb^2) / (ub - lb) = ub + lb.
1261  const int64 y_lb = x_lb * x_lb;
1262  const int64 above_slope = x_ub + x_lb;
1263  const double max_lp_y = y_lb + above_slope * (x_lp_value - x_lb);
1264  if (y_lp_value >= max_lp_y + kMinCutViolation) {
1265  // cut: y <= (x_lb + x_ub) * x - x_lb * x_ub
1266  LinearConstraint above_cut;
1267  above_cut.vars.push_back(y);
1268  above_cut.coeffs.push_back(IntegerValue(1));
1269  above_cut.vars.push_back(x);
1270  above_cut.coeffs.push_back(IntegerValue(-above_slope));
1271  above_cut.lb = kMinIntegerValue;
1272  above_cut.ub = IntegerValue(-x_lb * x_ub);
1273  manager->AddCut(above_cut, "SquareUpper", lp_values);
1274  }
1275 
1276  // Second cut: target should be above all the lines
1277  // (value, value ^ 2) to (value + 1, (value + 1) ^ 2)
1278  // The slope of that line is 2 * value + 1
1279  //
1280  // Note that we only add one of these cuts. The one for x_lp_value in
1281  // [value, value + 1].
1282  const int64 x_floor = static_cast<int64>(std::floor(x_lp_value));
1283  const int64 below_slope = 2 * x_floor + 1;
1284  const double min_lp_y =
1285  below_slope * x_lp_value - x_floor - x_floor * x_floor;
1286  if (min_lp_y >= y_lp_value + kMinCutViolation) {
1287  // cut: y >= below_slope * (x - x_floor) + x_floor ^ 2
1288  // : y >= below_slope * x - x_floor ^ 2 - x_floor
1289  LinearConstraint below_cut;
1290  below_cut.vars.push_back(y);
1291  below_cut.coeffs.push_back(IntegerValue(1));
1292  below_cut.vars.push_back(x);
1293  below_cut.coeffs.push_back(-IntegerValue(below_slope));
1294  below_cut.lb = IntegerValue(-x_floor - x_floor * x_floor);
1295  below_cut.ub = kMaxIntegerValue;
1296  manager->AddCut(below_cut, "SquareLower", lp_values);
1297  }
1298  };
1299 
1300  return result;
1301 }
1302 
1304  const gtl::ITIVector<IntegerVariable, double>& lp_values,
1305  LinearConstraint* cut) const {
1307  /*substitute_only_inner_variables=*/false, IntegerVariable(0), lp_values,
1308  cut, nullptr, nullptr);
1309 }
1310 
1313  auto it = cache_.find(var);
1314  if (it != cache_.end()) return it->second;
1315  return BestImpliedBoundInfo();
1316 }
1317 
1319 ImpliedBoundsProcessor::ComputeBestImpliedBound(
1320  IntegerVariable var,
1321  const gtl::ITIVector<IntegerVariable, double>& lp_values,
1322  std::vector<LinearConstraint>* implied_bound_cuts) const {
1323  auto it = cache_.find(var);
1324  if (it != cache_.end()) return it->second;
1325  BestImpliedBoundInfo result;
1326 
1327  const IntegerValue lb = integer_trail_->LevelZeroLowerBound(var);
1328  for (const ImpliedBoundEntry& entry :
1329  implied_bounds_->GetImpliedBounds(var)) {
1330  // Only process entries with a Boolean variable currently part of the LP
1331  // we are considering for this cut.
1332  //
1333  // TODO(user): the more we use cuts, the less it make sense to have a
1334  // lot of small independent LPs.
1335  if (!lp_vars_.contains(PositiveVariable(entry.literal_view))) {
1336  continue;
1337  }
1338 
1339  // The equation is X = lb + diff * Bool + Slack where Bool is in [0, 1]
1340  // and slack in [0, ub - lb].
1341  const IntegerValue diff = entry.lower_bound - lb;
1342  CHECK_GE(diff, 0);
1343  const double bool_lp_value = entry.is_positive
1344  ? lp_values[entry.literal_view]
1345  : 1.0 - lp_values[entry.literal_view];
1346  const double slack_lp_value =
1347  lp_values[var] - ToDouble(lb) - bool_lp_value * ToDouble(diff);
1348 
1349  // If the implied bound equation is not respected, we just add it
1350  // to implied_bound_cuts, and skip the entry for now.
1351  if (slack_lp_value < -1e-6) {
1352  if (implied_bound_cuts != nullptr) {
1353  LinearConstraint ib_cut;
1354  std::vector<std::pair<IntegerVariable, IntegerValue>> terms;
1355  ib_cut.lb = kMinIntegerValue; // Not relevant.
1356  ib_cut.ub = IntegerValue(0);
1357  if (entry.is_positive) {
1358  // X >= Indicator * (bound - lb) + lb
1359  terms.push_back({entry.literal_view, diff});
1360  terms.push_back({var, IntegerValue(-1)});
1361  ib_cut.ub = -lb;
1362  } else {
1363  // X >= -Indicator * (bound - lb) + bound
1364  terms.push_back({entry.literal_view, -diff});
1365  terms.push_back({var, IntegerValue(-1)});
1366  ib_cut.ub = -entry.lower_bound;
1367  }
1368  CleanTermsAndFillConstraint(&terms, &ib_cut);
1369  implied_bound_cuts->push_back(std::move(ib_cut));
1370  }
1371  continue;
1372  }
1373 
1374  // We look for tight implied bounds, and amongst the tightest one, we
1375  // prefer larger coefficient in front of the Boolean.
1376  if (slack_lp_value + 1e-4 < result.slack_lp_value ||
1377  (slack_lp_value < result.slack_lp_value + 1e-4 &&
1378  diff > result.bound_diff)) {
1379  result.bool_lp_value = bool_lp_value;
1380  result.slack_lp_value = slack_lp_value;
1381 
1382  result.bound_diff = diff;
1383  result.is_positive = entry.is_positive;
1384  result.bool_var = entry.literal_view;
1385  }
1386  }
1387  cache_[var] = result;
1388  return result;
1389 }
1390 
1392  bool substitute_only_inner_variables, IntegerVariable first_slack,
1393  const gtl::ITIVector<IntegerVariable, double>& lp_values,
1394  LinearConstraint* cut, std::vector<SlackInfo>* slack_infos,
1395  std::vector<LinearConstraint>* implied_bound_cuts) const {
1396  tmp_terms_.clear();
1397  IntegerValue new_ub = cut->ub;
1398  bool changed = false;
1399 
1400  // TODO(user): we could relax a bit this test.
1401  int64 overflow_detection = 0;
1402 
1403  const int size = cut->vars.size();
1404  for (int i = 0; i < size; ++i) {
1405  IntegerVariable var = cut->vars[i];
1406  IntegerValue coeff = cut->coeffs[i];
1407 
1408  // Starts by positive coefficient.
1409  // TODO(user): Not clear this is best.
1410  if (coeff < 0) {
1411  coeff = -coeff;
1412  var = NegationOf(var);
1413  }
1414 
1415  // Find the best implied bound to use.
1416  // TODO(user): We could also use implied upper bound, that is try with
1417  // NegationOf(var).
1418  const BestImpliedBoundInfo info =
1419  ComputeBestImpliedBound(var, lp_values, implied_bound_cuts);
1420  {
1421  // This make sure the implied bound for NegationOf(var) is "cached" so
1422  // that GetCachedImpliedBoundInfo() will work. It will also add any
1423  // relevant implied bound cut.
1424  //
1425  // TODO(user): this is a bit hacky. Find a cleaner way.
1426  ComputeBestImpliedBound(NegationOf(var), lp_values, implied_bound_cuts);
1427  }
1428 
1429  const int old_size = tmp_terms_.size();
1430 
1431  // Shall we keep the original term ?
1432  bool keep_term = false;
1433  if (info.bool_var == kNoIntegerVariable) keep_term = true;
1434  if (CapProd(std::abs(coeff.value()), info.bound_diff.value()) ==
1435  kint64max) {
1436  keep_term = true;
1437  }
1438 
1439  // TODO(user): On some problem, not replacing the variable at their bound
1440  // by an implied bounds seems beneficial. This is especially the case on
1441  // g200x740.mps.gz
1442  //
1443  // Note that in ComputeCut() the variable with an LP value at the bound do
1444  // not contribute to the cut efficacity (no loss) but do contribute to the
1445  // various heuristic based on the coefficient magnitude.
1446  if (substitute_only_inner_variables) {
1447  const IntegerValue lb = integer_trail_->LevelZeroLowerBound(var);
1448  const IntegerValue ub = integer_trail_->LevelZeroUpperBound(var);
1449  if (lp_values[var] - ToDouble(lb) < 1e-2) keep_term = true;
1450  if (ToDouble(ub) - lp_values[var] < 1e-2) keep_term = true;
1451  }
1452 
1453  // This is when we do not add slack.
1454  if (slack_infos == nullptr) {
1455  // We do not want to loose anything, so we only replace if the slack lp is
1456  // zero.
1457  if (info.slack_lp_value > 1e-6) keep_term = true;
1458  }
1459 
1460  if (keep_term) {
1461  tmp_terms_.push_back({var, coeff});
1462  } else {
1463  // Substitute.
1464  const IntegerValue lb = integer_trail_->LevelZeroLowerBound(var);
1465  const IntegerValue ub = integer_trail_->LevelZeroUpperBound(var);
1466 
1467  SlackInfo slack_info;
1468  slack_info.lp_value = info.slack_lp_value;
1469  slack_info.lb = 0;
1470  slack_info.ub = ub - lb;
1471 
1472  if (info.is_positive) {
1473  // X = Indicator * diff + lb + Slack
1474  tmp_terms_.push_back({info.bool_var, coeff * info.bound_diff});
1475  if (!AddProductTo(-coeff, lb, &new_ub)) {
1476  VLOG(2) << "Overflow";
1477  return;
1478  }
1479  if (slack_infos != nullptr) {
1480  tmp_terms_.push_back({first_slack, coeff});
1481  first_slack += 2;
1482 
1483  // slack = X - Indicator * info.bound_diff - lb;
1484  slack_info.terms.push_back({var, IntegerValue(1)});
1485  slack_info.terms.push_back({info.bool_var, -info.bound_diff});
1486  slack_info.offset = -lb;
1487  slack_infos->push_back(slack_info);
1488  }
1489  } else {
1490  // X = (1 - Indicator) * (diff) + lb + Slack
1491  // X = -Indicator * (diff) + lb + diff + Slack
1492  tmp_terms_.push_back({info.bool_var, -coeff * info.bound_diff});
1493  if (!AddProductTo(-coeff, lb + info.bound_diff, &new_ub)) {
1494  VLOG(2) << "Overflow";
1495  return;
1496  }
1497  if (slack_infos != nullptr) {
1498  tmp_terms_.push_back({first_slack, coeff});
1499  first_slack += 2;
1500 
1501  // slack = X + Indicator * info.bound_diff - lb - diff;
1502  slack_info.terms.push_back({var, IntegerValue(1)});
1503  slack_info.terms.push_back({info.bool_var, +info.bound_diff});
1504  slack_info.offset = -lb - info.bound_diff;
1505  slack_infos->push_back(slack_info);
1506  }
1507  }
1508  changed = true;
1509  }
1510 
1511  // Add all the new terms coefficient to the overflow detection to avoid
1512  // issue when merging terms refering to the same variable.
1513  for (int i = old_size; i < tmp_terms_.size(); ++i) {
1514  overflow_detection =
1515  CapAdd(overflow_detection, std::abs(tmp_terms_[i].second.value()));
1516  }
1517  }
1518 
1519  if (overflow_detection >= kMaxIntegerValue) {
1520  VLOG(2) << "Overflow";
1521  return;
1522  }
1523  if (!changed) return;
1524 
1525  // Update the cut.
1526  //
1527  // Note that because of our overflow_detection variable, there should be
1528  // no integer overflow when we merge identical terms.
1529  cut->lb = kMinIntegerValue; // Not relevant.
1530  cut->ub = new_ub;
1531  CleanTermsAndFillConstraint(&tmp_terms_, cut);
1532 }
1533 
1534 bool ImpliedBoundsProcessor::DebugSlack(IntegerVariable first_slack,
1535  const LinearConstraint& initial_cut,
1536  const LinearConstraint& cut,
1537  const std::vector<SlackInfo>& info) {
1538  tmp_terms_.clear();
1539  IntegerValue new_ub = cut.ub;
1540  for (int i = 0; i < cut.vars.size(); ++i) {
1541  // Simple copy for non-slack variables.
1542  if (cut.vars[i] < first_slack) {
1543  tmp_terms_.push_back({cut.vars[i], cut.coeffs[i]});
1544  continue;
1545  }
1546 
1547  // Replace slack by its definition.
1548  const IntegerValue multiplier = cut.coeffs[i];
1549  const int index = (cut.vars[i].value() - first_slack.value()) / 2;
1550  for (const std::pair<IntegerVariable, IntegerValue>& term :
1551  info[index].terms) {
1552  tmp_terms_.push_back({term.first, term.second * multiplier});
1553  }
1554  new_ub -= multiplier * info[index].offset;
1555  }
1556 
1557  LinearConstraint tmp_cut;
1558  tmp_cut.lb = kMinIntegerValue; // Not relevant.
1559  tmp_cut.ub = new_ub;
1560  CleanTermsAndFillConstraint(&tmp_terms_, &tmp_cut);
1561  MakeAllVariablesPositive(&tmp_cut);
1562 
1563  // We need to canonicalize the initial_cut too for comparison. Note that we
1564  // only use this for debug, so we don't care too much about the memory and
1565  // extra time.
1566  // TODO(user): Expose CanonicalizeConstraint() from the manager.
1567  LinearConstraint tmp_copy;
1568  tmp_terms_.clear();
1569  for (int i = 0; i < initial_cut.vars.size(); ++i) {
1570  tmp_terms_.push_back({initial_cut.vars[i], initial_cut.coeffs[i]});
1571  }
1572  tmp_copy.lb = kMinIntegerValue; // Not relevant.
1573  tmp_copy.ub = new_ub;
1574  CleanTermsAndFillConstraint(&tmp_terms_, &tmp_copy);
1575  MakeAllVariablesPositive(&tmp_copy);
1576 
1577  if (tmp_cut == tmp_copy) return true;
1578 
1579  LOG(INFO) << first_slack;
1580  LOG(INFO) << tmp_copy.DebugString();
1581  LOG(INFO) << cut.DebugString();
1582  LOG(INFO) << tmp_cut.DebugString();
1583  return false;
1584 }
1585 
1586 namespace {
1587 
1588 void TryToGenerateAllDiffCut(
1589  const std::vector<std::pair<double, IntegerVariable>>& sorted_vars_lp,
1590  const IntegerTrail& integer_trail,
1591  const gtl::ITIVector<IntegerVariable, double>& lp_values,
1592  LinearConstraintManager* manager) {
1593  Domain current_union;
1594  std::vector<IntegerVariable> current_set_vars;
1595  double sum = 0.0;
1596  for (auto value_var : sorted_vars_lp) {
1597  sum += value_var.first;
1598  const IntegerVariable var = value_var.second;
1599  Domain var_domain = integer_trail.InitialVariableDomain(var);
1600  // TODO(user): The union of the domain of the variable being considered
1601  // does not give the tightest bounds, try to get better bounds.
1602  current_union = current_union.UnionWith(var_domain);
1603  current_set_vars.push_back(var);
1604  const int64 required_min_sum =
1605  SumOfKMinValueInDomain(current_union, current_set_vars.size());
1606  const int64 required_max_sum =
1607  SumOfKMaxValueInDomain(current_union, current_set_vars.size());
1608  if (sum < required_min_sum || sum > required_max_sum) {
1609  LinearConstraint cut;
1610  for (IntegerVariable var : current_set_vars) {
1611  cut.AddTerm(var, IntegerValue(1));
1612  }
1613  cut.lb = IntegerValue(required_min_sum);
1614  cut.ub = IntegerValue(required_max_sum);
1615  manager->AddCut(cut, "all_diff", lp_values);
1616  // NOTE: We can extend the current set but it is more helpful to generate
1617  // the cut on a different set of variables so we reset the counters.
1618  sum = 0.0;
1619  current_set_vars.clear();
1620  current_union = Domain();
1621  }
1622  }
1623 }
1624 
1625 } // namespace
1626 
1628  const std::vector<IntegerVariable>& vars, Model* model) {
1629  CutGenerator result;
1630  result.vars = vars;
1631  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
1632  Trail* trail = model->GetOrCreate<Trail>();
1633  result.generate_cuts =
1634  [vars, integer_trail, trail](
1635  const gtl::ITIVector<IntegerVariable, double>& lp_values,
1636  LinearConstraintManager* manager) {
1637  // These cuts work at all levels but the generator adds too many cuts on
1638  // some instances and degrade the performance so we only use it at level
1639  // 0.
1640  if (trail->CurrentDecisionLevel() > 0) return;
1641  std::vector<std::pair<double, IntegerVariable>> sorted_vars;
1642  for (const IntegerVariable var : vars) {
1643  if (integer_trail->LevelZeroLowerBound(var) ==
1644  integer_trail->LevelZeroUpperBound(var)) {
1645  continue;
1646  }
1647  sorted_vars.push_back(std::make_pair(lp_values[var], var));
1648  }
1649  std::sort(sorted_vars.begin(), sorted_vars.end());
1650  TryToGenerateAllDiffCut(sorted_vars, *integer_trail, lp_values,
1651  manager);
1652  // Other direction.
1653  std::reverse(sorted_vars.begin(), sorted_vars.end());
1654  TryToGenerateAllDiffCut(sorted_vars, *integer_trail, lp_values,
1655  manager);
1656  };
1657  VLOG(1) << "Created all_diff cut generator of size: " << vars.size();
1658  return result;
1659 }
1660 
1661 namespace {
1662 // Returns max((w2i - w1i)*Li, (w2i - w1i)*Ui).
1663 IntegerValue MaxCornerDifference(const IntegerVariable var,
1664  const IntegerValue w1_i,
1665  const IntegerValue w2_i,
1666  const IntegerTrail& integer_trail) {
1667  const IntegerValue lb = integer_trail.LevelZeroLowerBound(var);
1668  const IntegerValue ub = integer_trail.LevelZeroUpperBound(var);
1669  return std::max((w2_i - w1_i) * lb, (w2_i - w1_i) * ub);
1670 }
1671 
1672 // This is the coefficient of zk in the cut, where k = max_index.
1673 // MPlusCoefficient_ki = max((wki - wI(i)i) * Li,
1674 // (wki - wI(i)i) * Ui)
1675 // = max corner difference for variable i,
1676 // target expr I(i), max expr k.
1677 // The coefficient of zk is Sum(i=1..n)(MPlusCoefficient_ki) + bk
1678 IntegerValue MPlusCoefficient(
1679  const std::vector<IntegerVariable>& x_vars,
1680  const std::vector<LinearExpression>& exprs,
1681  const gtl::ITIVector<IntegerVariable, int>& variable_partition,
1682  const int max_index, const IntegerTrail& integer_trail) {
1683  IntegerValue coeff = exprs[max_index].offset;
1684  // TODO(user): This algo is quadratic since GetCoefficientOfPositiveVar()
1685  // is linear. This can be optimized (better complexity) if needed.
1686  for (const IntegerVariable var : x_vars) {
1687  const int target_index = variable_partition[var];
1688  if (max_index != target_index) {
1689  coeff += MaxCornerDifference(
1690  var, GetCoefficientOfPositiveVar(var, exprs[target_index]),
1691  GetCoefficientOfPositiveVar(var, exprs[max_index]), integer_trail);
1692  }
1693  }
1694  return coeff;
1695 }
1696 
1697 // Compute the value of
1698 // rhs = wI(i)i * xi + Sum(k=1..d)(MPlusCoefficient_ki * zk)
1699 // for variable xi for given target index I(i).
1700 double ComputeContribution(
1701  const IntegerVariable xi_var, const std::vector<IntegerVariable>& z_vars,
1702  const std::vector<LinearExpression>& exprs,
1703  const gtl::ITIVector<IntegerVariable, double>& lp_values,
1704  const IntegerTrail& integer_trail, const int target_index) {
1705  CHECK_GE(target_index, 0);
1706  CHECK_LT(target_index, exprs.size());
1707  const LinearExpression& target_expr = exprs[target_index];
1708  const double xi_value = lp_values[xi_var];
1709  const IntegerValue wt_i = GetCoefficientOfPositiveVar(xi_var, target_expr);
1710  double contrib = wt_i.value() * xi_value;
1711  for (int expr_index = 0; expr_index < exprs.size(); ++expr_index) {
1712  if (expr_index == target_index) continue;
1713  const LinearExpression& max_expr = exprs[expr_index];
1714  const double z_max_value = lp_values[z_vars[expr_index]];
1715  const IntegerValue corner_value = MaxCornerDifference(
1716  xi_var, wt_i, GetCoefficientOfPositiveVar(xi_var, max_expr),
1717  integer_trail);
1718  contrib += corner_value.value() * z_max_value;
1719  }
1720  return contrib;
1721 }
1722 } // namespace
1723 
1725  const IntegerVariable target, const std::vector<LinearExpression>& exprs,
1726  const std::vector<IntegerVariable>& z_vars, Model* model) {
1727  CutGenerator result;
1728  std::vector<IntegerVariable> x_vars;
1729  result.vars = {target};
1730  const int num_exprs = exprs.size();
1731  for (int i = 0; i < num_exprs; ++i) {
1732  result.vars.push_back(z_vars[i]);
1733  x_vars.insert(x_vars.end(), exprs[i].vars.begin(), exprs[i].vars.end());
1734  }
1736  // All expressions should only contain positive variables.
1737  DCHECK(std::all_of(x_vars.begin(), x_vars.end(), [](IntegerVariable var) {
1738  return VariableIsPositive(var);
1739  }));
1740  result.vars.insert(result.vars.end(), x_vars.begin(), x_vars.end());
1741 
1742  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
1743  result.generate_cuts =
1744  [x_vars, z_vars, target, num_exprs, exprs, integer_trail, model](
1745  const gtl::ITIVector<IntegerVariable, double>& lp_values,
1746  LinearConstraintManager* manager) {
1747  gtl::ITIVector<IntegerVariable, int> variable_partition(
1748  lp_values.size(), -1);
1749  gtl::ITIVector<IntegerVariable, double> variable_partition_contrib(
1750  lp_values.size(), std::numeric_limits<double>::infinity());
1751  for (int expr_index = 0; expr_index < num_exprs; ++expr_index) {
1752  for (const IntegerVariable var : x_vars) {
1753  const double contribution = ComputeContribution(
1754  var, z_vars, exprs, lp_values, *integer_trail, expr_index);
1755  const double prev_contribution = variable_partition_contrib[var];
1756  if (contribution < prev_contribution) {
1757  variable_partition[var] = expr_index;
1758  variable_partition_contrib[var] = contribution;
1759  }
1760  }
1761  }
1762 
1763  LinearConstraintBuilder cut(model, /*lb=*/IntegerValue(0),
1764  /*ub=*/kMaxIntegerValue);
1765  double violation = lp_values[target];
1766  cut.AddTerm(target, IntegerValue(-1));
1767 
1768  for (const IntegerVariable xi_var : x_vars) {
1769  const int input_index = variable_partition[xi_var];
1770  const LinearExpression& expr = exprs[input_index];
1771  const IntegerValue coeff = GetCoefficientOfPositiveVar(xi_var, expr);
1772  if (coeff != IntegerValue(0)) {
1773  cut.AddTerm(xi_var, coeff);
1774  }
1775  violation -= coeff.value() * lp_values[xi_var];
1776  }
1777  for (int expr_index = 0; expr_index < num_exprs; ++expr_index) {
1778  const IntegerVariable z_var = z_vars[expr_index];
1779  const IntegerValue z_coeff = MPlusCoefficient(
1780  x_vars, exprs, variable_partition, expr_index, *integer_trail);
1781  if (z_coeff != IntegerValue(0)) {
1782  cut.AddTerm(z_var, z_coeff);
1783  }
1784  violation -= z_coeff.value() * lp_values[z_var];
1785  }
1786  if (violation > 1e-2) {
1787  manager->AddCut(cut.Build(), "LinMax", lp_values);
1788  }
1789  };
1790  return result;
1791 }
1792 
1794  IntegerVariable size,
1795  IntegerVariable end,
1796  Literal presence,
1797  Model* model) {
1798  CutGenerator result;
1799 
1800  result.vars.push_back(start);
1801  result.vars.push_back(size);
1802  result.vars.push_back(end);
1803 
1804  Trail* trail = model->GetOrCreate<Trail>();
1805  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
1806  result.generate_cuts =
1807  [start, size, end, presence, trail, integer_trail, model](
1808  const gtl::ITIVector<IntegerVariable, double>& lp_values,
1809  LinearConstraintManager* manager) {
1810  if (model->GetOrCreate<Trail>()->CurrentDecisionLevel() > 0) return;
1811  if (trail->Assignment().LiteralIsAssigned(presence) &&
1812  trail->Assignment().LiteralIsTrue(presence)) {
1813  LinearConstraintBuilder cut(model, IntegerValue(0), IntegerValue(0));
1814  cut.AddTerm(start, IntegerValue(1));
1815  cut.AddTerm(size, IntegerValue(1));
1816  cut.AddTerm(end, IntegerValue(-1));
1817  manager->AddCut(cut.Build(), "Interval", lp_values);
1818  }
1819  };
1820  return result;
1821 }
1822 
1824  const std::vector<IntervalVariable>& intervals,
1825  const IntegerVariable capacity, const std::vector<IntegerVariable>& demands,
1826  Model* model) {
1827  CutGenerator result;
1828 
1829  result.vars = demands;
1830  IntervalsRepository* intervals_repo =
1831  model->GetOrCreate<IntervalsRepository>();
1832  for (const IntervalVariable interval : intervals) {
1833  result.vars.push_back(intervals_repo->StartVar(interval));
1834  result.vars.push_back(intervals_repo->EndVar(interval));
1835  result.vars.push_back(intervals_repo->SizeVar(interval));
1836  }
1837 
1838  struct Event {
1839  int interval_index;
1840  IntegerValue time;
1841  bool positive;
1842  IntegerVariable demand;
1843  Literal presence_literal;
1844  };
1845 
1846  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
1847  result.generate_cuts =
1848  [intervals, capacity, demands, integer_trail, intervals_repo, model](
1849  const gtl::ITIVector<IntegerVariable, double>& lp_values,
1850  LinearConstraintManager* manager) {
1851  if (model->GetOrCreate<Trail>()->CurrentDecisionLevel() > 0) return;
1852 
1853  std::vector<Event> events;
1854  // Iterate through the intervals. If start_max < end_min, the demand is
1855  // mandatory.
1856  for (int i = 0; i < intervals.size(); ++i) {
1857  const IntervalVariable interval = intervals[i];
1858  const IntegerVariable start_var = intervals_repo->StartVar(interval);
1859  const IntegerVariable end_var = intervals_repo->EndVar(interval);
1860 
1861  const IntegerValue start_max =
1862  integer_trail->LevelZeroUpperBound(start_var);
1863  const IntegerValue end_min =
1864  integer_trail->LevelZeroLowerBound(end_var);
1865 
1866  if (start_max >= end_min) continue;
1867 
1868  Event e1;
1869  e1.interval_index = i;
1870  e1.time = start_max;
1871  e1.demand = demands[i];
1872  e1.positive = true;
1873  e1.presence_literal = intervals_repo->IsOptional(interval)
1874  ? intervals_repo->IsPresentLiteral(interval)
1876  Event e2 = e1;
1877  e2.time = end_min;
1878  e2.positive = false;
1879  events.push_back(e1);
1880  events.push_back(e2);
1881  }
1882 
1883  // Sort events by time.
1884  // It is also important that all positive event with the same time as
1885  // negative events appear after for the correctness of the algo below.
1886  std::sort(events.begin(), events.end(),
1887  [](const Event i, const Event j) {
1888  if (i.time == j.time) {
1889  if (i.positive == j.positive) {
1890  return i.interval_index < j.interval_index;
1891  }
1892  return !i.positive;
1893  }
1894  return i.time < j.time;
1895  });
1896 
1897  std::vector<Event> cut_events;
1898  bool added_positive_event = false;
1899  for (const Event& e : events) {
1900  if (e.positive) {
1901  added_positive_event = true;
1902  cut_events.push_back(e);
1903  continue;
1904  }
1905  if (added_positive_event && cut_events.size() > 1) {
1906  // Create cut.
1907  bool cut_generated = true;
1909  IntegerValue(0));
1910  cut.AddTerm(capacity, IntegerValue(-1));
1911  for (const Event& cut_event : cut_events) {
1912  if (cut_event.presence_literal == Literal(kTrueLiteralIndex)) {
1913  cut.AddTerm(cut_event.demand, IntegerValue(1));
1914  } else {
1915  cut_generated &= cut.AddLiteralTerm(
1916  cut_event.presence_literal,
1917  integer_trail->LevelZeroLowerBound(cut_event.demand));
1918  if (!cut_generated) break;
1919  }
1920  }
1921  if (cut_generated) {
1922  // Violation of the cut is checked by AddCut so we don't check
1923  // it here.
1924  manager->AddCut(cut.Build(), "Cumulative", lp_values);
1925  }
1926  }
1927  // Remove the event.
1928  int new_size = 0;
1929  for (int i = 0; i < cut_events.size(); ++i) {
1930  if (cut_events[i].interval_index == e.interval_index) {
1931  continue;
1932  }
1933  cut_events[new_size] = cut_events[i];
1934  new_size++;
1935  }
1936  cut_events.resize(new_size);
1937  added_positive_event = false;
1938  }
1939  };
1940  return result;
1941 }
1942 
1943 } // namespace sat
1944 } // namespace operations_research
operations_research::sat::Trail
Definition: sat_base.h:233
operations_research::sat::ImpliedBoundsProcessor::ClearCache
void ClearCache() const
Definition: cuts.h:106
var
IntVar * var
Definition: expr_array.cc:1858
operations_research::sat::IntervalsRepository::EndVar
IntegerVariable EndVar(IntervalVariable i) const
Definition: intervals.h:79
operations_research::sat::CutGenerator::vars
std::vector< IntegerVariable > vars
Definition: cuts.h:41
min
int64 min
Definition: alldiff_cst.cc:138
operations_research::sat::LinearConstraint::ub
IntegerValue ub
Definition: linear_constraint.h:41
integral_types.h
operations_research::sat::KnapsackItem::profit
double profit
Definition: cuts.h:264
operations_research::KnapsackSolverForCuts::Solve
double Solve(TimeLimit *time_limit, bool *is_solution_optimal)
Definition: knapsack_solver_for_cuts.cc:320
operations_research::CapSub
int64 CapSub(int64 x, int64 y)
Definition: saturated_arithmetic.h:154
operations_research::sat::kNoIntegerVariable
const IntegerVariable kNoIntegerVariable(-1)
operations_research::sat::CleanTermsAndFillConstraint
void CleanTermsAndFillConstraint(std::vector< std::pair< IntegerVariable, IntegerValue >> *terms, LinearConstraint *constraint)
Definition: linear_constraint.cc:77
operations_research::sat::CreateAllDifferentCutGenerator
CutGenerator CreateAllDifferentCutGenerator(const std::vector< IntegerVariable > &vars, Model *model)
Definition: cuts.cc:1627
operations_research::sat::FloorRatio
IntegerValue FloorRatio(IntegerValue dividend, IntegerValue positive_divisor)
Definition: integer.h:101
max
int64 max
Definition: alldiff_cst.cc:139
operations_research::sat::CanBeFilteredUsingCutLowerBound
bool CanBeFilteredUsingCutLowerBound(const LinearConstraint &preprocessed_constraint, const gtl::ITIVector< IntegerVariable, double > &lp_values, const IntegerTrail &integer_trail)
Definition: cuts.cc:275
operations_research::sat::ImpliedBoundsProcessor::SlackInfo
Definition: cuts.h:76
time_limit.h
operations_research::sat::GetFactorT
IntegerValue GetFactorT(IntegerValue rhs_remainder, IntegerValue divisor, IntegerValue max_t)
Definition: cuts.cc:601
operations_research::sat::RoundingOptions
Definition: cuts.h:198
operations_research::sat::ImpliedBounds
Definition: implied_bounds.h:77
operations_research::CapProd
int64 CapProd(int64 x, int64 y)
Definition: saturated_arithmetic.h:231
operations_research::sat::CeilRatio
IntegerValue CeilRatio(IntegerValue dividend, IntegerValue positive_divisor)
Definition: integer.h:92
operations_research::sat::LinearConstraint::DebugString
std::string DebugString() const
Definition: linear_constraint.h:53
operations_research::sat::ImpliedBoundsProcessor::BestImpliedBoundInfo::slack_lp_value
double slack_lp_value
Definition: cuts.h:110
linear_constraint.h
operations_research::sat::IntegerTrail::LevelZeroLowerBound
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Definition: integer.h:1267
operations_research::Domain::UnionWith
Domain UnionWith(const Domain &domain) const
Returns the union of D and domain.
Definition: sorted_interval_list.cc:321
operations_research::sat::IntegerTrail::LevelZeroUpperBound
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
Definition: integer.h:1272
value
int64 value
Definition: demon_profiler.cc:43
operations_research::sat::IntervalsRepository::SizeVar
IntegerVariable SizeVar(IntervalVariable i) const
Definition: intervals.h:77
operations_research::sat::LinearExpression
Definition: linear_constraint.h:165
weight
int64 weight
Definition: pack.cc:509
operations_research::sat::KnapsackItem
Definition: cuts.h:263
operations_research::sat::LinearConstraintManager::AddCut
bool AddCut(LinearConstraint ct, std::string type_name, const gtl::ITIVector< IntegerVariable, double > &lp_solution, std::string extra_info="")
Definition: linear_constraint_manager.cc:204
operations_research::sat::LinearConstraint::lb
IntegerValue lb
Definition: linear_constraint.h:40
operations_research
The vehicle routing library lets one model and solve generic vehicle routing problems ranging from th...
Definition: dense_doubly_linked_list.h:21
operations_research::sat::ImpliedBoundsProcessor::BestImpliedBoundInfo::bool_var
IntegerVariable bool_var
Definition: cuts.h:113
operations_research::sat::NegationOf
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Definition: integer.cc:42
operations_research::sat::ImpliedBoundsProcessor
Definition: cuts.h:54
operations_research::sat::IntegerTrail
Definition: integer.h:534
operations_research::sat::kTrueLiteralIndex
const LiteralIndex kTrueLiteralIndex(-2)
operations_research::sat::ImpliedBoundsProcessor::SlackInfo::lb
IntegerValue lb
Definition: cuts.h:82
operations_research::sat::IntervalsRepository::StartVar
IntegerVariable StartVar(IntervalVariable i) const
Definition: intervals.h:78
operations_research::sat::PositiveVariable
IntegerVariable PositiveVariable(IntegerVariable i)
Definition: integer.h:145
int64
int64_t int64
Definition: integral_types.h:34
operations_research::sat::ConvertToKnapsackForm
void ConvertToKnapsackForm(const LinearConstraint &constraint, std::vector< LinearConstraint > *knapsack_constraints, IntegerTrail *integer_trail)
Definition: cuts.cc:373
operations_research::Domain
We call domain any subset of Int64 = [kint64min, kint64max].
Definition: sorted_interval_list.h:81
operations_research::sat::LiftKnapsackCut
bool LiftKnapsackCut(const LinearConstraint &constraint, const gtl::ITIVector< IntegerVariable, double > &lp_values, const std::vector< IntegerValue > &cut_vars_original_coefficients, const IntegerTrail &integer_trail, TimeLimit *time_limit, LinearConstraint *cut)
Definition: cuts.cc:157
operations_research::sat::Model
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:38
index
int index
Definition: pack.cc:508
sat_base.h
operations_research::sat::CreateSquareCutGenerator
CutGenerator CreateSquareCutGenerator(IntegerVariable y, IntegerVariable x, Model *model)
Definition: cuts.cc:1236
operations_research::SumOfKMinValueInDomain
int64 SumOfKMinValueInDomain(const Domain &domain, int k)
Definition: sorted_interval_list.cc:535
operations_research::sat::ImpliedBounds::GetImpliedBounds
const std::vector< ImpliedBoundEntry > & GetImpliedBounds(IntegerVariable var)
Definition: implied_bounds.cc:133
operations_research::sat::Trail::CurrentDecisionLevel
int CurrentDecisionLevel() const
Definition: sat_base.h:355
operations_research::sat::IntegerTrail::InitialVariableDomain
const Domain & InitialVariableDomain(IntegerVariable var) const
Definition: integer.cc:632
operations_research::sat::KnapsackItem::weight
double weight
Definition: cuts.h:265
ratio
Fractional ratio
Definition: revised_simplex.cc:1793
operations_research::sat::Literal
Definition: sat_base.h:64
demand
int64 demand
Definition: resource.cc:123
operations_research::sat::ImpliedBoundsProcessor::SlackInfo::terms
std::vector< std::pair< IntegerVariable, IntegerValue > > terms
Definition: cuts.h:78
operations_research::sat::LinearConstraint
Definition: linear_constraint.h:39
operations_research::sat::LinearConstraintBuilder::AddTerm
void AddTerm(IntegerVariable var, IntegerValue coeff)
Definition: linear_constraint.cc:22
operations_research::sat::ImpliedBoundsProcessor::DebugSlack
bool DebugSlack(IntegerVariable first_slack, const LinearConstraint &initial_cut, const LinearConstraint &cut, const std::vector< SlackInfo > &info)
Definition: cuts.cc:1534
operations_research::sat::GetPreprocessedLinearConstraint
LinearConstraint GetPreprocessedLinearConstraint(const LinearConstraint &constraint, const gtl::ITIVector< IntegerVariable, double > &lp_values, const IntegerTrail &integer_trail)
Definition: cuts.cc:235
operations_research::sat::IntTypeAbs
IntType IntTypeAbs(IntType t)
Definition: integer.h:77
time_limit
SharedTimeLimit * time_limit
Definition: cp_model_solver.cc:2025
operations_research::sat::ImpliedBoundsProcessor::SlackInfo::ub
IntegerValue ub
Definition: cuts.h:83
operations_research::CapAdd
int64 CapAdd(int64 x, int64 y)
Definition: saturated_arithmetic.h:124
intervals.h
operations_research::TimeLimit
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:105
operations_research::sat::RoundingOptions::max_scaling
IntegerValue max_scaling
Definition: cuts.h:199
gtl::STLSortAndRemoveDuplicates
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition: stl_util.h:58
operations_research::sat::kMaxIntegerValue
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
operations_research::sat::ImpliedBoundsProcessor::SlackInfo::offset
IntegerValue offset
Definition: cuts.h:79
operations_research::TimeLimit::LimitReached
bool LimitReached()
Returns true when the external limit is true, or the deterministic time is over the deterministic lim...
Definition: time_limit.h:532
operations_research::KnapsackSolverForCuts::set_solution_upper_bound_threshold
void set_solution_upper_bound_threshold(const double solution_upper_bound_threshold)
Definition: knapsack_solver_for_cuts.h:330
operations_research::sat::CreateLinMaxCutGenerator
CutGenerator CreateLinMaxCutGenerator(const IntegerVariable target, const std::vector< LinearExpression > &exprs, const std::vector< IntegerVariable > &z_vars, Model *model)
Definition: cuts.cc:1724
operations_research::sat::AddProductTo
bool AddProductTo(IntegerValue a, IntegerValue b, IntegerValue *result)
Definition: integer.h:121
knapsack_solver_for_cuts.h
start_max
Rev< int64 > start_max
Definition: sched_constraints.cc:242
operations_research::sat::LinearConstraint::AddTerm
void AddTerm(IntegerVariable var, IntegerValue coeff)
Definition: linear_constraint.h:48
operations_research::sat::LinearConstraintManager
Definition: linear_constraint_manager.h:40
operations_research::sat::CanBeFilteredUsingKnapsackUpperBound
bool CanBeFilteredUsingKnapsackUpperBound(const LinearConstraint &constraint, const gtl::ITIVector< IntegerVariable, double > &lp_values, const IntegerTrail &integer_trail)
Definition: cuts.cc:321
operations_research::sat::CreatePositiveMultiplicationCutGenerator
CutGenerator CreatePositiveMultiplicationCutGenerator(IntegerVariable z, IntegerVariable x, IntegerVariable y, Model *model)
Definition: cuts.cc:1140
operations_research::sat::ImpliedBoundsProcessor::BestImpliedBoundInfo::is_positive
bool is_positive
Definition: cuts.h:111
operations_research::sat::MakeAllCoefficientsPositive
void MakeAllCoefficientsPositive(LinearConstraint *constraint)
Definition: linear_constraint.cc:209
operations_research::sat::GetCoefficientOfPositiveVar
IntegerValue GetCoefficientOfPositiveVar(const IntegerVariable var, const LinearExpression &expr)
Definition: linear_constraint.cc:341
model
GRBmodel * model
Definition: gurobi_interface.cc:195
operations_research::sat::CanFormValidKnapsackCover
bool CanFormValidKnapsackCover(const LinearConstraint &preprocessed_constraint, const gtl::ITIVector< IntegerVariable, double > &lp_values, const IntegerTrail &integer_trail)
Definition: cuts.cc:355
operations_research::sat::CreateOptionalIntervalCutGenerator
CutGenerator CreateOptionalIntervalCutGenerator(IntegerVariable start, IntegerVariable size, IntegerVariable end, Literal presence, Model *model)
Definition: cuts.cc:1793
operations_research::sat::RemoveZeroTerms
void RemoveZeroTerms(LinearConstraint *constraint)
Definition: linear_constraint.cc:196
operations_research::sat::ImpliedBoundsProcessor::BestImpliedBoundInfo
Definition: cuts.h:108
operations_research::KnapsackSolverForCuts
Definition: knapsack_solver_for_cuts.h:300
operations_research::sat::GetSuperAdditiveRoundingFunction
std::function< IntegerValue(IntegerValue)> GetSuperAdditiveRoundingFunction(IntegerValue rhs_remainder, IntegerValue divisor, IntegerValue t, IntegerValue max_scaling)
Definition: cuts.cc:609
operations_research::sat::ToDouble
double ToDouble(IntegerValue value)
Definition: integer.h:69
operations_research::sat::ImpliedBoundsProcessor::BestImpliedBoundInfo::bound_diff
IntegerValue bound_diff
Definition: cuts.h:112
coefficient
int64 coefficient
Definition: routing_search.cc:973
operations_research::sat::kMinIntegerValue
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue)
operations_research::sat::LinearConstraintBuilder
Definition: linear_constraint.h:82
operations_research::sat::MakeAllVariablesPositive
void MakeAllVariablesPositive(LinearConstraint *constraint)
Definition: linear_constraint.cc:220
operations_research::KnapsackSolverForCuts::set_node_limit
void set_node_limit(const int64 node_limit)
Definition: knapsack_solver_for_cuts.h:336
end_min
Rev< int64 > end_min
Definition: sched_constraints.cc:243
stl_util.h
operations_research::KnapsackSolverForCuts::best_solution
bool best_solution(int item_id) const
Definition: knapsack_solver_for_cuts.h:341
operations_research::sat::ComputeActivity
double ComputeActivity(const LinearConstraint &constraint, const gtl::ITIVector< IntegerVariable, double > &values)
Definition: linear_constraint.cc:116
operations_research::sat::ImpliedBoundsProcessor::ProcessUpperBoundedConstraintWithSlackCreation
void ProcessUpperBoundedConstraintWithSlackCreation(bool substitute_only_inner_variables, IntegerVariable first_slack, const gtl::ITIVector< IntegerVariable, double > &lp_values, LinearConstraint *cut, std::vector< SlackInfo > *slack_infos, std::vector< LinearConstraint > *implied_bound_cuts) const
Definition: cuts.cc:1391
operations_research::sat::ImpliedBoundEntry
Definition: implied_bounds.h:39
lower_bounds
std::vector< double > lower_bounds
Definition: sat/lp_utils.cc:288
operations_research::SumOfKMaxValueInDomain
int64 SumOfKMaxValueInDomain(const Domain &domain, int k)
Definition: sorted_interval_list.cc:549
operations_research::sat::LinearConstraint::vars
std::vector< IntegerVariable > vars
Definition: linear_constraint.h:42
operations_research::sat::GetKnapsackUpperBound
double GetKnapsackUpperBound(std::vector< KnapsackItem > items, const double capacity)
Definition: cuts.cc:303
operations_research::sat::CreateCumulativeCutGenerator
CutGenerator CreateCumulativeCutGenerator(const std::vector< IntervalVariable > &intervals, const IntegerVariable capacity, const std::vector< IntegerVariable > &demands, Model *model)
Definition: cuts.cc:1823
operations_research::sat::IntegerRoundingCutHelper::ComputeCut
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:692
operations_research::sat::LinearConstraintBuilder::Build
LinearConstraint Build()
Definition: linear_constraint.cc:108
operations_research::sat::CutGenerator::generate_cuts
std::function< void(const gtl::ITIVector< IntegerVariable, double > &lp_values, LinearConstraintManager *manager)> generate_cuts
Definition: cuts.h:44
gtl::ITIVector< IntegerVariable, double >
capacity
int64 capacity
Definition: routing_flow.cc:129
interval
IntervalVar * interval
Definition: resource.cc:98
operations_research::sat::CutGenerator
Definition: cuts.h:40
operations_research::sat::IntervalVar::StartVar
IntVar StartVar() const
Returns the start variable.
Definition: cp_model.cc:296
operations_research::sat::IntervalsRepository
Definition: intervals.h:45
operations_research::sat::ImpliedBoundsProcessor::SlackInfo::lp_value
double lp_value
Definition: cuts.h:84
operations_research::sat::DivideByGCD
void DivideByGCD(LinearConstraint *constraint)
Definition: linear_constraint.cc:182
cuts.h
upper_bounds
std::vector< double > upper_bounds
Definition: sat/lp_utils.cc:289
operations_research::sat::CreateKnapsackCoverCutGenerator
CutGenerator CreateKnapsackCoverCutGenerator(const std::vector< LinearConstraint > &base_constraints, const std::vector< IntegerVariable > &vars, Model *model)
Definition: cuts.cc:422
operations_research::sat::ImpliedBoundsProcessor::ProcessUpperBoundedConstraint
void ProcessUpperBoundedConstraint(const gtl::ITIVector< IntegerVariable, double > &lp_values, LinearConstraint *cut) const
Definition: cuts.cc:1303
operations_research::sat::LinearConstraintBuilder::AddLiteralTerm
ABSL_MUST_USE_RESULT bool AddLiteralTerm(Literal lit, IntegerValue coeff)
Definition: linear_constraint.cc:47
operations_research::KnapsackSolverForCuts::GetUpperBound
double GetUpperBound()
Definition: knapsack_solver_for_cuts.h:319
operations_research::sat::ConstraintIsTriviallyTrue
bool ConstraintIsTriviallyTrue(const LinearConstraint &constraint, const IntegerTrail &integer_trail)
Definition: cuts.cc:259
operations_research::KnapsackSolverForCuts::Init
void Init(const std::vector< double > &profits, const std::vector< double > &weights, const double capacity)
Definition: knapsack_solver_for_cuts.cc:286
integer.h
operations_research::sat::LinearConstraint::coeffs
std::vector< IntegerValue > coeffs
Definition: linear_constraint.h:43
kint64max
static const int64 kint64max
Definition: integral_types.h:62
time
int64 time
Definition: resource.cc:1683
operations_research::sat::ImpliedBoundsProcessor::GetCachedImpliedBoundInfo
BestImpliedBoundInfo GetCachedImpliedBoundInfo(IntegerVariable var)
Definition: cuts.cc:1312