OR-Tools  9.6
cuts.cc
Go to the documentation of this file.
1 // Copyright 2010-2022 Google LLC
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
14 #include "ortools/sat/cuts.h"
15 
16 #include <algorithm>
17 #include <cmath>
18 #include <cstdint>
19 #include <cstdlib>
20 #include <functional>
21 #include <limits>
22 #include <memory>
23 #include <string>
24 #include <utility>
25 #include <vector>
26 
27 #include "absl/container/btree_set.h"
28 #include "absl/container/flat_hash_map.h"
29 #include "absl/container/flat_hash_set.h"
30 #include "ortools/base/logging.h"
31 #include "ortools/base/stl_util.h"
33 #include "ortools/sat/clause.h"
35 #include "ortools/sat/integer.h"
38 #include "ortools/sat/model.h"
39 #include "ortools/sat/sat_base.h"
44 
45 namespace operations_research {
46 namespace sat {
47 
48 std::string CutTerm::DebugString() const {
49  return absl::StrCat("coeff=", coeff.value(), " lp=", lp_value,
50  " range=", bound_diff.value());
51 }
52 
53 bool CutTerm::Complement(IntegerValue* rhs) {
54  // We replace coeff * X by coeff * (X - bound_diff + bound_diff)
55  // which gives -coeff * complement(X) + coeff * bound_diff;
56  if (!AddProductTo(-coeff, bound_diff, rhs)) return false;
57 
58  // We keep the same expression variable.
59  for (int i = 0; i < 2; ++i) {
60  expr_coeffs[i] = -expr_coeffs[i];
61  }
63 
64  // Note that this is not involutive because of floating point error. Fix?
66  coeff = -coeff;
67  return true;
68 }
69 
70 // To try to minimize the risk of overflow, we switch to the bound closer
71 // to the lp_value. Since most of our base constraint for cut are tight,
72 // hopefully this is not too bad.
73 bool CutData::AppendOneTerm(IntegerVariable var, IntegerValue coeff,
74  double lp_value, IntegerValue lb, IntegerValue ub) {
75  if (coeff == 0) return true;
76  const IntegerValue bound_diff = ub - lb;
77 
78  // Complement the variable so that it is always closer to its lb.
79  bool complement = false;
80  const double lb_dist = std::abs(lp_value - ToDouble(lb));
81  const double ub_dist = std::abs(lp_value - ToDouble(ub));
82  if (ub_dist < lb_dist) {
83  complement = true;
84  }
85 
86  // See formula below, the constant term is either coeff * lb or coeff * ub.
87  if (!AddProductTo(-coeff, complement ? ub : lb, &rhs)) {
88  return false;
89  }
90 
91  // Deal with fixed variable, no need to shift back in this case, we can
92  // just remove the term.
93  if (bound_diff == 0) return true;
94 
95  CutTerm entry;
96  entry.expr_vars[0] = var;
97  entry.expr_coeffs[1] = 0;
98  entry.bound_diff = bound_diff;
99  if (complement) {
100  // X = -(UB - X) + UB
101  entry.expr_coeffs[0] = -IntegerValue(1);
102  entry.expr_offset = ub;
103  entry.coeff = -coeff;
104  entry.lp_value = ToDouble(ub) - lp_value;
105  } else {
106  // C = (X - LB) + LB
107  entry.expr_coeffs[0] = IntegerValue(1);
108  entry.expr_offset = -lb;
109  entry.coeff = coeff;
110  entry.lp_value = lp_value - ToDouble(lb);
111  }
112  terms.push_back(entry);
113  return true;
114 }
115 
117  const LinearConstraint& base_ct,
119  IntegerTrail* integer_trail) {
120  rhs = base_ct.ub;
121  terms.clear();
122  const int num_terms = base_ct.vars.size();
123  for (int i = 0; i < num_terms; ++i) {
124  const IntegerVariable var = base_ct.vars[i];
125  if (!AppendOneTerm(var, base_ct.coeffs[i], lp_values[base_ct.vars[i]],
126  integer_trail->LevelZeroLowerBound(var),
127  integer_trail->LevelZeroUpperBound(var))) {
128  return false;
129  }
130  }
131  return true;
132 }
133 
135  const LinearConstraint& base_ct, const std::vector<double>& lp_values,
136  const std::vector<IntegerValue>& lower_bounds,
137  const std::vector<IntegerValue>& upper_bounds) {
138  rhs = base_ct.ub;
139  terms.clear();
140 
141  const int size = lp_values.size();
142  if (size == 0) return true;
143 
144  CHECK_EQ(lower_bounds.size(), size);
145  CHECK_EQ(upper_bounds.size(), size);
146  CHECK_EQ(base_ct.vars.size(), size);
147  CHECK_EQ(base_ct.coeffs.size(), size);
148  CHECK_EQ(base_ct.lb, kMinIntegerValue);
149 
150  for (int i = 0; i < size; ++i) {
151  if (!AppendOneTerm(base_ct.vars[i], base_ct.coeffs[i], lp_values[i],
152  lower_bounds[i], upper_bounds[i])) {
153  return false;
154  }
155  }
156 
157  return true;
158 }
159 
163  for (int i = 0; i < terms.size(); ++i) {
164  CutTerm& entry = terms[i];
166  if (entry.HasRelevantLpValue()) {
169  }
170  }
171 
172  // Sort by larger lp_value first.
173  std::sort(terms.begin(), terms.begin() + num_relevant_entries,
174  [](const CutTerm& a, const CutTerm& b) {
175  return a.lp_value > b.lp_value;
176  });
177 }
178 
180  num_merges_ = 0;
181  constraint_is_indexed_ = false;
182  direct_index_.clear();
183  complemented_index_.clear();
184 }
185 
186 void CutDataBuilder::RegisterAllBooleansTerms(const CutData& cut) {
187  constraint_is_indexed_ = true;
188  const int size = cut.terms.size();
189  for (int i = 0; i < size; ++i) {
190  const CutTerm& term = cut.terms[i];
191  if (term.bound_diff != 1) continue;
192  if (!term.IsSimple()) continue;
193  if (term.expr_coeffs[0] > 0) {
194  direct_index_[term.expr_vars[0]] = i;
195  } else {
196  complemented_index_[term.expr_vars[0]] = i;
197  }
198  }
199 }
200 
201 void CutDataBuilder::AddOrMergeTerm(const CutTerm& term, IntegerValue t,
202  CutData* cut) {
203  if (!constraint_is_indexed_) {
204  RegisterAllBooleansTerms(*cut);
205  }
206 
207  DCHECK(term.IsSimple());
208  const IntegerVariable var = term.expr_vars[0];
209  const int new_index = cut->terms.size();
210  const auto [it, inserted] =
211  term.expr_coeffs[0] > 0 ? direct_index_.insert({var, new_index})
212  : complemented_index_.insert({var, new_index});
213  const int entry_index = it->second;
214  if (inserted) {
215  cut->terms.push_back(term);
216  } else {
217  // We can only merge the term if term.coeff + old_coeff do not overflow and
218  // if t * new_coeff do not overflow.
219  //
220  // If we cannot merge the term, we will keep them separate. The produced cut
221  // will be less strong, but can still be used.
222  const int64_t new_coeff =
223  CapAdd(cut->terms[entry_index].coeff.value(), term.coeff.value());
224  const int64_t overflow_check = CapProd(t.value(), new_coeff);
225  if (AtMinOrMaxInt64(new_coeff) || AtMinOrMaxInt64(overflow_check)) {
226  // If we cannot merge the term, we keep them separate.
227  cut->terms.push_back(term);
228  } else {
229  ++num_merges_;
230  cut->terms[entry_index].coeff = IntegerValue(new_coeff);
231  }
232  }
233 }
234 
236  LinearConstraint* output) {
237  tmp_map_.clear();
238  IntegerValue new_rhs = cut.rhs;
239  for (const CutTerm& term : cut.terms) {
240  for (int i = 0; i < 2; ++i) {
241  if (term.expr_coeffs[i] == 0) continue;
242  if (!AddProductTo(term.coeff, term.expr_coeffs[i],
243  &tmp_map_[term.expr_vars[i]])) {
244  return false;
245  }
246  }
247  if (!AddProductTo(-term.coeff, term.expr_offset, &new_rhs)) {
248  return false;
249  }
250  }
251 
252  output->ClearTerms();
253  output->lb = kMinIntegerValue;
254  output->ub = new_rhs;
255  for (const auto [var, coeff] : tmp_map_) {
256  if (coeff == 0) continue;
257  output->vars.push_back(var);
258  output->coeffs.push_back(coeff);
259  }
260  DivideByGCD(output);
261  return true;
262 }
263 
264 namespace {
265 
266 // Minimum amount of violation of the cut constraint by the solution. This
267 // is needed to avoid numerical issues and adding cuts with minor effect.
268 const double kMinCutViolation = 1e-4;
269 
270 IntegerValue CapProdI(IntegerValue a, IntegerValue b) {
271  return IntegerValue(CapProd(a.value(), b.value()));
272 }
273 
274 IntegerValue CapSubI(IntegerValue a, IntegerValue b) {
275  return IntegerValue(CapSub(a.value(), b.value()));
276 }
277 
278 IntegerValue CapAddI(IntegerValue a, IntegerValue b) {
279  return IntegerValue(CapAdd(a.value(), b.value()));
280 }
281 
282 bool ProdOverflow(IntegerValue t, IntegerValue value) {
283  return AtMinOrMaxInt64(CapProd(t.value(), value.value()));
284 }
285 
286 } // namespace
287 
288 // Compute the larger t <= max_t such that t * rhs_remainder >= divisor / 2.
289 //
290 // This is just a separate function as it is slightly faster to compute the
291 // result only once.
292 IntegerValue GetFactorT(IntegerValue rhs_remainder, IntegerValue divisor,
293  IntegerValue max_magnitude) {
294  // Make sure that when we multiply the rhs or the coefficient by a factor t,
295  // we do not have an integer overflow. Note that the rhs should be counted
296  // in max_magnitude since we will apply f() on it.
297  IntegerValue max_t(std::numeric_limits<int64_t>::max());
298  if (max_magnitude != 0) {
299  max_t = max_t / max_magnitude;
300  }
301  return rhs_remainder == 0
302  ? max_t
303  : std::min(max_t, CeilRatio(divisor / 2, rhs_remainder));
304 }
305 
306 std::function<IntegerValue(IntegerValue)> GetSuperAdditiveRoundingFunction(
307  IntegerValue rhs_remainder, IntegerValue divisor, IntegerValue t,
308  IntegerValue max_scaling) {
309  DCHECK_GE(max_scaling, 1);
310  DCHECK_GE(t, 1);
311 
312  // Adjust after the multiplication by t.
313  rhs_remainder *= t;
314  DCHECK_LT(rhs_remainder, divisor);
315 
316  // Make sure we don't have an integer overflow below. Note that we assume that
317  // divisor and the maximum coeff magnitude are not too different (maybe a
318  // factor 1000 at most) so that the final result will never overflow.
319  max_scaling =
320  std::min(max_scaling, std::numeric_limits<int64_t>::max() / divisor);
321 
322  const IntegerValue size = divisor - rhs_remainder;
323  if (max_scaling == 1 || size == 1) {
324  // TODO(user): Use everywhere a two step computation to avoid overflow?
325  // First divide by divisor, then multiply by t. For now, we limit t so that
326  // we never have an overflow instead.
327  return [t, divisor](IntegerValue coeff) {
328  return FloorRatio(t * coeff, divisor);
329  };
330  } else if (size <= max_scaling) {
331  return [size, rhs_remainder, t, divisor](IntegerValue coeff) {
332  const IntegerValue t_coeff = t * coeff;
333  const IntegerValue ratio = FloorRatio(t_coeff, divisor);
334  const IntegerValue remainder = PositiveRemainder(t_coeff, divisor);
335  const IntegerValue diff = remainder - rhs_remainder;
336  return size * ratio + std::max(IntegerValue(0), diff);
337  };
338  } else if (max_scaling.value() * rhs_remainder.value() < divisor) {
339  // Because of our max_t limitation, the rhs_remainder might stay small.
340  //
341  // If it is "too small" we cannot use the code below because it will not be
342  // valid. So we just divide divisor into max_scaling bucket. The
343  // rhs_remainder will be in the bucket 0.
344  //
345  // Note(user): This seems the same as just increasing t, modulo integer
346  // overflows. Maybe we should just always do the computation like this so
347  // that we can use larger t even if coeff is close to kint64max.
348  return [t, divisor, max_scaling](IntegerValue coeff) {
349  const IntegerValue t_coeff = t * coeff;
350  const IntegerValue ratio = FloorRatio(t_coeff, divisor);
351  const IntegerValue remainder = PositiveRemainder(t_coeff, divisor);
352  const IntegerValue bucket = FloorRatio(remainder * max_scaling, divisor);
353  return max_scaling * ratio + bucket;
354  };
355  } else {
356  // We divide (size = divisor - rhs_remainder) into (max_scaling - 1) buckets
357  // and increase the function by 1 / max_scaling for each of them.
358  //
359  // Note that for different values of max_scaling, we get a family of
360  // functions that do not dominate each others. So potentially, a max scaling
361  // as low as 2 could lead to the better cut (this is exactly the Letchford &
362  // Lodi function).
363  //
364  // Another interesting fact, is that if we want to compute the maximum alpha
365  // for a constraint with 2 terms like:
366  // divisor * Y + (ratio * divisor + remainder) * X
367  // <= rhs_ratio * divisor + rhs_remainder
368  // so that we have the cut:
369  // Y + (ratio + alpha) * X <= rhs_ratio
370  // This is the same as computing the maximum alpha such that for all integer
371  // X > 0 we have CeilRatio(alpha * divisor * X, divisor)
372  // <= CeilRatio(remainder * X - rhs_remainder, divisor).
373  // We can prove that this alpha is of the form (n - 1) / n, and it will
374  // be reached by such function for a max_scaling of n.
375  //
376  // TODO(user): This function is not always maximal when
377  // size % (max_scaling - 1) == 0. Improve?
378  return [size, rhs_remainder, t, divisor, max_scaling](IntegerValue coeff) {
379  const IntegerValue t_coeff = t * coeff;
380  const IntegerValue ratio = FloorRatio(t_coeff, divisor);
381  const IntegerValue remainder = PositiveRemainder(t_coeff, divisor);
382  const IntegerValue diff = remainder - rhs_remainder;
383  const IntegerValue bucket =
384  diff > 0 ? CeilRatio(diff * (max_scaling - 1), size)
385  : IntegerValue(0);
386  return max_scaling * ratio + bucket;
387  };
388  }
389 }
390 
392  if (!VLOG_IS_ON(1)) return;
393  if (shared_stats_ == nullptr) return;
394  std::vector<std::pair<std::string, int64_t>> stats;
395  stats.push_back({"rounding_cut/num_initial_ibs_", total_num_initial_ibs_});
396  stats.push_back(
397  {"rounding_cut/num_initial_merges_", total_num_initial_merges_});
398  stats.push_back({"rounding_cut/num_pos_lifts", total_num_pos_lifts_});
399  stats.push_back({"rounding_cut/num_neg_lifts", total_num_neg_lifts_});
400  stats.push_back(
401  {"rounding_cut/num_post_complements", total_num_post_complements_});
402  stats.push_back({"rounding_cut/num_overflows", total_num_overflow_abort_});
403  stats.push_back({"rounding_cut/num_adjusts", total_num_coeff_adjust_});
404  stats.push_back({"rounding_cut/num_merges", total_num_merges_});
405  stats.push_back({"rounding_cut/num_bumps", total_num_bumps_});
406  stats.push_back(
407  {"rounding_cut/num_final_complements", total_num_final_complements_});
408  stats.push_back({"rounding_cut/num_dominating_f", total_num_dominating_f_});
409  shared_stats_->AddStats(stats);
410 }
411 
412 double IntegerRoundingCutHelper::GetScaledViolation(
413  IntegerValue divisor, IntegerValue max_scaling,
414  IntegerValue remainder_threshold, const CutData& cut) {
415  IntegerValue rhs = cut.rhs;
416  IntegerValue max_magnitude = cut.max_magnitude;
417  const IntegerValue initial_rhs_remainder = PositiveRemainder(rhs, divisor);
418  if (initial_rhs_remainder < remainder_threshold) return 0.0;
419 
420  // We will adjust coefficient that are just under an exact multiple of
421  // divisor to an exact multiple. This is meant to get rid of small errors
422  // that appears due to rounding error in our exact computation of the
423  // initial constraint given to this class.
424  //
425  // Each adjustement will cause the initial_rhs_remainder to increase, and we
426  // do not want to increase it above divisor. Our threshold below guarantees
427  // this. Note that the higher the rhs_remainder becomes, the more the
428  // function f() has a chance to reduce the violation, so it is not always a
429  // good idea to use all the slack we have between initial_rhs_remainder and
430  // divisor.
431  //
432  // TODO(user): We could see if for a fixed function f, the increase is
433  // interesting?
434  // before: f(rhs) - f(coeff) * lp_value
435  // after: f(rhs + increase * bound_diff) - f(coeff + increase) * lp_value.
436  adjusted_coeffs_.clear();
437  const IntegerValue adjust_threshold =
438  (divisor - initial_rhs_remainder - 1) /
439  IntegerValue(std::max(1000, cut.num_relevant_entries));
440  if (adjust_threshold > 0) {
441  // Even before we finish the adjust, we can have a lower bound on the
442  // activily loss using this divisor, and so we can abort early. This is
443  // similar to what is done below.
444  double max_violation = ToDouble(initial_rhs_remainder);
445  for (int i = 0; i < cut.num_relevant_entries; ++i) {
446  const CutTerm& entry = cut.terms[i];
447  const IntegerValue remainder = PositiveRemainder(entry.coeff, divisor);
448  if (remainder == 0) continue;
449  if (remainder <= initial_rhs_remainder) {
450  // We do not know exactly f() yet, but it will always round to the
451  // floor of the division by divisor in this case.
452  max_violation -= ToDouble(remainder) * entry.lp_value;
453  if (max_violation <= 1e-3) return 0.0;
454  continue;
455  }
456 
457  // Adjust coeff of the form k * divisor - epsilon.
458  const IntegerValue adjust = divisor - remainder;
459  const IntegerValue prod = CapProdI(adjust, entry.bound_diff);
460  if (prod <= adjust_threshold) {
461  rhs += prod;
462  const IntegerValue new_coeff = entry.coeff + adjust;
463  adjusted_coeffs_.push_back({i, new_coeff});
464  max_magnitude = std::max(max_magnitude, IntTypeAbs(new_coeff));
465  }
466  }
467  }
468 
469  max_magnitude = std::max(max_magnitude, IntTypeAbs(rhs));
470  const IntegerValue rhs_remainder = PositiveRemainder(rhs, divisor);
471  const IntegerValue t = GetFactorT(rhs_remainder, divisor, max_magnitude);
472  const auto f =
473  GetSuperAdditiveRoundingFunction(rhs_remainder, divisor, t, max_scaling);
474 
475  // As we round coefficients, we will compute the loss compared to the
476  // current scaled constraint activity. As soon as this loss crosses the
477  // slack, then we known that there is no violation and we can abort early.
478  //
479  // TODO(user): modulo the scaling, we could compute the exact threshold
480  // using our current best cut. Note that we also have to account the change
481  // in slack due to the adjust code above.
482  const double scaling = ToDouble(f(divisor)) / ToDouble(divisor);
483  double max_violation = scaling * ToDouble(rhs_remainder);
484 
485  // Apply f() to the cut and compute the cut violation. Note that it is
486  // okay to just look at the relevant indices since the other have a lp
487  // value which is almost zero. Doing it like this is faster, and even if
488  // the max_magnitude might be off it should still be relevant enough.
489  double violation = -ToDouble(f(rhs));
490  double l2_norm = 0.0;
491  int adjusted_coeffs_index = 0;
492  for (int i = 0; i < cut.num_relevant_entries; ++i) {
493  const CutTerm& entry = cut.terms[i];
494 
495  // Adjust coeff according to our previous computation if needed.
496  IntegerValue coeff = entry.coeff;
497  if (adjusted_coeffs_index < adjusted_coeffs_.size() &&
498  adjusted_coeffs_[adjusted_coeffs_index].first == i) {
499  coeff = adjusted_coeffs_[adjusted_coeffs_index].second;
500  adjusted_coeffs_index++;
501  }
502 
503  if (coeff == 0) continue;
504  const IntegerValue new_coeff = f(coeff);
505  const double new_coeff_double = ToDouble(new_coeff);
506  const double lp_value = entry.lp_value;
507 
508  // TODO(user): Shall we compute the norm after slack are substituted back?
509  // it might be widely different. Another reason why this might not be
510  // the best measure.
511  l2_norm += new_coeff_double * new_coeff_double;
512  violation += new_coeff_double * lp_value;
513  max_violation -= (scaling * ToDouble(coeff) - new_coeff_double) * lp_value;
514  if (max_violation <= 1e-3) return 0.0;
515  }
516  if (l2_norm == 0.0) return 0.0;
517 
518  // Here we scale by the L2 norm over the "relevant" positions. This seems
519  // to work slighly better in practice.
520  //
521  // Note(user): The non-relevant position have an LP value of zero. If their
522  // coefficient is positive, it seems good not to take it into account in the
523  // norm since the larger this coeff is, the stronger the cut. If the coeff
524  // is negative though, a large coeff means a small increase from zero of the
525  // lp value will make the cut satisfied, so we might want to look at them.
526  return violation / sqrt(l2_norm);
527 }
528 
529 bool IntegerRoundingCutHelper::HasComplementedImpliedBound(
530  const CutTerm& entry, ImpliedBoundsProcessor* ib_processor) {
531  if (ib_processor == nullptr) return false;
532  if (!entry.IsSimple()) return false;
533  if (entry.bound_diff == 1) return false;
534  const ImpliedBoundsProcessor::BestImpliedBoundInfo info =
535  ib_processor->GetCachedImpliedBoundInfo(
536  entry.expr_coeffs[0] > 0 ? NegationOf(entry.expr_vars[0])
537  : entry.expr_vars[0]);
538  return info.bool_var != kNoIntegerVariable;
539 }
540 
541 // TODO(user): This is slow, 50% of run time on a2c1s1.pb.gz. Optimize!
543  RoundingOptions options, const CutData& base_ct,
544  ImpliedBoundsProcessor* ib_processor) {
545  // Try IB before heuristic?
546  // This should be better except it can mess up the norm and the divisors.
547  best_cut_ = base_ct;
548  if (options.use_ib_before_heuristic && ib_processor != nullptr) {
549  cut_builder_.ClearIndices();
550  const int old_size = static_cast<int>(best_cut_.terms.size());
551  bool abort = true;
552  for (int i = 0; i < old_size; ++i) {
553  if (best_cut_.terms[i].bound_diff <= 1) continue;
554  if (!best_cut_.terms[i].HasRelevantLpValue()) continue;
555 
556  if (options.prefer_positive_ib && best_cut_.terms[i].coeff < 0) {
557  // We complement the term before trying the implied bound.
558  if (best_cut_.terms[i].Complement(&best_cut_.rhs)) {
559  if (ib_processor->TryToExpandWithLowerImpliedbound(
560  IntegerValue(1), i,
561  /*complement=*/true, &best_cut_, &cut_builder_)) {
562  ++total_num_initial_ibs_;
563  abort = false;
564  continue;
565  }
566  best_cut_.terms[i].Complement(&best_cut_.rhs);
567  }
568  }
569 
570  if (ib_processor->TryToExpandWithLowerImpliedbound(
571  IntegerValue(1), i,
572  /*complement=*/true, &best_cut_, &cut_builder_)) {
573  abort = false;
574  ++total_num_initial_ibs_;
575  }
576  }
577  total_num_initial_merges_ += cut_builder_.NumMergesSinceLastClear();
578 
579  // TODO(user): We assume that this is called with and without the option
580  // use_ib_before_heuristic, so that we can abort if no IB has been applied
581  // since then we will redo the computation. This is not really clean.
582  if (abort) return false;
583  }
584 
585  // Our heuristic will try to generate a few different cuts, and we will keep
586  // the most violated one scaled by the l2 norm of the relevant position.
587  //
588  // TODO(user): Experiment for the best value of this initial violation
589  // threshold. Note also that we use the l2 norm on the restricted position
590  // here. Maybe we should change that? On that note, the L2 norm usage seems
591  // a bit weird to me since it grows with the number of term in the cut. And
592  // often, we already have a good cut, and we make it stronger by adding
593  // extra terms that do not change its activity.
594  //
595  // The discussion above only concern the best_scaled_violation initial
596  // value. The remainder_threshold allows to not consider cuts for which the
597  // final efficacity is clearly lower than 1e-3 (it is a bound, so we could
598  // generate cuts with a lower efficacity than this).
599  //
600  // TODO(user): If the rhs is small and close to zero, we might want to
601  // consider different way of complementing the variables.
602  best_cut_.Canonicalize();
603  const IntegerValue remainder_threshold(
604  std::max(IntegerValue(1), best_cut_.max_magnitude / 1000));
605  if (best_cut_.rhs >= 0 && best_cut_.rhs < remainder_threshold) {
606  return false;
607  }
608 
609  // There is no point trying twice the same divisor or a divisor that is too
610  // small. Note that we use a higher threshold than the remainder_threshold
611  // because we can boost the remainder thanks to our adjusting heuristic
612  // below and also because this allows to have cuts with a small range of
613  // coefficients.
614  divisors_.clear();
615  for (const CutTerm& entry : best_cut_.terms) {
616  // Note that because of the slacks, initial coeff are here too.
617  const IntegerValue magnitude = IntTypeAbs(entry.coeff);
618  if (magnitude <= remainder_threshold) continue;
619  divisors_.push_back(magnitude);
620  }
621  if (divisors_.empty()) return false;
622  gtl::STLSortAndRemoveDuplicates(&divisors_, std::greater<IntegerValue>());
623 
624  // Note that most of the time is spend here since we call this function on
625  // many linear equation, and just a few of them have a good enough scaled
626  // violation. We can spend more time afterwards to tune the cut.
627  //
628  // TODO(user): Avoid quadratic algorithm? Note that we are quadratic in
629  // relevant positions not the full cut size, but this is still too much on
630  // some problems.
631  IntegerValue best_divisor(0);
632  double best_scaled_violation = 1e-3;
633  for (const IntegerValue divisor : divisors_) {
634  // Note that the function will abort right away if PositiveRemainder() is
635  // not good enough, so it is quick for bad divisor.
636  const double violation = GetScaledViolation(divisor, options.max_scaling,
637  remainder_threshold, best_cut_);
638  if (violation > best_scaled_violation) {
639  best_scaled_violation = violation;
640  best_adjusted_coeffs_ = adjusted_coeffs_;
641  best_divisor = divisor;
642  }
643  }
644  if (best_divisor == 0) return false;
645 
646  // Try best_divisor divided by small number.
647  for (int div = 2; div < 9; ++div) {
648  const IntegerValue divisor = best_divisor / IntegerValue(div);
649  if (divisor <= 1) continue;
650  const double violation = GetScaledViolation(divisor, options.max_scaling,
651  remainder_threshold, best_cut_);
652  if (violation > best_scaled_violation) {
653  best_scaled_violation = violation;
654  best_adjusted_coeffs_ = adjusted_coeffs_;
655  best_divisor = divisor;
656  }
657  }
658 
659  // Re try complementation on the transformed cut.
660  for (CutTerm& entry : best_cut_.terms) {
661  if (!entry.HasRelevantLpValue()) break;
662  if (entry.coeff % best_divisor == 0) continue;
663 
664  // Temporary try to complement this variable.
665  if (!entry.Complement(&best_cut_.rhs)) continue;
666 
667  const double violation = GetScaledViolation(
668  best_divisor, options.max_scaling, remainder_threshold, best_cut_);
669  if (violation > best_scaled_violation) {
670  // keep the change.
671  ++total_num_post_complements_;
672  best_scaled_violation = violation;
673  best_adjusted_coeffs_ = adjusted_coeffs_;
674  } else {
675  // Restore.
676  entry.Complement(&best_cut_.rhs);
677  }
678  }
679 
680  // Adjust coefficients as computed by the best GetScaledViolation().
681  for (const auto [index, new_coeff] : best_adjusted_coeffs_) {
682  ++total_num_coeff_adjust_;
683  CutTerm& entry = best_cut_.terms[index];
684  const IntegerValue remainder = new_coeff - entry.coeff;
685  CHECK_GT(remainder, 0);
686  entry.coeff = new_coeff;
687  best_cut_.rhs += remainder * entry.bound_diff;
688  best_cut_.max_magnitude =
689  std::max(best_cut_.max_magnitude, IntTypeAbs(new_coeff));
690  }
691  best_cut_.max_magnitude =
692  std::max(best_cut_.max_magnitude, IntTypeAbs(best_cut_.rhs));
693 
694  // Create the base super-additive function f().
695  const IntegerValue rhs_remainder =
696  PositiveRemainder(best_cut_.rhs, best_divisor);
697  IntegerValue factor_t =
698  GetFactorT(rhs_remainder, best_divisor, best_cut_.max_magnitude);
699  auto f = GetSuperAdditiveRoundingFunction(rhs_remainder, best_divisor,
700  factor_t, options.max_scaling);
701 
702  // Look amongst all our possible function f() for one that dominate greedily
703  // our current best one. Note that we prefer lower scaling factor since that
704  // result in a cut with lower coefficients.
705  //
706  // We only look at relevant position and ignore the other. Not sure this is
707  // the best approach.
708  remainders_.clear();
709  for (const CutTerm& entry : best_cut_.terms) {
710  if (!entry.HasRelevantLpValue()) break;
711  const IntegerValue coeff = entry.coeff;
712  const IntegerValue r = PositiveRemainder(coeff, best_divisor);
713  if (r > rhs_remainder) remainders_.push_back(r);
714  }
715  gtl::STLSortAndRemoveDuplicates(&remainders_);
716  if (remainders_.size() <= 100) {
717  best_rs_.clear();
718  for (const IntegerValue r : remainders_) {
719  best_rs_.push_back(f(r));
720  }
721  IntegerValue best_d = f(best_divisor);
722 
723  // Note that the complexity seems high 100 * 2 * options.max_scaling, but
724  // this only run on cuts that are already efficient and the inner loop tend
725  // to abort quickly. I didn't see this code in the cpu profile so far.
726  for (const IntegerValue t :
727  {IntegerValue(1),
728  GetFactorT(rhs_remainder, best_divisor, best_cut_.max_magnitude)}) {
729  for (IntegerValue s(2); s <= options.max_scaling; ++s) {
730  const auto g =
731  GetSuperAdditiveRoundingFunction(rhs_remainder, best_divisor, t, s);
732  int num_strictly_better = 0;
733  rs_.clear();
734  const IntegerValue d = g(best_divisor);
735  for (int i = 0; i < best_rs_.size(); ++i) {
736  const IntegerValue temp = g(remainders_[i]);
737  if (temp * best_d < best_rs_[i] * d) break;
738  if (temp * best_d > best_rs_[i] * d) num_strictly_better++;
739  rs_.push_back(temp);
740  }
741  if (rs_.size() == best_rs_.size() && num_strictly_better > 0) {
742  ++total_num_dominating_f_;
743  f = g;
744  factor_t = t;
745  best_rs_ = rs_;
746  best_d = d;
747  }
748  }
749  }
750  }
751 
752  // Use implied bounds to "lift" Booleans into the cut.
753  // This should lead to stronger cuts even if the norms migth be worse.
754  num_ib_used_ = 0;
755  if (ib_processor != nullptr) {
756  cut_builder_.ClearIndices();
757  const int old_size = best_cut_.terms.size();
758  for (int i = 0; i < old_size; ++i) {
759  CutTerm& term = best_cut_.terms[i];
760 
761  // We only want to expand non-Boolean and non-slack term!
762  if (term.bound_diff <= 1) continue;
763  if (!term.IsSimple()) continue;
764 
765  if (ib_processor->TryToExpandWithLowerImpliedbound(
766  factor_t, i, /*complement=*/false, &best_cut_, &cut_builder_)) {
767  ++num_ib_used_;
768  ++total_num_pos_lifts_;
769  continue;
770  }
771 
772  // Use the implied bound on (-X) if it is beneficial to do so.
773  // Like complementing, this is not always good.
774  //
775  // We have comp(X) = diff - X = diff * B + S
776  // X = diff * (1 - B) - S.
777  // So if we applies f, we will get:
778  // f(coeff * diff) * (1 - B) + f(-coeff) * S
779  // and substituing S = diff * (1 - B) - X, we get:
780  // -f(-coeff) * X + [f(coeff * diff) + f(-coeff) * diff] (1 - B).
781  //
782  // TODO(user): Note that while the violation might be higher, if the slack
783  // becomes large this will result in a less powerfull cut. Shall we do
784  // that? It is a bit the same problematic with complementing.
785  //
786  // TODO(user): If the slack is close to zero, then this transformation
787  // will always increase the violation. So we could potentially do it in
788  // Before our divisor selection heuristic. But the norm of the final cut
789  // will increase too.
790  if (!HasComplementedImpliedBound(term, ib_processor)) continue;
792  ib_processor->GetCachedImpliedBoundInfo(
793  term.expr_coeffs[0] > 0 ? NegationOf(term.expr_vars[0])
794  : term.expr_vars[0]);
795  const IntegerValue lb = -term.expr_offset;
796  const IntegerValue bound_diff = info.implied_bound - lb;
797  // We do not want overflow when computing f().
798  if (ProdOverflow(factor_t, CapProdI(term.coeff, bound_diff))) {
799  continue;
800  }
801 
802  // We only consider IB that span the full range here.
803  if (bound_diff != term.bound_diff) continue;
804 
805  // Note that -f(-coeff) >= f(coeff) but coeff_b <= 0.
806  const IntegerValue coeff_b =
807  f(term.coeff * bound_diff) + f(-term.coeff) * bound_diff;
808  CHECK_LE(coeff_b, 0);
809  const double lp1 = ToDouble(f(term.coeff)) * term.lp_value;
810  const double lp2 = -ToDouble(f(-term.coeff)) * term.lp_value +
811  ToDouble(coeff_b) * (1 - info.bool_lp_value);
812  if (lp2 > lp1 + 1e-2) {
813  // Create the Boolean term for (1 - B) in X = diff * (1 - B) - S
814  // We reverse the is_positive meaning here since we have (1 - B).
815  CutTerm bool_term;
816  bool_term.coeff = bound_diff * term.coeff;
817  bool_term.expr_vars[0] = info.bool_var;
818  bool_term.expr_coeffs[1] = 0;
819  bool_term.bound_diff = IntegerValue(1);
820  bool_term.lp_value = 1.0 - info.bool_lp_value;
821  if (!info.is_positive) {
822  bool_term.expr_coeffs[0] = IntegerValue(1);
823  bool_term.expr_offset = IntegerValue(0);
824  } else {
825  bool_term.expr_coeffs[0] = IntegerValue(-1);
826  bool_term.expr_offset = IntegerValue(1);
827  }
828 
829  // Create the slack term in X = diff * (1 - B) - S
830  CutTerm slack_term;
831  slack_term.coeff = -term.coeff;
832  slack_term.expr_vars[0] = term.expr_vars[0];
833  slack_term.expr_coeffs[0] = -term.expr_coeffs[0];
834  slack_term.expr_vars[1] = bool_term.expr_vars[0];
835  slack_term.expr_coeffs[1] = bound_diff * bool_term.expr_coeffs[0];
836  slack_term.expr_offset =
837  bound_diff * bool_term.expr_offset - term.expr_offset;
838  slack_term.lp_value = info.SlackLpValue(lb);
839  slack_term.bound_diff = term.bound_diff;
840 
841  // Commit the change.
842  ++num_ib_used_;
843  ++total_num_neg_lifts_;
844  term = slack_term;
845  cut_builder_.AddOrMergeTerm(bool_term, factor_t, &best_cut_);
846  }
847  }
848  total_num_merges_ += cut_builder_.NumMergesSinceLastClear();
849  }
850 
851  // More complementation, but for the same f.
852  // If we can do that, it probably means our heuristics above are not great.
853  for (int i = 0; i < 3; ++i) {
854  const int64_t saved = total_num_final_complements_;
855  for (CutTerm& entry : best_cut_.terms) {
856  // Complementing an entry gives:
857  // [a * X <= b] -> [-a * (diff - X) <= b - a * diff]
858  //
859  // We will compare what happen when we apply f:
860  // [f(b) - f(a) * lp(X)] -> [f(b - a * diff) - f(-a) * (diff - lp(X))].
861  //
862  // If lp(X) is zero, then the transformation is always worse.
863  // Because f(b - a * diff) >= f(b) + f(-a) * diff by super-additivity.
864  //
865  // However the larger is X, the better it gets since at diff, we have
866  // f(b) >= f(b - a * diff) + f(a * diff) >= f(b - a * diff) + f(a) * diff.
867  //
868  // TODO(user): It is still unclear if we have a * X + b * (1 - X) <= rhs
869  // for a Boolean X, what is the best way to apply f and if we should merge
870  // the terms. If there is no other terms, best is probably
871  // f(rhs - a) * X + f(rhs - b) * (1 - X).
872  if (entry.coeff % best_divisor == 0) continue;
873  if (!entry.HasRelevantLpValue()) continue;
874 
875  // Avoid potential overflow here.
876  const IntegerValue prod(CapProdI(entry.bound_diff, entry.coeff));
877  if (ProdOverflow(factor_t, prod)) continue;
878  if (ProdOverflow(factor_t, CapSubI(best_cut_.rhs, prod))) continue;
879 
880  const double lp1 = ToDouble(f(best_cut_.rhs)) -
881  ToDouble(f(entry.coeff)) * entry.lp_value;
882  const double lp2 = ToDouble(f(best_cut_.rhs - prod)) -
883  ToDouble(f(-entry.coeff)) *
884  (ToDouble(entry.bound_diff) - entry.lp_value);
885  if (lp2 + 1e-2 < lp1) {
886  if (!entry.Complement(&best_cut_.rhs)) continue;
887  ++total_num_final_complements_;
888  }
889  }
890  if (total_num_final_complements_ == saved) break;
891  }
892 
893  // Apply f() to the best_cut_ with a potential improvement for one Boolean:
894  //
895  // If we have a Boolean X, and a cut: terms + a * X <= b;
896  // By setting X to true or false, we have two inequalities:
897  // terms <= b if X == 0
898  // terms <= b - a if X == 1
899  // We can apply f to both inequalities and recombine:
900  // f(terms) <= f(b) * (1 - X) + f(b - a) * X
901  // Which change the final coeff of X from f(a) to [f(b) - f(b - a)].
902  // This can only improve the cut since f(b) >= f(b - a) + f(a)
903  //
904  // Note that we re-Canonicalize after our possible complementation so that the
905  // "improvement" is applied to larger lp_value first.
906  best_cut_.Canonicalize();
907  bool improved = false;
908  const IntegerValue rhs = best_cut_.rhs;
909  const IntegerValue f_rhs = f(best_cut_.rhs);
910  best_cut_.rhs = f_rhs;
911  for (CutTerm& entry : best_cut_.terms) {
912  const IntegerValue f_coeff = f(entry.coeff);
913  if (!improved && entry.bound_diff == 1 &&
914  !ProdOverflow(factor_t, CapSubI(rhs, entry.coeff))) {
915  const IntegerValue alternative = f_rhs - f(rhs - entry.coeff);
916  DCHECK_GE(alternative, f_coeff);
917  if (alternative > f_coeff) {
918  ++total_num_bumps_;
919  improved = true;
920  entry.coeff = alternative;
921  continue;
922  }
923  }
924  entry.coeff = f_coeff;
925  }
926 
927  if (!cut_builder_.ConvertToLinearConstraint(best_cut_, &cut_)) {
928  ++total_num_overflow_abort_;
929  return false;
930  }
931  return true;
932 }
933 
935  if (!VLOG_IS_ON(1)) return;
936  if (shared_stats_ == nullptr) return;
937  std::vector<std::pair<std::string, int64_t>> stats;
938  stats.push_back({"cover_cut/num_overflows", total_num_overflow_abort_});
939  stats.push_back({"cover_cut/num_lifting", total_num_lifting_});
940  stats.push_back({"cover_cut/num_implied_bounds", total_num_ibs_});
941  shared_stats_->AddStats(stats);
942 }
943 
944 // Try a simple cover heuristic.
945 // Look for violated CUT of the form: sum (UB - X) or (X - LB) >= 1.
946 int CoverCutHelper::GetCoverSize(int relevant_size, IntegerValue* rhs) {
947  if (relevant_size == 0) return 0;
948 
949  // Sorting can be slow, so we start by splitting the vector in 3 parts
950  // [can always be in cover, candidates, can never be in cover].
951  int part1 = 0;
952  const double threshold = 1.0 / static_cast<double>(relevant_size);
953  for (int i = 0; i < relevant_size;) {
954  const double dist = base_ct_.terms[i].LpDistToMaxValue();
955  if (dist < threshold) {
956  // Move to part 1.
957  std::swap(base_ct_.terms[i], base_ct_.terms[part1]);
958  ++i;
959  ++part1;
960  } else if (dist < 0.9999) {
961  // Keep in part 2.
962  ++i;
963  } else {
964  // Exclude entirely (part 3).
965  --relevant_size;
966  std::swap(base_ct_.terms[i], base_ct_.terms[relevant_size]);
967  }
968  }
969  std::sort(base_ct_.terms.begin() + part1,
970  base_ct_.terms.begin() + relevant_size,
971  [](const CutTerm& a, const CutTerm& b) {
972  const double dist_a = a.LpDistToMaxValue();
973  const double dist_b = b.LpDistToMaxValue();
974  if (dist_a == dist_b) {
975  // Prefer low coefficients if the distance is the same.
976  return a.coeff < b.coeff;
977  }
978  return dist_a < dist_b;
979  });
980 
981  double activity = 0.0;
982  int cover_size = relevant_size;
983  *rhs = base_ct_.rhs;
984  for (int i = 0; i < relevant_size; ++i) {
985  const CutTerm& term = base_ct_.terms[i];
986  activity += term.LpDistToMaxValue();
987 
988  // As an heuristic we select all the term so that the sum of distance
989  // to the upper bound is <= 1.0. If the corresponding rhs is negative, then
990  // we will have a cut of violation at least 0.0. Note that this violation
991  // can be improved by the lifting.
992  //
993  // TODO(user): experiment with different threshold (even greater than one).
994  // Or come up with an algo that incorporate the lifting into the heuristic.
995  if (activity > 0.9999) {
996  cover_size = i; // before this entry.
997  break;
998  }
999 
1000  if (!AddProductTo(-term.coeff, term.bound_diff, rhs)) {
1001  // Abort early if we run into overflow.
1002  // In that case, rhs must be negative, and we can try this cover still.
1003  cover_size = i;
1004  DCHECK_LT(*rhs, 0);
1005  break;
1006  }
1007  }
1008 
1009  // If the rhs is now negative, we have a cut.
1010  //
1011  // Note(user): past this point, now that a given "base" cover has been chosen,
1012  // we basically compute the cut (of the form sum X <= bound) with the maximum
1013  // possible violation. Note also that we lift as much as possible, so we don't
1014  // necessarily optimize for the cut efficacity though. But we do get a
1015  // stronger cut.
1016  if (*rhs >= 0) return 0;
1017  if (cover_size == 0) return 0;
1018 
1019  // Transform to a minimal cover. We want to greedily remove the largest coeff
1020  // first, so we have more chance for the "lifting" below which can increase
1021  // the cut violation. If the coeff are the same, we prefer to remove high
1022  // distance from upper bound first.
1023  std::sort(base_ct_.terms.begin(), base_ct_.terms.begin() + cover_size,
1024  [](const CutTerm& a, const CutTerm& b) {
1025  if (a.coeff == b.coeff) {
1026  return a.LpDistToMaxValue() > b.LpDistToMaxValue();
1027  }
1028  return a.coeff > b.coeff;
1029  });
1030  for (int i = 0; i < cover_size; ++i) {
1031  const CutTerm& t = base_ct_.terms[i];
1032  if (t.bound_diff * t.coeff + *rhs >= 0) continue;
1033  *rhs += t.bound_diff * t.coeff;
1034  std::swap(base_ct_.terms[i], base_ct_.terms[--cover_size]);
1035  }
1036  DCHECK_GT(cover_size, 0);
1037 
1038  return cover_size;
1039 }
1040 
1041 bool CoverCutHelper::MakeAllTermsPositive(CutData* cut) {
1042  // Make sure each coeff is positive.
1043  //
1044  // TODO(user): maybe we should do it all at once to avoid some overflow
1045  // condition.
1046  for (CutTerm& term : cut->terms) {
1047  if (term.coeff >= 0) continue;
1048  if (!term.Complement(&cut->rhs)) {
1049  ++total_num_overflow_abort_;
1050  return false;
1051  }
1052  }
1053 
1054  // We should have aborted early if the base constraint was already infeasible.
1055  CHECK_GE(cut->rhs, 0);
1056  return true;
1057 }
1058 
1059 bool CoverCutHelper::TrySimpleKnapsack(const CutData& input,
1060  ImpliedBoundsProcessor* ib_processor) {
1061  cut_.Clear();
1062  base_ct_ = input;
1063 
1064  if (ib_processor != nullptr) {
1065  cut_builder_.ClearIndices();
1066  const int old_size = static_cast<int>(base_ct_.terms.size());
1067  for (int i = 0; i < old_size; ++i) {
1068  // We only look at non-Boolean with an lp value not close to the upper
1069  // bound.
1070  const CutTerm& term = base_ct_.terms[i];
1071  if (term.bound_diff <= 1) continue;
1072  if (term.lp_value + 1e-4 > static_cast<double>(term.bound_diff.value())) {
1073  continue;
1074  }
1075 
1076  if (ib_processor->TryToExpandWithLowerImpliedbound(
1077  IntegerValue(1), i,
1078  /*complement=*/false, &base_ct_, &cut_builder_)) {
1079  ++total_num_ibs_;
1080  }
1081  }
1082  }
1083 
1084  IntegerValue rhs;
1085  const int base_size = static_cast<int>(base_ct_.terms.size());
1086  const int cover_size = GetCoverSize(base_size, &rhs);
1087  if (cover_size == 0) return false;
1088 
1089  // The cut is just that the sum of variable cannot be at their max value.
1090  base_ct_.rhs = IntegerValue(-1);
1091  IntegerValue max_coeff(0);
1092  for (int i = 0; i < cover_size; ++i) {
1093  max_coeff = std::max(max_coeff, base_ct_.terms[i].coeff);
1094  base_ct_.terms[i].coeff = IntegerValue(1);
1095  base_ct_.rhs += base_ct_.terms[i].bound_diff;
1096  }
1097  CHECK_GT(max_coeff, 0);
1098 
1099  // In case the max_coeff variable is not binary, it might be possible to
1100  // tighten the cut a bit more.
1101  //
1102  // Note(user): I never observed this on the miplib so far.
1103  if (max_coeff < -rhs) {
1104  const IntegerValue m = FloorRatio(-rhs - 1, max_coeff);
1105  rhs += max_coeff * m;
1106  base_ct_.rhs -= m;
1107  }
1108  CHECK_LT(rhs, 0);
1109 
1110  IntegerValue max_base_magnitude = max_coeff;
1111  max_base_magnitude = std::max(max_base_magnitude, IntTypeAbs(base_ct_.rhs));
1112  for (int i = cover_size; i < base_size; ++i) {
1113  max_base_magnitude = std::max(max_base_magnitude, base_ct_.terms[i].coeff);
1114  }
1115  const IntegerValue max_scaling(std::min(
1116  IntegerValue(60), FloorRatio(kMaxIntegerValue, max_base_magnitude)));
1117 
1118  // Lift all at once the variables not used in the cover.
1119  //
1120  // We have a cut of the form sum_i X_i <= b that we will lift into
1121  // sum_i scaling X_i + sum f(base_coeff_j) X_j <= b * scaling.
1122  //
1123  // Using the super additivity of f() and how we construct it, for all N >= 0
1124  // we know that: sum_j base_coeff_j X_j <= N * max_coeff + (max_coeff - slack)
1125  // implies that: sum_j f(base_coeff_j) X_j <= N * scaling.
1126  // So by inverting the implication we have:
1127  // 1/ lift > N * scaling => lift_sum > N * max_coeff + (max_coeff - slack)
1128  // We also have:
1129  // 2/ cut > b -(N+1) => original sum + (N+1) * max_coeff >= rhs + slack
1130  //
1131  // Now if we assume cut * scaling + lift > b * scaling,
1132  // by taking the largest N >=0 such that lift > N * scaling, we have
1133  // lift <= (N + 1) * scaling, so cut * scaling > (b - (N+1)) * scaling
1134  //
1135  // And adding scaling * 2/ + 1/ we prove what we want:
1136  // cut * scaling + lift > b * scaling => original_sum + lift_sum > rhs.
1137  const IntegerValue slack = -rhs;
1138  const IntegerValue remainder = max_coeff - slack;
1139  const auto f = GetSuperAdditiveRoundingFunction(remainder, max_coeff,
1140  IntegerValue(1), max_scaling);
1141 
1142  const IntegerValue scaling = f(max_coeff);
1143  if (scaling > 1) {
1144  for (int i = 0; i < cover_size; ++i) {
1145  base_ct_.terms[i].coeff *= scaling;
1146  }
1147  base_ct_.rhs *= scaling;
1148  }
1149 
1150  num_lifting_ = 0;
1151  for (int i = cover_size; i < base_size; ++i) {
1152  const IntegerValue positive_coeff = base_ct_.terms[i].coeff;
1153  const IntegerValue new_coeff = f(positive_coeff);
1154  base_ct_.terms[i].coeff = new_coeff;
1155  if (new_coeff != 0) ++num_lifting_;
1156  }
1157  total_num_lifting_ += num_lifting_;
1158 
1159  if (!cut_builder_.ConvertToLinearConstraint(base_ct_, &cut_)) {
1160  cut_.Clear();
1161  ++total_num_overflow_abort_;
1162  return false;
1163  }
1164  if (scaling > 1) DivideByGCD(&cut_);
1165  return true;
1166 }
1167 
1168 bool CoverCutHelper::TryWithLetchfordSouliLifting(
1169  const CutData& input, ImpliedBoundsProcessor* ib_processor) {
1170  cut_.Clear();
1171  base_ct_ = input;
1172 
1173  // Perform IB expansion with no restriction, all coeff should still be
1174  // positive.
1175  //
1176  // TODO(user): Merge Boolean terms that are complement of each other.
1177  if (ib_processor != nullptr) {
1178  cut_builder_.ClearIndices();
1179  const int old_size = static_cast<int>(base_ct_.terms.size());
1180  for (int i = 0; i < old_size; ++i) {
1181  if (base_ct_.terms[i].bound_diff <= 1) continue;
1182  if (ib_processor->TryToExpandWithLowerImpliedbound(
1183  IntegerValue(1), i,
1184  /*complement=*/false, &base_ct_, &cut_builder_)) {
1185  ++total_num_ibs_;
1186  }
1187  }
1188  }
1189 
1190  // TODO(user): we currently only deal with Boolean in the cover. Fix.
1191  const int num_bools =
1192  std::partition(base_ct_.terms.begin(), base_ct_.terms.end(),
1193  [](const CutTerm& t) { return t.bound_diff == 1; }) -
1194  base_ct_.terms.begin();
1195  if (num_bools == 0) return false;
1196 
1197  IntegerValue rhs;
1198  const int cover_size = GetCoverSize(num_bools, &rhs);
1199  if (cover_size == 0) return false;
1200 
1201  // Collect the weight in the cover.
1202  IntegerValue sum(0);
1203  std::vector<IntegerValue> cover_weights;
1204  for (int i = 0; i < cover_size; ++i) {
1205  CHECK_EQ(base_ct_.terms[i].bound_diff, 1);
1206  CHECK_GT(base_ct_.terms[i].coeff, 0);
1207  cover_weights.push_back(base_ct_.terms[i].coeff);
1208  sum = CapAddI(sum, base_ct_.terms[i].coeff);
1209  }
1210  if (AtMinOrMaxInt64(sum.value())) {
1211  ++total_num_overflow_abort_;
1212  return false;
1213  }
1214  CHECK_GT(sum, base_ct_.rhs);
1215 
1216  // Compute the correct threshold so that if we round down larger weights to
1217  // p/q. We have sum of the weight in cover == base_rhs.
1218  IntegerValue p(0);
1219  IntegerValue q(0);
1220  IntegerValue previous_sum(0);
1221  std::sort(cover_weights.begin(), cover_weights.end());
1222  for (int i = 0; i < cover_size; ++i) {
1223  q = IntegerValue(cover_weights.size() - i);
1224  if (previous_sum + cover_weights[i] * q > base_ct_.rhs) {
1225  p = base_ct_.rhs - previous_sum;
1226  break;
1227  }
1228  previous_sum += cover_weights[i];
1229  }
1230  CHECK_GE(q, 1);
1231 
1232  // Compute thresholds.
1233  // For the first q values, thresholds[i] is the smallest integer such that
1234  // q * threshold[i] > p * (i + 1).
1235  std::vector<IntegerValue> thresholds;
1236  for (int i = 0; i < q; ++i) {
1237  // TODO(user): compute this in an overflow-safe way.
1238  if (CapProd(p.value(), i + 1) >= std::numeric_limits<int64_t>::max() - 1) {
1239  ++total_num_overflow_abort_;
1240  return false;
1241  }
1242  thresholds.push_back(CeilRatio(p * (i + 1) + 1, q));
1243  }
1244 
1245  // For the other values, we just add the weights.
1246  std::reverse(cover_weights.begin(), cover_weights.end());
1247  for (int i = q.value(); i < cover_size; ++i) {
1248  thresholds.push_back(thresholds.back() + cover_weights[i]);
1249  }
1250  CHECK_EQ(thresholds.back(), base_ct_.rhs + 1);
1251 
1252  // Generate the cut.
1253  //
1254  // Our algo is quadratic in worst case, but large coefficients should be
1255  // rare, and in practice we don't really see this.
1256  //
1257  // Note that this work for non-Boolean since we can just "theorically" split
1258  // them as a sum of Booleans :) Probably a cleaner proof exist by just using
1259  // the super-additivity of the lifting function on [0, rhs].
1260  temp_cut_.rhs = IntegerValue(cover_size - 1);
1261  temp_cut_.terms.clear();
1262 
1263  num_lifting_ = 0;
1264  const int base_size = static_cast<int>(base_ct_.terms.size());
1265  for (int i = 0; i < base_size; ++i) {
1266  const CutTerm& term = base_ct_.terms[i];
1267  const IntegerValue coeff = term.coeff;
1268  IntegerValue cut_coeff(1);
1269  if (coeff < thresholds[0]) {
1270  if (i >= cover_size) continue;
1271  } else {
1272  // Find the largest index <= coeff.
1273  //
1274  // TODO(user): For exact multiple of p/q we can increase the coeff by 1/2.
1275  // See section in the paper on getting maximal super additive function.
1276  for (int i = 1; i < cover_size; ++i) {
1277  if (coeff < thresholds[i]) break;
1278  cut_coeff = IntegerValue(i + 1);
1279  }
1280  if (cut_coeff != 0 && i >= cover_size) ++num_lifting_;
1281  if (cut_coeff > 1 && i < cover_size) ++num_lifting_; // happen?
1282  }
1283 
1284  temp_cut_.terms.push_back(term);
1285  temp_cut_.terms.back().coeff = cut_coeff;
1286  }
1287  if (!cut_builder_.ConvertToLinearConstraint(temp_cut_, &cut_)) {
1288  cut_.Clear();
1289  ++total_num_overflow_abort_;
1290  return false;
1291  }
1292  return true;
1293 }
1294 
1296  AffineExpression x,
1297  AffineExpression y,
1298  int linearization_level,
1299  Model* model) {
1300  CutGenerator result;
1301  if (z.var != kNoIntegerVariable) result.vars.push_back(z.var);
1302  if (x.var != kNoIntegerVariable) result.vars.push_back(x.var);
1303  if (y.var != kNoIntegerVariable) result.vars.push_back(y.var);
1304 
1305  IntegerTrail* const integer_trail = model->GetOrCreate<IntegerTrail>();
1306  Trail* trail = model->GetOrCreate<Trail>();
1307 
1308  result.generate_cuts =
1309  [z, x, y, linearization_level, model, trail, integer_trail](
1311  LinearConstraintManager* manager) {
1312  if (trail->CurrentDecisionLevel() > 0 && linearization_level == 1) {
1313  return true;
1314  }
1315  const int64_t x_lb = integer_trail->LevelZeroLowerBound(x).value();
1316  const int64_t x_ub = integer_trail->LevelZeroUpperBound(x).value();
1317  const int64_t y_lb = integer_trail->LevelZeroLowerBound(y).value();
1318  const int64_t y_ub = integer_trail->LevelZeroUpperBound(y).value();
1319 
1320  // if x or y are fixed, the McCormick equations are exact.
1321  if (x_lb == x_ub || y_lb == y_ub) return true;
1322 
1323  // Check for overflow with the product of expression bounds and the
1324  // product of one expression bound times the constant part of the other
1325  // expression.
1326  const int64_t x_max_amp = std::max(std::abs(x_lb), std::abs(x_ub));
1327  const int64_t y_max_amp = std::max(std::abs(y_lb), std::abs(y_ub));
1328  constexpr int64_t kMaxSafeInteger = (int64_t{1} << 53) - 1;
1329  if (CapProd(y_max_amp, x_max_amp) > kMaxSafeInteger) return true;
1330  if (CapProd(y_max_amp, std::abs(x.constant.value())) >
1331  kMaxSafeInteger) {
1332  return true;
1333  }
1334  if (CapProd(x_max_amp, std::abs(y.constant.value())) >
1335  kMaxSafeInteger) {
1336  return true;
1337  }
1338 
1339  const double x_lp_value = x.LpValue(lp_values);
1340  const double y_lp_value = y.LpValue(lp_values);
1341  const double z_lp_value = z.LpValue(lp_values);
1342 
1343  // TODO(user): As the bounds change monotonically, these cuts
1344  // dominate any previous one. try to keep a reference to the cut and
1345  // replace it. Alternatively, add an API for a level-zero bound change
1346  // callback.
1347 
1348  // Cut -z + x_coeff * x + y_coeff* y <= rhs
1349  auto try_add_above_cut = [&](int64_t x_coeff, int64_t y_coeff,
1350  int64_t rhs) {
1351  if (-z_lp_value + x_lp_value * x_coeff + y_lp_value * y_coeff >=
1352  rhs + kMinCutViolation) {
1354  /*ub=*/IntegerValue(rhs));
1355  cut.AddTerm(z, IntegerValue(-1));
1356  if (x_coeff != 0) cut.AddTerm(x, IntegerValue(x_coeff));
1357  if (y_coeff != 0) cut.AddTerm(y, IntegerValue(y_coeff));
1358  manager->AddCut(cut.Build(), "PositiveProduct", lp_values);
1359  }
1360  };
1361 
1362  // Cut -z + x_coeff * x + y_coeff* y >= rhs
1363  auto try_add_below_cut = [&](int64_t x_coeff, int64_t y_coeff,
1364  int64_t rhs) {
1365  if (-z_lp_value + x_lp_value * x_coeff + y_lp_value * y_coeff <=
1366  rhs - kMinCutViolation) {
1367  LinearConstraintBuilder cut(model, /*lb=*/IntegerValue(rhs),
1368  /*ub=*/kMaxIntegerValue);
1369  cut.AddTerm(z, IntegerValue(-1));
1370  if (x_coeff != 0) cut.AddTerm(x, IntegerValue(x_coeff));
1371  if (y_coeff != 0) cut.AddTerm(y, IntegerValue(y_coeff));
1372  manager->AddCut(cut.Build(), "PositiveProduct", lp_values);
1373  }
1374  };
1375 
1376  // McCormick relaxation of bilinear constraints. These 4 cuts are the
1377  // exact facets of the x * y polyhedron for a bounded x and y.
1378  //
1379  // Each cut correspond to plane that contains two of the line
1380  // (x=x_lb), (x=x_ub), (y=y_lb), (y=y_ub). The easiest to
1381  // understand them is to draw the x*y curves and see the 4
1382  // planes that correspond to the convex hull of the graph.
1383  try_add_above_cut(y_lb, x_lb, x_lb * y_lb);
1384  try_add_above_cut(y_ub, x_ub, x_ub * y_ub);
1385  try_add_below_cut(y_ub, x_lb, x_lb * y_ub);
1386  try_add_below_cut(y_lb, x_ub, x_ub * y_lb);
1387  return true;
1388  };
1389 
1390  return result;
1391 }
1392 
1394  AffineExpression square,
1395  IntegerValue x_lb,
1396  IntegerValue x_ub, Model* model) {
1397  const IntegerValue above_slope = x_ub + x_lb;
1399  -x_lb * x_ub);
1400  above_hyperplan.AddTerm(square, 1);
1401  above_hyperplan.AddTerm(x, IntegerValue(-above_slope));
1402  return above_hyperplan.Build();
1403 }
1404 
1406  AffineExpression square,
1407  IntegerValue x_value,
1408  Model* model) {
1409  const IntegerValue below_slope = 2 * x_value + 1;
1410  LinearConstraintBuilder below_hyperplan(model, -x_value - x_value * x_value,
1412  below_hyperplan.AddTerm(square, 1);
1413  below_hyperplan.AddTerm(x, -below_slope);
1414  return below_hyperplan.Build();
1415 }
1416 
1418  int linearization_level, Model* model) {
1419  CutGenerator result;
1420  if (x.var != kNoIntegerVariable) result.vars.push_back(x.var);
1421  if (y.var != kNoIntegerVariable) result.vars.push_back(y.var);
1422 
1423  Trail* trail = model->GetOrCreate<Trail>();
1424  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
1425  result.generate_cuts =
1426  [y, x, linearization_level, trail, integer_trail, model](
1428  LinearConstraintManager* manager) {
1429  if (trail->CurrentDecisionLevel() > 0 && linearization_level == 1) {
1430  return true;
1431  }
1432  const IntegerValue x_ub = integer_trail->LevelZeroUpperBound(x);
1433  const IntegerValue x_lb = integer_trail->LevelZeroLowerBound(x);
1434  DCHECK_GE(x_lb, 0);
1435 
1436  if (x_lb == x_ub) return true;
1437 
1438  // Check for potential overflows.
1439  if (x_ub > (int64_t{1} << 31)) return true;
1440  DCHECK_GE(x_lb, 0);
1441  manager->AddCut(ComputeHyperplanAboveSquare(x, y, x_lb, x_ub, model),
1442  "SquareUpper", lp_values);
1443 
1444  const IntegerValue x_floor =
1445  static_cast<int64_t>(std::floor(x.LpValue(lp_values)));
1446  manager->AddCut(ComputeHyperplanBelowSquare(x, y, x_floor, model),
1447  "SquareLower", lp_values);
1448  return true;
1449  };
1450 
1451  return result;
1452 }
1453 
1454 ImpliedBoundsProcessor::BestImpliedBoundInfo
1455 ImpliedBoundsProcessor::GetCachedImpliedBoundInfo(IntegerVariable var) const {
1456  auto it = cache_.find(var);
1457  if (it != cache_.end()) {
1458  BestImpliedBoundInfo result = it->second;
1459  if (result.bool_var == kNoIntegerVariable) return BestImpliedBoundInfo();
1460  if (integer_trail_->IsFixed(result.bool_var)) return BestImpliedBoundInfo();
1461  return result;
1462  }
1463  return BestImpliedBoundInfo();
1464 }
1465 
1467 ImpliedBoundsProcessor::ComputeBestImpliedBound(
1468  IntegerVariable var,
1470  auto it = cache_.find(var);
1471  if (it != cache_.end()) return it->second;
1472  BestImpliedBoundInfo result;
1473  double result_slack_lp_value = std::numeric_limits<double>::infinity();
1474  const IntegerValue lb = integer_trail_->LevelZeroLowerBound(var);
1475  for (const ImpliedBoundEntry& entry :
1476  implied_bounds_->GetImpliedBounds(var)) {
1477  // Only process entries with a Boolean variable currently part of the LP
1478  // we are considering for this cut.
1479  //
1480  // TODO(user): the more we use cuts, the less it make sense to have a
1481  // lot of small independent LPs.
1482  if (!lp_vars_.contains(PositiveVariable(entry.literal_view))) {
1483  continue;
1484  }
1485 
1486  // The equation is X = lb + diff * Bool + Slack where Bool is in [0, 1]
1487  // and slack in [0, ub - lb].
1488  const IntegerValue diff = entry.lower_bound - lb;
1489  CHECK_GE(diff, 0);
1490  const double bool_lp_value = entry.is_positive
1491  ? lp_values[entry.literal_view]
1492  : 1.0 - lp_values[entry.literal_view];
1493  const double slack_lp_value =
1494  lp_values[var] - ToDouble(lb) - bool_lp_value * ToDouble(diff);
1495 
1496  // If the implied bound equation is not respected, we just add it
1497  // to implied_bound_cuts, and skip the entry for now.
1498  if (slack_lp_value < -1e-4) {
1499  LinearConstraint ib_cut;
1500  ib_cut.lb = kMinIntegerValue;
1501  std::vector<std::pair<IntegerVariable, IntegerValue>> terms;
1502  if (entry.is_positive) {
1503  // X >= Indicator * (bound - lb) + lb
1504  terms.push_back({entry.literal_view, diff});
1505  terms.push_back({var, IntegerValue(-1)});
1506  ib_cut.ub = -lb;
1507  } else {
1508  // X >= -Indicator * (bound - lb) + bound
1509  terms.push_back({entry.literal_view, -diff});
1510  terms.push_back({var, IntegerValue(-1)});
1511  ib_cut.ub = -entry.lower_bound;
1512  }
1513  CleanTermsAndFillConstraint(&terms, &ib_cut);
1514  ib_cut_pool_.AddCut(std::move(ib_cut), "IB", lp_values);
1515  continue;
1516  }
1517 
1518  // We look for tight implied bounds, and amongst the tightest one, we
1519  // prefer larger coefficient in front of the Boolean.
1520  if (slack_lp_value + 1e-4 < result_slack_lp_value ||
1521  (slack_lp_value < result_slack_lp_value + 1e-4 &&
1522  entry.lower_bound > result.implied_bound)) {
1523  result_slack_lp_value = slack_lp_value;
1524  result.var_lp_value = lp_values[var];
1525  result.bool_lp_value = bool_lp_value;
1526  result.implied_bound = entry.lower_bound;
1527  result.is_positive = entry.is_positive;
1528  result.bool_var = entry.literal_view;
1529  }
1530  }
1531  cache_[var] = result;
1532  return result;
1533 }
1534 
1535 void ImpliedBoundsProcessor::RecomputeCacheAndSeparateSomeImpliedBoundCuts(
1537  cache_.clear();
1538  for (const IntegerVariable var :
1539  implied_bounds_->VariablesWithImpliedBounds()) {
1540  if (!lp_vars_.contains(PositiveVariable(var))) continue;
1541  ComputeBestImpliedBound(var, lp_values);
1542  }
1543 }
1544 
1545 // Important: The cut_builder_ must have been reset.
1546 bool ImpliedBoundsProcessor::TryToExpandWithLowerImpliedbound(
1547  IntegerValue factor_t, int i, bool complement, CutData* cut,
1548  CutDataBuilder* builder) {
1549  CutTerm& term = cut->terms[i];
1550 
1551  // We only want to expand non-Boolean and non-slack term!
1552  if (term.bound_diff <= 1) return false;
1553  if (!term.IsSimple()) return false;
1554  CHECK_EQ(IntTypeAbs(term.expr_coeffs[0]), 1);
1555 
1556  // Try lower bounded direction for implied bound.
1557  // This kind should always be beneficial if it exists:
1558  //
1559  // Because X = bound_diff * B + S
1560  // We can replace coeff * X by the expression before applying f:
1561  // = f(coeff * bound_diff) * B + f(coeff) * [X - bound_diff * B]
1562  // = f(coeff) * X + (f(coeff * bound_diff) - f(coeff) * bound_diff] * B
1563  // So we can "lift" B into the cut with a non-negative coefficient.
1564  //
1565  // Note that this lifting is really the same as if we used that implied
1566  // bound before since in f(coeff * bound_diff) * B + f(coeff) * S, if we
1567  // replace S by its value [X - bound_diff * B] we get the same result.
1568  //
1569  // TODO(user): Ignore if bound_diff == 1 ? But we can still merge B with
1570  // another entry if it exists, so it can still be good in this case.
1571  //
1572  // TODO(user): Only do it if coeff_b > 0 ? But again we could still merge
1573  // B with an existing Boolean for a better cut even if coeff_b == 0.
1574  const IntegerVariable ib_var = term.expr_coeffs[0] > 0
1575  ? term.expr_vars[0]
1576  : NegationOf(term.expr_vars[0]);
1578  GetCachedImpliedBoundInfo(ib_var);
1579  const IntegerValue lb = -term.expr_offset;
1580  const IntegerValue bound_diff = info.implied_bound - lb;
1581  if (bound_diff <= 0) return false;
1582  if (info.bool_var == kNoIntegerVariable) return false;
1583  if (ProdOverflow(factor_t, CapProdI(term.coeff, bound_diff))) return false;
1584 
1585  // We have X = info.diff * Boolean + slack.
1586  CutTerm bool_term;
1587  bool_term.coeff = term.coeff * bound_diff;
1588  bool_term.expr_vars[0] = info.bool_var;
1589  bool_term.expr_coeffs[1] = 0;
1590  bool_term.bound_diff = IntegerValue(1);
1591  bool_term.lp_value = info.bool_lp_value;
1592  if (info.is_positive) {
1593  bool_term.expr_coeffs[0] = IntegerValue(1);
1594  bool_term.expr_offset = IntegerValue(0);
1595  } else {
1596  bool_term.expr_coeffs[0] = IntegerValue(-1);
1597  bool_term.expr_offset = IntegerValue(1);
1598  }
1599 
1600  // Create slack.
1601  // The expression is term.exp - bound_diff * bool_term
1602  // The variable shouldn't be the same.
1603  DCHECK_NE(term.expr_vars[0], bool_term.expr_vars[0]);
1604  CutTerm slack_term;
1605  slack_term.expr_vars[0] = term.expr_vars[0];
1606  slack_term.expr_coeffs[0] = term.expr_coeffs[0];
1607  slack_term.expr_vars[1] = bool_term.expr_vars[0];
1608  slack_term.expr_coeffs[1] = -bound_diff * bool_term.expr_coeffs[0];
1609  slack_term.expr_offset =
1610  term.expr_offset - bound_diff * bool_term.expr_offset;
1611 
1612  slack_term.lp_value = info.SlackLpValue(lb);
1613  slack_term.coeff = term.coeff;
1614  slack_term.bound_diff = term.bound_diff;
1615 
1616  // It should be good to use IB, but sometime we have things like
1617  // 7.3 = 2 * bool@1 + 5.3 and the expanded Boolean is at its upper bound.
1618  // It is always good to complement such variable.
1619  //
1620  // Note that here we do more and just complement anything closer to UB.
1621  //
1622  // TODO(user): Because of merges, we might have entry with a coefficient of
1623  // zero than are not useful. Remove them.
1624  if (complement) {
1625  if (bool_term.lp_value > 0.5) {
1626  bool_term.Complement(&cut->rhs);
1627  }
1628  if (slack_term.lp_value >
1629  0.5 * static_cast<double>(slack_term.bound_diff.value())) {
1630  slack_term.Complement(&cut->rhs);
1631  }
1632  }
1633 
1634  term = slack_term;
1635  builder->AddOrMergeTerm(bool_term, factor_t, cut);
1636  return true;
1637 }
1638 
1639 std::string SingleNodeFlow::DebugString() const {
1640  return absl::StrCat("#in:", in_flow.size(), " #out:", out_flow.size(),
1641  " demand:", demand.value(), " #bool:", num_bool,
1642  " #lb:", num_to_lb, " #ub:", num_to_ub);
1643 }
1644 
1645 bool FlowCoverCutHelper::TryXminusLB(IntegerVariable var, double lp_value,
1646  IntegerValue lb, IntegerValue ub,
1647  IntegerValue coeff,
1648  ImpliedBoundsProcessor* ib_helper,
1649  SingleNodeFlow* result) const {
1652  if (ib.bool_var == kNoIntegerVariable) return false;
1653  if (ib.implied_bound != -lb) return false;
1654 
1655  // We have -var >= (ub - lb) bool - ub;
1656  // so (var - lb) <= -(ub - lb) * bool + ub - lb;
1657  // and (var - lb) <= bound_diff * (1 - bool).
1658  FlowInfo info;
1659  if (ib.is_positive) {
1660  info.bool_lp_value = 1 - ib.bool_lp_value;
1661  info.bool_expr.var = ib.bool_var;
1662  info.bool_expr.coeff = -1;
1663  info.bool_expr.constant = 1;
1664  } else {
1665  info.bool_lp_value = ib.bool_lp_value;
1666  info.bool_expr.var = ib.bool_var;
1667  info.bool_expr.coeff = 1;
1668  }
1669  info.capacity = IntTypeAbs(coeff) * (ub - lb);
1670  info.flow_lp_value = ToDouble(IntTypeAbs(coeff)) * (lp_value - ToDouble(lb));
1671  info.flow_expr.var = var;
1672  info.flow_expr.coeff = IntTypeAbs(coeff);
1673  info.flow_expr.constant = -lb * IntTypeAbs(coeff);
1674 
1675  // We use (var - lb) so sign is preserved
1676  result->demand -= coeff * lb;
1677  if (coeff > 0) {
1678  result->in_flow.push_back(info);
1679  } else {
1680  result->out_flow.push_back(info);
1681  }
1682  return true;
1683 }
1684 
1685 bool FlowCoverCutHelper::TryUBminusX(IntegerVariable var, double lp_value,
1686  IntegerValue lb, IntegerValue ub,
1687  IntegerValue coeff,
1688  ImpliedBoundsProcessor* ib_helper,
1689  SingleNodeFlow* result) const {
1690  const ImpliedBoundsProcessor::BestImpliedBoundInfo ib =
1691  ib_helper->GetCachedImpliedBoundInfo(var);
1692  if (ib.bool_var == kNoIntegerVariable) return false;
1693  if (ib.implied_bound != ub) return false;
1694 
1695  // We have var >= (ub - lb) bool + lb.
1696  // so ub - var <= ub - (ub - lb) * bool - lb.
1697  // and (ub - var) <= bound_diff * (1 - bool).
1698  FlowInfo info;
1699  if (ib.is_positive) {
1700  info.bool_lp_value = 1 - ib.bool_lp_value;
1701  info.bool_expr.var = ib.bool_var;
1702  info.bool_expr.coeff = -1;
1703  info.bool_expr.constant = 1;
1704  } else {
1705  info.bool_lp_value = ib.bool_lp_value;
1706  info.bool_expr.var = ib.bool_var;
1707  info.bool_expr.coeff = 1;
1708  }
1709  info.capacity = IntTypeAbs(coeff) * (ub - lb);
1710  info.flow_lp_value = ToDouble(IntTypeAbs(coeff)) * (ToDouble(ub) - lp_value);
1711  info.flow_expr.var = var;
1712  info.flow_expr.coeff = -IntTypeAbs(coeff);
1713  info.flow_expr.constant = ub * IntTypeAbs(coeff);
1714 
1715  // We reverse the sign because we use (ub - var) here.
1716  // So coeff * var = -coeff * (ub - var) + coeff * ub;
1717  result->demand -= coeff * ub;
1718  if (coeff > 0) {
1719  result->out_flow.push_back(info);
1720  } else {
1721  result->in_flow.push_back(info);
1722  }
1723  return true;
1724 }
1725 
1726 bool FlowCoverCutHelper::ComputeFlowCoverRelaxationAndGenerateCut(
1727  const LinearConstraint& base_ct,
1729  IntegerTrail* integer_trail, ImpliedBoundsProcessor* ib_helper) {
1730  if (!ComputeFlowCoverRelaxation(base_ct, lp_values, &snf_, integer_trail,
1731  ib_helper)) {
1732  return false;
1733  }
1734  return GenerateCut(snf_);
1735 }
1736 
1737 bool FlowCoverCutHelper::ComputeFlowCoverRelaxation(
1738  const LinearConstraint& base_ct,
1740  SingleNodeFlow* snf, IntegerTrail* integer_trail,
1741  ImpliedBoundsProcessor* ib_helper) {
1742  snf->clear();
1743  snf->demand = base_ct.ub;
1744  const int size = base_ct.vars.size();
1745  for (int i = 0; i < size; ++i) {
1746  // We can either use (X - LB) or (UB - X) for a variable in [0, capacity].
1747  const IntegerVariable var = base_ct.vars[i];
1748  const IntegerValue coeff = base_ct.coeffs[i];
1749 
1750  // Hack: abort if coefficient in the base constraint are too large.
1751  if (IntTypeAbs(coeff) > 1'000'000) return false;
1752 
1753  const IntegerValue lb = integer_trail->LevelZeroLowerBound(var);
1754  const IntegerValue ub = integer_trail->LevelZeroUpperBound(var);
1755  const IntegerValue capacity(
1756  CapProd(IntTypeAbs(coeff).value(), (ub - lb).value()));
1757  if (capacity >= kMaxIntegerValue) return false;
1758  if (lb == ub) {
1759  // Fixed variable shouldn't really appear here.
1760  snf->demand -= coeff * lb;
1761  continue;
1762  }
1763 
1764  // We have a Boolean, this is an easy case.
1765  if (ub - lb == 1) {
1766  ++snf->num_bool;
1767  FlowInfo info;
1768  info.bool_lp_value = (lp_values[var] - ToDouble(lb));
1769  info.capacity = capacity;
1770  info.bool_expr.var = var;
1771  info.bool_expr.coeff = 1;
1772  info.bool_expr.constant = -lb;
1773 
1774  info.flow_lp_value = ToDouble(capacity) * info.bool_lp_value;
1775  info.flow_expr.var = var;
1776  info.flow_expr.coeff = info.capacity;
1777  info.flow_expr.constant = -lb * info.capacity;
1778 
1779  // coeff * x = coeff * (x - lb) + coeff * lb;
1780  snf->demand -= coeff * lb;
1781  if (coeff > 0) {
1782  snf->in_flow.push_back(info);
1783  } else {
1784  snf->out_flow.push_back(info);
1785  }
1786  continue;
1787  }
1788 
1789  // TODO(user): Improve our logic to decide what implied bounds to use. We
1790  // rely on the best implied bounds, not necessarily one implying var at its
1791  // level zero bound like we need here.
1792  const double lp = lp_values[var];
1793  const bool prefer_lb = (lp - ToDouble(lb)) > (ToDouble(ub) - lp);
1794  if (prefer_lb) {
1795  if (TryXminusLB(var, lp, lb, ub, coeff, ib_helper, snf)) {
1796  ++snf->num_to_lb;
1797  continue;
1798  }
1799  if (TryUBminusX(var, lp, lb, ub, coeff, ib_helper, snf)) {
1800  ++snf->num_to_ub;
1801  continue;
1802  }
1803  } else {
1804  if (TryUBminusX(var, lp, lb, ub, coeff, ib_helper, snf)) {
1805  ++snf->num_to_ub;
1806  continue;
1807  }
1808  if (TryXminusLB(var, lp, lb, ub, coeff, ib_helper, snf)) {
1809  ++snf->num_to_lb;
1810  continue;
1811  }
1812  }
1813 
1814  // Abort.
1815  // TODO(user): Technically we can always use a arc usage Boolean fixed to 1.
1816  return false;
1817  }
1818 
1819  return true;
1820 }
1821 
1822 // Reference: "Lifted flow cover inequalities for mixed 0-1 integer programs".
1823 // Zonghao Gu, George L. Nemhauser, Martin W.P. Savelsbergh. 1999.
1824 bool FlowCoverCutHelper::GenerateCut(const SingleNodeFlow& data) {
1825  if (data.empty()) return false;
1826  const double tolerance = 1e-2;
1827 
1828  // We are looking for two subsets CI (in-flow subset) and CO (out-flow subset)
1829  // so that sum_CI capa - sum_CO capa = demand + slack, slack > 0.
1830  //
1831  // Moreover we want to maximize sum_CI bool_lp_value + sum_CO bool_lp_value.
1832  std::vector<bool> in_cover(data.in_flow.size(), false);
1833  std::vector<bool> out_cover(data.out_flow.size(), false);
1834 
1835  // Start by selecting all the possible in_flow (except low bool value) and
1836  // all the out_flow with a bool value close to one.
1837  IntegerValue slack;
1838  {
1839  IntegerValue sum_in = 0;
1840  IntegerValue sum_out = 0;
1841  for (int i = 0; i < data.in_flow.size(); ++i) {
1842  const FlowInfo& info = data.in_flow[i];
1843  if (info.bool_lp_value > tolerance) {
1844  in_cover[i] = true;
1845  sum_in += info.capacity;
1846  }
1847  }
1848  for (int i = 0; i < data.out_flow.size(); ++i) {
1849  const FlowInfo& info = data.out_flow[i];
1850  if (info.bool_lp_value > 1 - tolerance) {
1851  out_cover[i] = true;
1852  sum_out += info.capacity;
1853  }
1854  }
1855 
1856  // This is the best slack we can hope for.
1857  slack = sum_in - sum_out - data.demand;
1858  }
1859  if (slack <= 0) return false;
1860 
1861  // Now greedily remove item from the in_cover and add_item to the out_cover
1862  // as long as we have remaining slack. We prefer item with a high score an
1863  // low slack variation.
1864  //
1865  // Note that this is just the classic greedy heuristic of a knapsack problem.
1866  if (slack > 1) {
1867  struct Item {
1868  bool correspond_to_in_flow;
1869  int index;
1870  double score;
1871  };
1872  std::vector<Item> actions;
1873  for (int i = 0; i < data.in_flow.size(); ++i) {
1874  if (!in_cover[i]) continue;
1875  const FlowInfo& info = data.in_flow[i];
1876  if (info.bool_lp_value > 1 - tolerance) continue; // Do not remove these.
1877  actions.push_back(
1878  {true, i, (1 - info.bool_lp_value) / ToDouble(info.capacity)});
1879  }
1880  for (int i = 0; i < data.out_flow.size(); ++i) {
1881  if (out_cover[i]) continue;
1882  const FlowInfo& info = data.out_flow[i];
1883  if (info.bool_lp_value < tolerance) continue; // Do not add these.
1884  actions.push_back(
1885  {false, i, info.bool_lp_value / ToDouble(info.capacity)});
1886  }
1887 
1888  // Sort by decreasing score.
1889  std::sort(actions.begin(), actions.end(),
1890  [](const Item& a, const Item& b) { return a.score > b.score; });
1891 
1892  // Greedily remove/add item as long as we have slack.
1893  for (const Item& item : actions) {
1894  if (item.correspond_to_in_flow) {
1895  const IntegerValue delta = data.in_flow[item.index].capacity;
1896  if (delta >= slack) continue;
1897  slack -= delta;
1898  in_cover[item.index] = false;
1899  } else {
1900  const IntegerValue delta = data.out_flow[item.index].capacity;
1901  if (delta >= slack) continue;
1902  slack -= delta;
1903  out_cover[item.index] = true;
1904  }
1905  }
1906  }
1907 
1908  // The non-lifted simple generalized flow cover inequality (SGFCI) cut will be
1909  // demand - sum_CI flow_i - sum_CI++ (capa_i - slack)(1 - bool_i)
1910  // + sum_CO capa_i + sum_L- slack * bool_i + sum_L-- flow_i >=0
1911  //
1912  // Where CI++ are the arc with capa > slack in CI.
1913  // And L is O \ CO. L- arc with capa > slack and L-- the other.
1914  //
1915  // TODO(user): Also try to generate the extended generalized flow cover
1916  // inequality (EGFCI).
1917  CHECK_GT(slack, 0);
1918 
1919  // For display only.
1920  slack_ = slack;
1921  num_in_ignored_ = 0;
1922  num_in_flow_ = 0;
1923  num_in_bin_ = 0;
1924  num_out_capa_ = 0;
1925  num_out_flow_ = 0;
1926  num_out_bin_ = 0;
1927 
1928  cut_builder_.Clear();
1929  for (int i = 0; i < data.in_flow.size(); ++i) {
1930  const FlowInfo& info = data.in_flow[i];
1931  if (!in_cover[i]) {
1932  num_in_ignored_++;
1933  continue;
1934  }
1935  num_in_flow_++;
1936  cut_builder_.AddTerm(info.flow_expr, -1);
1937  if (info.capacity > slack) {
1938  num_in_bin_++;
1939  const IntegerValue coeff = info.capacity - slack;
1940  cut_builder_.AddConstant(-coeff);
1941  cut_builder_.AddTerm(info.bool_expr, coeff);
1942  }
1943  }
1944  for (int i = 0; i < data.out_flow.size(); ++i) {
1945  const FlowInfo& info = data.out_flow[i];
1946  if (out_cover[i]) {
1947  num_out_capa_++;
1948  cut_builder_.AddConstant(info.capacity);
1949  } else if (info.capacity > slack) {
1950  num_out_bin_++;
1951  cut_builder_.AddTerm(info.bool_expr, slack);
1952  } else {
1953  num_out_flow_++;
1954  cut_builder_.AddTerm(info.flow_expr, 1);
1955  }
1956  }
1957 
1958  // TODO(user): Lift the cut.
1959  cut_ = cut_builder_.BuildConstraint(-data.demand, kMaxIntegerValue);
1960  return true;
1961 }
1962 
1963 void SumOfAllDiffLowerBounder::Clear() {
1964  min_values_.clear();
1965  expr_mins_.clear();
1966 }
1967 
1968 void SumOfAllDiffLowerBounder::Add(const AffineExpression& expr, int num_exprs,
1969  const IntegerTrail& integer_trail) {
1970  expr_mins_.push_back(integer_trail.LevelZeroLowerBound(expr).value());
1971 
1972  if (integer_trail.IsFixed(expr)) {
1973  min_values_.insert(integer_trail.FixedValue(expr));
1974  } else {
1975  if (expr.coeff > 0) {
1976  int count = 0;
1977  for (const IntegerValue value :
1978  integer_trail.InitialVariableDomain(expr.var).Values()) {
1979  min_values_.insert(expr.ValueAt(value));
1980  if (++count >= num_exprs) break;
1981  }
1982  } else {
1983  int count = 0;
1984  for (const IntegerValue value :
1985  integer_trail.InitialVariableDomain(expr.var).Negation().Values()) {
1986  min_values_.insert(-expr.ValueAt(value));
1987  if (++count >= num_exprs) break;
1988  }
1989  }
1990  }
1991 }
1992 
1993 IntegerValue SumOfAllDiffLowerBounder::SumOfMinDomainValues() {
1994  int count = 0;
1995  IntegerValue sum = 0;
1996  for (const IntegerValue value : min_values_) {
1997  sum += value;
1998  if (++count >= expr_mins_.size()) return sum;
1999  }
2000  return sum;
2001 }
2002 
2003 IntegerValue SumOfAllDiffLowerBounder::SumOfDifferentMins() {
2004  std::sort(expr_mins_.begin(), expr_mins_.end());
2005  IntegerValue tmp_value = kMinIntegerValue;
2006  IntegerValue result = 0;
2007  for (const IntegerValue value : expr_mins_) {
2008  // Make sure values are different.
2009  tmp_value = std::max(tmp_value + 1, value);
2010  result += tmp_value;
2011  }
2012  return result;
2013 }
2014 
2015 IntegerValue SumOfAllDiffLowerBounder::GetBestLowerBound(std::string& suffix) {
2016  const IntegerValue domain_bound = SumOfMinDomainValues();
2017  const IntegerValue alldiff_bound = SumOfDifferentMins();
2018  if (domain_bound > alldiff_bound) {
2019  suffix = "d";
2020  return domain_bound;
2021  }
2022  suffix = alldiff_bound > domain_bound ? "a" : "e";
2023  return alldiff_bound;
2024 }
2025 
2026 namespace {
2027 
2028 void TryToGenerateAllDiffCut(
2029  const std::vector<std::pair<double, AffineExpression>>& sorted_exprs_lp,
2030  const IntegerTrail& integer_trail,
2032  TopNCuts& top_n_cuts, Model* model) {
2033  const int num_exprs = sorted_exprs_lp.size();
2034 
2035  std::vector<AffineExpression> current_set_exprs;
2036  SumOfAllDiffLowerBounder diff_mins;
2037  SumOfAllDiffLowerBounder negated_diff_maxes;
2038 
2039  double sum = 0.0;
2040 
2041  for (const auto& [expr_lp, expr] : sorted_exprs_lp) {
2042  sum += expr_lp;
2043  diff_mins.Add(expr, num_exprs, integer_trail);
2044  negated_diff_maxes.Add(expr.Negated(), num_exprs, integer_trail);
2045  current_set_exprs.push_back(expr);
2046  CHECK_EQ(current_set_exprs.size(), diff_mins.size());
2047  CHECK_EQ(current_set_exprs.size(), negated_diff_maxes.size());
2048  std::string min_suffix;
2049  const IntegerValue required_min_sum =
2050  diff_mins.GetBestLowerBound(min_suffix);
2051  std::string max_suffix;
2052  const IntegerValue required_max_sum =
2053  -negated_diff_maxes.GetBestLowerBound(max_suffix);
2054  if (sum < ToDouble(required_min_sum) - kMinCutViolation ||
2055  sum > ToDouble(required_max_sum) + kMinCutViolation) {
2056  LinearConstraintBuilder cut(model, required_min_sum, required_max_sum);
2057  for (AffineExpression expr : current_set_exprs) {
2058  cut.AddTerm(expr, IntegerValue(1));
2059  }
2060  top_n_cuts.AddCut(cut.Build(),
2061  absl::StrCat("AllDiff_", min_suffix, max_suffix),
2062  lp_values);
2063  // NOTE: We can extend the current set but it is more helpful to generate
2064  // the cut on a different set of variables so we reset the counters.
2065  sum = 0.0;
2066  current_set_exprs.clear();
2067  diff_mins.Clear();
2068  negated_diff_maxes.Clear();
2069  }
2070  }
2071 }
2072 
2073 } // namespace
2074 
2076  const std::vector<AffineExpression>& exprs, Model* model) {
2077  CutGenerator result;
2078  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
2079 
2080  for (const AffineExpression& expr : exprs) {
2081  if (!integer_trail->IsFixed(expr)) {
2082  result.vars.push_back(expr.var);
2083  }
2084  }
2086 
2087  Trail* trail = model->GetOrCreate<Trail>();
2088  result.generate_cuts =
2089  [exprs, integer_trail, trail, model](
2091  LinearConstraintManager* manager) {
2092  // These cuts work at all levels but the generator adds too many cuts on
2093  // some instances and degrade the performance so we only use it at level
2094  // 0.
2095  if (trail->CurrentDecisionLevel() > 0) return true;
2096  std::vector<std::pair<double, AffineExpression>> sorted_exprs;
2097  for (const AffineExpression expr : exprs) {
2098  if (integer_trail->LevelZeroLowerBound(expr) ==
2099  integer_trail->LevelZeroUpperBound(expr)) {
2100  continue;
2101  }
2102  sorted_exprs.push_back(std::make_pair(expr.LpValue(lp_values), expr));
2103  }
2104 
2105  TopNCuts top_n_cuts(5);
2106  std::sort(sorted_exprs.begin(), sorted_exprs.end(),
2107  [](std::pair<double, AffineExpression>& a,
2108  const std::pair<double, AffineExpression>& b) {
2109  return a.first < b.first;
2110  });
2111  TryToGenerateAllDiffCut(sorted_exprs, *integer_trail, lp_values,
2112  top_n_cuts, model);
2113  // Other direction.
2114  std::reverse(sorted_exprs.begin(), sorted_exprs.end());
2115  TryToGenerateAllDiffCut(sorted_exprs, *integer_trail, lp_values,
2116  top_n_cuts, model);
2117  top_n_cuts.TransferToManager(lp_values, manager);
2118  return true;
2119  };
2120  VLOG(2) << "Created all_diff cut generator of size: " << exprs.size();
2121  return result;
2122 }
2123 
2124 namespace {
2125 // Returns max((w2i - w1i)*Li, (w2i - w1i)*Ui).
2126 IntegerValue MaxCornerDifference(const IntegerVariable var,
2127  const IntegerValue w1_i,
2128  const IntegerValue w2_i,
2129  const IntegerTrail& integer_trail) {
2130  const IntegerValue lb = integer_trail.LevelZeroLowerBound(var);
2131  const IntegerValue ub = integer_trail.LevelZeroUpperBound(var);
2132  return std::max((w2_i - w1_i) * lb, (w2_i - w1_i) * ub);
2133 }
2134 
2135 // This is the coefficient of zk in the cut, where k = max_index.
2136 // MPlusCoefficient_ki = max((wki - wI(i)i) * Li,
2137 // (wki - wI(i)i) * Ui)
2138 // = max corner difference for variable i,
2139 // target expr I(i), max expr k.
2140 // The coefficient of zk is Sum(i=1..n)(MPlusCoefficient_ki) + bk
2141 IntegerValue MPlusCoefficient(
2142  const std::vector<IntegerVariable>& x_vars,
2143  const std::vector<LinearExpression>& exprs,
2144  const absl::StrongVector<IntegerVariable, int>& variable_partition,
2145  const int max_index, const IntegerTrail& integer_trail) {
2146  IntegerValue coeff = exprs[max_index].offset;
2147  // TODO(user): This algo is quadratic since GetCoefficientOfPositiveVar()
2148  // is linear. This can be optimized (better complexity) if needed.
2149  for (const IntegerVariable var : x_vars) {
2150  const int target_index = variable_partition[var];
2151  if (max_index != target_index) {
2152  coeff += MaxCornerDifference(
2153  var, GetCoefficientOfPositiveVar(var, exprs[target_index]),
2154  GetCoefficientOfPositiveVar(var, exprs[max_index]), integer_trail);
2155  }
2156  }
2157  return coeff;
2158 }
2159 
2160 // Compute the value of
2161 // rhs = wI(i)i * xi + Sum(k=1..d)(MPlusCoefficient_ki * zk)
2162 // for variable xi for given target index I(i).
2163 double ComputeContribution(
2164  const IntegerVariable xi_var, const std::vector<IntegerVariable>& z_vars,
2165  const std::vector<LinearExpression>& exprs,
2167  const IntegerTrail& integer_trail, const int target_index) {
2168  CHECK_GE(target_index, 0);
2169  CHECK_LT(target_index, exprs.size());
2170  const LinearExpression& target_expr = exprs[target_index];
2171  const double xi_value = lp_values[xi_var];
2172  const IntegerValue wt_i = GetCoefficientOfPositiveVar(xi_var, target_expr);
2173  double contrib = ToDouble(wt_i) * xi_value;
2174  for (int expr_index = 0; expr_index < exprs.size(); ++expr_index) {
2175  if (expr_index == target_index) continue;
2176  const LinearExpression& max_expr = exprs[expr_index];
2177  const double z_max_value = lp_values[z_vars[expr_index]];
2178  const IntegerValue corner_value = MaxCornerDifference(
2179  xi_var, wt_i, GetCoefficientOfPositiveVar(xi_var, max_expr),
2180  integer_trail);
2181  contrib += ToDouble(corner_value) * z_max_value;
2182  }
2183  return contrib;
2184 }
2185 } // namespace
2186 
2188  const IntegerVariable target, const std::vector<LinearExpression>& exprs,
2189  const std::vector<IntegerVariable>& z_vars, Model* model) {
2190  CutGenerator result;
2191  std::vector<IntegerVariable> x_vars;
2192  result.vars = {target};
2193  const int num_exprs = exprs.size();
2194  for (int i = 0; i < num_exprs; ++i) {
2195  result.vars.push_back(z_vars[i]);
2196  x_vars.insert(x_vars.end(), exprs[i].vars.begin(), exprs[i].vars.end());
2197  }
2199  // All expressions should only contain positive variables.
2200  DCHECK(std::all_of(x_vars.begin(), x_vars.end(), [](IntegerVariable var) {
2201  return VariableIsPositive(var);
2202  }));
2203  result.vars.insert(result.vars.end(), x_vars.begin(), x_vars.end());
2204 
2205  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
2206  result.generate_cuts =
2207  [x_vars, z_vars, target, num_exprs, exprs, integer_trail, model](
2209  LinearConstraintManager* manager) {
2210  absl::StrongVector<IntegerVariable, int> variable_partition(
2211  lp_values.size(), -1);
2212  absl::StrongVector<IntegerVariable, double> variable_partition_contrib(
2213  lp_values.size(), std::numeric_limits<double>::infinity());
2214  for (int expr_index = 0; expr_index < num_exprs; ++expr_index) {
2215  for (const IntegerVariable var : x_vars) {
2216  const double contribution = ComputeContribution(
2217  var, z_vars, exprs, lp_values, *integer_trail, expr_index);
2218  const double prev_contribution = variable_partition_contrib[var];
2219  if (contribution < prev_contribution) {
2220  variable_partition[var] = expr_index;
2221  variable_partition_contrib[var] = contribution;
2222  }
2223  }
2224  }
2225 
2226  LinearConstraintBuilder cut(model, /*lb=*/IntegerValue(0),
2227  /*ub=*/kMaxIntegerValue);
2228  double violation = lp_values[target];
2229  cut.AddTerm(target, IntegerValue(-1));
2230 
2231  for (const IntegerVariable xi_var : x_vars) {
2232  const int input_index = variable_partition[xi_var];
2233  const LinearExpression& expr = exprs[input_index];
2234  const IntegerValue coeff = GetCoefficientOfPositiveVar(xi_var, expr);
2235  if (coeff != IntegerValue(0)) {
2236  cut.AddTerm(xi_var, coeff);
2237  }
2238  violation -= ToDouble(coeff) * lp_values[xi_var];
2239  }
2240  for (int expr_index = 0; expr_index < num_exprs; ++expr_index) {
2241  const IntegerVariable z_var = z_vars[expr_index];
2242  const IntegerValue z_coeff = MPlusCoefficient(
2243  x_vars, exprs, variable_partition, expr_index, *integer_trail);
2244  if (z_coeff != IntegerValue(0)) {
2245  cut.AddTerm(z_var, z_coeff);
2246  }
2247  violation -= ToDouble(z_coeff) * lp_values[z_var];
2248  }
2249  if (violation > 1e-2) {
2250  manager->AddCut(cut.Build(), "LinMax", lp_values);
2251  }
2252  return true;
2253  };
2254  return result;
2255 }
2256 
2257 namespace {
2258 
2259 IntegerValue EvaluateMaxAffine(
2260  const std::vector<std::pair<IntegerValue, IntegerValue>>& affines,
2261  IntegerValue x) {
2262  IntegerValue y = kMinIntegerValue;
2263  for (const auto& p : affines) {
2264  y = std::max(y, x * p.first + p.second);
2265  }
2266  return y;
2267 }
2268 
2269 } // namespace
2270 
2272  const LinearExpression& target, IntegerVariable var,
2273  const std::vector<std::pair<IntegerValue, IntegerValue>>& affines,
2274  Model* model, LinearConstraintBuilder* builder) {
2275  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
2276  const IntegerValue x_min = integer_trail->LevelZeroLowerBound(var);
2277  const IntegerValue x_max = integer_trail->LevelZeroUpperBound(var);
2278 
2279  const IntegerValue y_at_min = EvaluateMaxAffine(affines, x_min);
2280  const IntegerValue y_at_max = EvaluateMaxAffine(affines, x_max);
2281 
2282  const IntegerValue delta_x = x_max - x_min;
2283  const IntegerValue delta_y = y_at_max - y_at_min;
2284 
2285  // target <= y_at_min + (delta_y / delta_x) * (var - x_min)
2286  // delta_x * target <= delta_x * y_at_min + delta_y * (var - x_min)
2287  // -delta_y * var + delta_x * target <= delta_x * y_at_min - delta_y * x_min
2288  //
2289  // Checks the rhs for overflows.
2290  if (AtMinOrMaxInt64(CapProd(delta_x.value(), y_at_min.value())) ||
2291  AtMinOrMaxInt64(CapProd(delta_y.value(), x_min.value()))) {
2292  return false;
2293  }
2294 
2295  builder->ResetBounds(kMinIntegerValue, delta_x * y_at_min - delta_y * x_min);
2296  builder->AddLinearExpression(target, delta_x);
2297  builder->AddTerm(var, -delta_y);
2298 
2299  // Prevent to create constraints that can overflow.
2300  if (!ValidateLinearConstraintForOverflow(builder->Build(), *integer_trail)) {
2301  VLOG(2) << "Linear constraint can cause overflow: " << builder->Build();
2302 
2303  return false;
2304  }
2305 
2306  return true;
2307 }
2308 
2310  LinearExpression target, IntegerVariable var,
2311  std::vector<std::pair<IntegerValue, IntegerValue>> affines,
2312  const std::string cut_name, Model* model) {
2313  CutGenerator result;
2314  result.vars = target.vars;
2315  result.vars.push_back(var);
2317 
2318  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
2319  result.generate_cuts =
2320  [target, var, affines, cut_name, integer_trail, model](
2322  LinearConstraintManager* manager) {
2323  if (integer_trail->IsFixed(var)) return true;
2324  LinearConstraintBuilder builder(model);
2325  if (BuildMaxAffineUpConstraint(target, var, affines, model, &builder)) {
2326  manager->AddCut(builder.Build(), cut_name, lp_values);
2327  }
2328  return true;
2329  };
2330  return result;
2331 }
2332 
2334  const std::vector<IntegerVariable>& base_variables, Model* model) {
2335  // Filter base_variables to only keep the one with a literal view, and
2336  // do the conversion.
2337  std::vector<IntegerVariable> variables;
2338  std::vector<Literal> literals;
2339  absl::flat_hash_map<LiteralIndex, IntegerVariable> positive_map;
2340  absl::flat_hash_map<LiteralIndex, IntegerVariable> negative_map;
2341  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
2342  auto* encoder = model->GetOrCreate<IntegerEncoder>();
2343  for (const IntegerVariable var : base_variables) {
2344  if (integer_trail->LowerBound(var) != IntegerValue(0)) continue;
2345  if (integer_trail->UpperBound(var) != IntegerValue(1)) continue;
2346  const LiteralIndex literal_index = encoder->GetAssociatedLiteral(
2347  IntegerLiteral::GreaterOrEqual(var, IntegerValue(1)));
2348  if (literal_index != kNoLiteralIndex) {
2349  variables.push_back(var);
2350  literals.push_back(Literal(literal_index));
2351  positive_map[literal_index] = var;
2352  negative_map[Literal(literal_index).NegatedIndex()] = var;
2353  }
2354  }
2355  CutGenerator result;
2356  result.vars = variables;
2357  auto* implication_graph = model->GetOrCreate<BinaryImplicationGraph>();
2358  result.generate_cuts =
2359  [variables, literals, implication_graph, positive_map, negative_map,
2361  LinearConstraintManager* manager) {
2362  std::vector<double> packed_values;
2363  for (int i = 0; i < literals.size(); ++i) {
2364  packed_values.push_back(lp_values[variables[i]]);
2365  }
2366  const std::vector<std::vector<Literal>> at_most_ones =
2367  implication_graph->GenerateAtMostOnesWithLargeWeight(literals,
2368  packed_values);
2369 
2370  for (const std::vector<Literal>& at_most_one : at_most_ones) {
2371  // We need to express such "at most one" in term of the initial
2372  // variables, so we do not use the
2373  // LinearConstraintBuilder::AddLiteralTerm() here.
2374  LinearConstraintBuilder builder(
2375  model, IntegerValue(std::numeric_limits<int64_t>::min()),
2376  IntegerValue(1));
2377  for (const Literal l : at_most_one) {
2378  if (positive_map.contains(l.Index())) {
2379  builder.AddTerm(positive_map.at(l.Index()), IntegerValue(1));
2380  } else {
2381  // Add 1 - X to the linear constraint.
2382  builder.AddTerm(negative_map.at(l.Index()), IntegerValue(-1));
2383  builder.AddConstant(IntegerValue(1));
2384  }
2385  }
2386 
2387  manager->AddCut(builder.Build(), "Clique", lp_values);
2388  }
2389  return true;
2390  };
2391  return result;
2392 }
2393 
2394 } // namespace sat
2395 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
Domain Negation() const
Returns {x ∈ Int64, ∃ e ∈ D, x = -e}.
DomainIteratorBeginEnd Values() const &
bool ConvertToLinearConstraint(const CutData &cut, LinearConstraint *output)
Definition: cuts.cc:235
void AddOrMergeTerm(const CutTerm &term, IntegerValue t, CutData *cut)
Definition: cuts.cc:201
BestImpliedBoundInfo GetCachedImpliedBoundInfo(IntegerVariable var) const
Definition: cuts.cc:1455
bool TryToExpandWithLowerImpliedbound(IntegerValue factor_t, int i, bool complement, CutData *cut, CutDataBuilder *builder)
Definition: cuts.cc:1546
const LinearConstraint & cut() const
Definition: cuts.h:409
bool ComputeCut(RoundingOptions options, const CutData &base_ct, ImpliedBoundsProcessor *ib_processor=nullptr)
Definition: cuts.cc:542
bool IsFixed(IntegerVariable i) const
Definition: integer.h:1565
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
Definition: integer.h:1646
IntegerValue FixedValue(IntegerVariable i) const
Definition: integer.h:1569
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Definition: integer.h:1641
const Domain & InitialVariableDomain(IntegerVariable var) const
Definition: integer.cc:852
void AddLinearExpression(const LinearExpression &expr)
void ResetBounds(IntegerValue lb, IntegerValue ub)
void AddTerm(IntegerVariable var, IntegerValue coeff)
LiteralIndex NegatedIndex() const
Definition: sat_base.h:91
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:42
void AddStats(absl::Span< const std::pair< std::string, int64_t >> stats)
IntegerValue GetBestLowerBound(std::string &suffix)
Definition: cuts.cc:2015
void Add(const AffineExpression &expr, int num_expr, const IntegerTrail &integer_trail)
Definition: cuts.cc:1968
void AddCut(LinearConstraint ct, const std::string &name, const absl::StrongVector< IntegerVariable, double > &lp_solution)
void TransferToManager(const absl::StrongVector< IntegerVariable, double > &lp_solution, LinearConstraintManager *manager)
int64_t b
int64_t a
int64_t value
IntVar * var
Definition: expr_array.cc:1874
GRBmodel * model
int index
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition: stl_util.h:58
static double ToDouble(double f)
Definition: lp_types.h:73
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:269
IntegerValue FloorRatio(IntegerValue dividend, IntegerValue positive_divisor)
Definition: integer.h:98
bool ValidateLinearConstraintForOverflow(const LinearConstraint &constraint, const IntegerTrail &integer_trail)
bool AddProductTo(IntegerValue a, IntegerValue b, IntegerValue *result)
Definition: integer.h:121
std::function< void(Model *)> GreaterOrEqual(IntegerVariable v, int64_t lb)
Definition: integer.h:1803
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
IntType IntTypeAbs(IntType t)
Definition: integer.h:85
LinearConstraint ComputeHyperplanBelowSquare(AffineExpression x, AffineExpression square, IntegerValue x_value, Model *model)
Definition: cuts.cc:1405
CutGenerator CreateAllDifferentCutGenerator(const std::vector< AffineExpression > &exprs, Model *model)
Definition: cuts.cc:2075
IntegerValue CeilRatio(IntegerValue dividend, IntegerValue positive_divisor)
Definition: integer.h:89
const LiteralIndex kNoLiteralIndex(-1)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
const IntegerVariable kNoIntegerVariable(-1)
CutGenerator CreateMaxAffineCutGenerator(LinearExpression target, IntegerVariable var, std::vector< std::pair< IntegerValue, IntegerValue >> affines, const std::string cut_name, Model *model)
Definition: cuts.cc:2309
IntegerVariable PositiveVariable(IntegerVariable i)
Definition: integer.h:149
CutGenerator CreateLinMaxCutGenerator(const IntegerVariable target, const std::vector< LinearExpression > &exprs, const std::vector< IntegerVariable > &z_vars, Model *model)
Definition: cuts.cc:2187
IntegerValue PositiveRemainder(IntegerValue dividend, IntegerValue positive_divisor)
Definition: integer.h:113
CutGenerator CreatePositiveMultiplicationCutGenerator(AffineExpression z, AffineExpression x, AffineExpression y, int linearization_level, Model *model)
Definition: cuts.cc:1295
bool BuildMaxAffineUpConstraint(const LinearExpression &target, IntegerVariable var, const std::vector< std::pair< IntegerValue, IntegerValue >> &affines, Model *model, LinearConstraintBuilder *builder)
Definition: cuts.cc:2271
IntegerValue GetFactorT(IntegerValue rhs_remainder, IntegerValue divisor, IntegerValue max_magnitude)
Definition: cuts.cc:292
std::function< IntegerValue(IntegerValue)> GetSuperAdditiveRoundingFunction(IntegerValue rhs_remainder, IntegerValue divisor, IntegerValue t, IntegerValue max_scaling)
Definition: cuts.cc:306
CutGenerator CreateSquareCutGenerator(AffineExpression y, AffineExpression x, int linearization_level, Model *model)
Definition: cuts.cc:1417
LinearConstraint ComputeHyperplanAboveSquare(AffineExpression x, AffineExpression square, IntegerValue x_lb, IntegerValue x_ub, Model *model)
Definition: cuts.cc:1393
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Definition: integer.cc:46
IntegerValue GetCoefficientOfPositiveVar(const IntegerVariable var, const LinearExpression &expr)
void CleanTermsAndFillConstraint(std::vector< std::pair< IntegerVariable, IntegerValue >> *terms, ClassWithVarsAndCoeffs *output)
CutGenerator CreateCliqueCutGenerator(const std::vector< IntegerVariable > &base_variables, Model *model)
Definition: cuts.cc:2333
void DivideByGCD(LinearConstraint *constraint)
double ToDouble(IntegerValue value)
Definition: integer.h:77
Collection of objects used to extend the Constraint Solver library.
bool AtMinOrMaxInt64(int64_t x)
int64_t CapAdd(int64_t x, int64_t y)
int64_t CapSub(int64_t x, int64_t y)
int64_t CapProd(int64_t x, int64_t y)
static int input(yyscan_t yyscanner)
int64_t demand
Definition: resource.cc:126
int64_t delta
Definition: resource.cc:1695
Fractional ratio
int64_t capacity
std::vector< double > lower_bounds
std::vector< double > upper_bounds
IntegerValue ValueAt(IntegerValue var_value) const
Definition: integer.h:291
double LpValue(const absl::StrongVector< IntegerVariable, double > &lp_values) const
Definition: integer.h:296
std::vector< CutTerm > terms
Definition: cuts.h:106
bool FillFromLinearConstraint(const LinearConstraint &base_ct, const absl::StrongVector< IntegerVariable, double > &lp_values, IntegerTrail *integer_trail)
Definition: cuts.cc:116
bool FillFromParallelVectors(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:134
bool AppendOneTerm(IntegerVariable var, IntegerValue coeff, double lp_value, IntegerValue lb, IntegerValue ub)
Definition: cuts.cc:73
std::vector< IntegerVariable > vars
Definition: cuts.h:50
std::function< bool(const absl::StrongVector< IntegerVariable, double > &lp_values, LinearConstraintManager *manager)> generate_cuts
Definition: cuts.h:54
double LpDistToMaxValue() const
Definition: cuts.h:64
bool HasRelevantLpValue() const
Definition: cuts.h:63
std::string DebugString() const
Definition: cuts.cc:48
std::array< IntegerVariable, 2 > expr_vars
Definition: cuts.h:84
bool Complement(IntegerValue *rhs)
Definition: cuts.cc:53
std::array< IntegerValue, 2 > expr_coeffs
Definition: cuts.h:85
AffineExpression flow_expr
Definition: cuts.h:246
AffineExpression bool_expr
Definition: cuts.h:247
std::vector< FlowInfo > out_flow
Definition: cuts.h:264
std::vector< FlowInfo > in_flow
Definition: cuts.h:263
#define VLOG(verboselevel)
Definition: vlog.h:39
#define VLOG_IS_ON(verboselevel)
Definition: vlog_is_on.h:47