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