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