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