Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
cuts.cc
Go to the documentation of this file.
1// Copyright 2010-2025 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 <array>
18#include <cmath>
19#include <cstdint>
20#include <functional>
21#include <limits>
22#include <string>
23#include <tuple>
24#include <utility>
25#include <vector>
26
27#include "absl/base/attributes.h"
28#include "absl/container/btree_map.h"
29#include "absl/container/btree_set.h"
30#include "absl/container/flat_hash_map.h"
31#include "absl/container/flat_hash_set.h"
32#include "absl/log/check.h"
33#include "absl/meta/type_traits.h"
34#include "absl/numeric/int128.h"
35#include "absl/strings/str_cat.h"
36#include "absl/strings/string_view.h"
37#include "absl/types/span.h"
42#include "ortools/sat/clause.h"
44#include "ortools/sat/integer.h"
48#include "ortools/sat/model.h"
54
55namespace operations_research {
56namespace sat {
57
58namespace {
59
60// TODO(user): the function ToDouble() does some testing for min/max integer
61// value and we don't need that here.
62double AsDouble(IntegerValue v) { return static_cast<double>(v.value()); }
63
64} // namespace
65
66std::string CutTerm::DebugString() const {
67 return absl::StrCat("coeff=", coeff.value(), " lp=", lp_value,
68 " range=", bound_diff.value(), " expr=[",
69 expr_coeffs[0].value(), ",", expr_coeffs[1].value(), "]",
70 " * [V", expr_vars[0].value(), ",V", expr_vars[1].value(),
71 "]", " + ", expr_offset.value());
72}
73
74std::string CutData::DebugString() const {
75 std::string result = absl::StrCat("CutData rhs=", rhs, "\n");
76 for (const CutTerm& term : terms) {
77 absl::StrAppend(&result, term.DebugString(), "\n");
78 }
79 return result;
80}
81
82void CutTerm::Complement(absl::int128* rhs) {
83 // We replace coeff * X by coeff * (X - bound_diff + bound_diff)
84 // which gives -coeff * complement(X) + coeff * bound_diff;
85 *rhs -= absl::int128(coeff.value()) * absl::int128(bound_diff.value());
86
87 // We keep the same expression variable.
88 for (int i = 0; i < 2; ++i) {
90 }
92
93 // Note that this is not involutive because of floating point error. Fix?
94 lp_value = static_cast<double>(bound_diff.value()) - lp_value;
95 coeff = -coeff;
96
97 // Swap the implied bound info.
99}
100
101void CutTerm::ReplaceExpressionByLiteral(IntegerVariable var) {
102 CHECK_EQ(bound_diff, 1);
103 expr_coeffs[1] = 0;
104 if (VariableIsPositive(var)) {
105 expr_vars[0] = var;
106 expr_coeffs[0] = 1;
107 expr_offset = 0;
108 } else {
109 expr_vars[0] = PositiveVariable(var);
110 expr_coeffs[0] = -1;
111 expr_offset = 1;
112 }
113}
114
115IntegerVariable CutTerm::GetUnderlyingLiteralOrNone() const {
116 if (expr_coeffs[1] != 0) return kNoIntegerVariable;
117 if (bound_diff != 1) return kNoIntegerVariable;
118
119 if (expr_coeffs[0] > 0) {
120 if (expr_coeffs[0] != 1) return kNoIntegerVariable;
121 if (expr_offset != 0) return kNoIntegerVariable;
122 CHECK(VariableIsPositive(expr_vars[0]));
123 return expr_vars[0];
124 }
125
126 if (expr_coeffs[0] != -1) return kNoIntegerVariable;
127 if (expr_offset != 1) return kNoIntegerVariable;
128 CHECK(VariableIsPositive(expr_vars[0]));
129 return NegationOf(expr_vars[0]);
130}
131
132// To try to minimize the risk of overflow, we switch to the bound closer
133// to the lp_value. Since most of our base constraint for cut are tight,
134// hopefully this is not too bad.
135bool CutData::AppendOneTerm(IntegerVariable var, IntegerValue coeff,
136 double lp_value, IntegerValue lb, IntegerValue ub) {
137 if (coeff == 0) return true;
138 const IntegerValue bound_diff = ub - lb;
139
140 // Complement the variable so that it is has a positive coefficient.
141 const bool complement = coeff < 0;
142
143 // See formula below, the constant term is either coeff * lb or coeff * ub.
144 rhs -= absl::int128(coeff.value()) *
145 absl::int128(complement ? ub.value() : lb.value());
146
147 // Deal with fixed variable, no need to shift back in this case, we can
148 // just remove the term.
149 if (bound_diff == 0) return true;
150
151 CutTerm entry;
152 entry.expr_vars[0] = var;
153 entry.expr_coeffs[1] = 0;
154 entry.bound_diff = bound_diff;
155 if (complement) {
156 // X = -(UB - X) + UB
157 entry.expr_coeffs[0] = -IntegerValue(1);
158 entry.expr_offset = ub;
159 entry.coeff = -coeff;
160 entry.lp_value = static_cast<double>(ub.value()) - lp_value;
161 } else {
162 // C = (X - LB) + LB
163 entry.expr_coeffs[0] = IntegerValue(1);
164 entry.expr_offset = -lb;
165 entry.coeff = coeff;
166 entry.lp_value = lp_value - static_cast<double>(lb.value());
167 }
168 terms.push_back(entry);
169 return true;
170}
171
173 const LinearConstraint& base_ct,
175 IntegerTrail* integer_trail) {
176 rhs = absl::int128(base_ct.ub.value());
177 terms.clear();
178 const int num_terms = base_ct.num_terms;
179 for (int i = 0; i < num_terms; ++i) {
180 const IntegerVariable var = base_ct.vars[i];
181 if (!AppendOneTerm(var, base_ct.coeffs[i], lp_values[base_ct.vars[i]],
182 integer_trail->LevelZeroLowerBound(var),
183 integer_trail->LevelZeroUpperBound(var))) {
184 return false;
185 }
186 }
187 return true;
188}
189
191 IntegerValue ub, absl::Span<const IntegerVariable> vars,
192 absl::Span<const IntegerValue> coeffs, absl::Span<const double> lp_values,
193 absl::Span<const IntegerValue> lower_bounds,
194 absl::Span<const IntegerValue> upper_bounds) {
195 rhs = absl::int128(ub.value());
196 terms.clear();
197
198 const int size = lp_values.size();
199 if (size == 0) return true;
200
201 CHECK_EQ(vars.size(), size);
202 CHECK_EQ(coeffs.size(), size);
203 CHECK_EQ(lower_bounds.size(), size);
204 CHECK_EQ(upper_bounds.size(), size);
205
206 for (int i = 0; i < size; ++i) {
207 if (!AppendOneTerm(vars[i], coeffs[i], lp_values[i], lower_bounds[i],
208 upper_bounds[i])) {
209 return false;
210 }
211 }
212
213 return true;
214}
215
217 for (CutTerm& term : terms) {
218 if (term.coeff >= 0) continue;
219 term.Complement(&rhs);
220 }
221}
222
224 for (CutTerm& term : terms) {
225 if (term.lp_value <= term.LpDistToMaxValue()) continue;
226 term.Complement(&rhs);
227 }
228}
229
231 for (const CutTerm& term : terms) {
232 if (term.coeff < 0) return false;
233 }
234 return true;
235}
236
239 max_magnitude = 0;
240 for (CutTerm& entry : terms) {
241 max_magnitude = std::max(max_magnitude, IntTypeAbs(entry.coeff));
242 if (entry.HasRelevantLpValue()) {
243 std::swap(terms[num_relevant_entries], entry);
245 }
246 }
247
248 // Sort by larger lp_value first.
249 std::sort(terms.begin(), terms.begin() + num_relevant_entries,
250 [](const CutTerm& a, const CutTerm& b) {
251 return a.lp_value > b.lp_value;
252 });
253}
254
256 double violation = -static_cast<double>(rhs);
257 for (const CutTerm& term : terms) {
258 violation += term.lp_value * static_cast<double>(term.coeff.value());
259 }
260 return violation;
261}
262
264 double violation = -static_cast<double>(rhs);
265 double norm = 0.0;
266 for (const CutTerm& term : terms) {
267 const double coeff = static_cast<double>(term.coeff.value());
268 violation += term.lp_value * coeff;
269 norm += coeff * coeff;
270 }
271 return violation / std::sqrt(norm);
272}
273
274// We can only merge the term if term.coeff + old_coeff do not overflow and
275// if t * new_coeff do not overflow.
276//
277// If we cannot merge the term, we will keep them separate. The produced cut
278// will be less strong, but can still be used.
279bool CutDataBuilder::MergeIfPossible(IntegerValue t, CutTerm& to_add,
280 CutTerm& target) {
281 DCHECK_EQ(to_add.expr_vars[0], target.expr_vars[0]);
282 DCHECK_EQ(to_add.expr_coeffs[0], target.expr_coeffs[0]);
283
284 const IntegerValue new_coeff = CapAddI(to_add.coeff, target.coeff);
285 if (AtMinOrMaxInt64I(new_coeff) || ProdOverflow(t, new_coeff)) {
286 return false;
287 }
288
289 to_add.coeff = 0; // Clear since we merge it.
290 target.coeff = new_coeff;
291 return true;
292}
293
294// We only deal with coeff * Bool or coeff * (1 - Bool)
295//
296// TODO(user): Because of merges, we might have entry with a coefficient of
297// zero than are not useful. Remove them?
298int CutDataBuilder::AddOrMergeBooleanTerms(absl::Span<CutTerm> new_terms,
299 IntegerValue t, CutData* cut) {
300 if (new_terms.empty()) return 0;
301
302 bool_index_.clear();
303 secondary_bool_index_.clear();
304 int num_merges = 0;
305
306 // Fill the maps.
307 int i = 0;
308 for (CutTerm& term : new_terms) {
309 const IntegerVariable var = term.expr_vars[0];
310 auto& map = term.expr_coeffs[0] > 0 ? bool_index_ : secondary_bool_index_;
311 const auto [it, inserted] = map.insert({var, i});
312 if (!inserted) {
313 if (MergeIfPossible(t, term, new_terms[it->second])) {
314 ++num_merges;
315 }
316 }
317 ++i;
318 }
319
320 // Loop over the cut now. Note that we loop with indices as we might add new
321 // terms in the middle of the loop.
322 for (CutTerm& term : cut->terms) {
323 if (term.bound_diff != 1) continue;
324 if (!term.IsSimple()) continue;
325
326 const IntegerVariable var = term.expr_vars[0];
327 auto& map = term.expr_coeffs[0] > 0 ? bool_index_ : secondary_bool_index_;
328 auto it = map.find(var);
329 if (it == map.end()) continue;
330
331 // We found a match, try to merge the map entry into the cut.
332 // Note that we don't waste time erasing this entry from the map since
333 // we should have no duplicates in the original cut.
334 if (MergeIfPossible(t, new_terms[it->second], term)) {
335 ++num_merges;
336 }
337 }
338
339 // Finally add the terms we couldn't merge.
340 for (const CutTerm& term : new_terms) {
341 if (term.coeff == 0) continue;
342 cut->terms.push_back(term);
343 }
344
345 return num_merges;
346}
347
348// TODO(user): Divide by gcd first to avoid possible overflow in the
349// conversion? it is however unlikely given that our coeffs should be small.
350ABSL_DEPRECATED("Only used in tests, this will be removed.")
352 LinearConstraint* output) {
353 tmp_map_.clear();
354 if (cut.rhs > absl::int128(std::numeric_limits<int64_t>::max()) ||
355 cut.rhs < absl::int128(std::numeric_limits<int64_t>::min())) {
356 return false;
357 }
358 IntegerValue new_rhs = static_cast<int64_t>(cut.rhs);
359 for (const CutTerm& term : cut.terms) {
360 for (int i = 0; i < 2; ++i) {
361 if (term.expr_coeffs[i] == 0) continue;
362 if (!AddProductTo(term.coeff, term.expr_coeffs[i],
363 &tmp_map_[term.expr_vars[i]])) {
364 return false;
365 }
366 }
367 if (!AddProductTo(-term.coeff, term.expr_offset, &new_rhs)) {
368 return false;
369 }
370 }
371
372 output->lb = kMinIntegerValue;
373 output->ub = new_rhs;
374 output->resize(tmp_map_.size());
375 int new_size = 0;
376 for (const auto [var, coeff] : tmp_map_) {
377 if (coeff == 0) continue;
378 output->vars[new_size] = var;
379 output->coeffs[new_size] = coeff;
380 ++new_size;
381 }
382 output->resize(new_size);
383 DivideByGCD(output);
384 return true;
385}
386
387namespace {
388
389// Minimum amount of violation of the cut constraint by the solution. This
390// is needed to avoid numerical issues and adding cuts with minor effect.
391const double kMinCutViolation = 1e-4;
392
393IntegerValue PositiveRemainder(absl::int128 dividend,
394 IntegerValue positive_divisor) {
395 DCHECK_GT(positive_divisor, 0);
396 const IntegerValue m =
397 static_cast<int64_t>(dividend % absl::int128(positive_divisor.value()));
398 return m < 0 ? m + positive_divisor : m;
399}
400
401// We use the fact that f(k * divisor + rest) = k * f(divisor) + f(rest)
402absl::int128 ApplyToInt128(const std::function<IntegerValue(IntegerValue)>& f,
403 IntegerValue divisor, absl::int128 value) {
404 const IntegerValue rest = PositiveRemainder(value, divisor);
405 const absl::int128 k =
406 (value - absl::int128(rest.value())) / absl::int128(divisor.value());
407 const absl::int128 result =
408 k * absl::int128(f(divisor).value()) + absl::int128(f(rest).value());
409 return result;
410}
411
412// Apply f() to the cut with a potential improvement for one Boolean:
413//
414// If we have a Boolean X, and a cut: terms + a * X <= b;
415// By setting X to true or false, we have two inequalities:
416// terms <= b if X == 0
417// terms <= b - a if X == 1
418// We can apply f to both inequalities and recombine:
419// f(terms) <= f(b) * (1 - X) + f(b - a) * X
420// Which change the final coeff of X from f(a) to [f(b) - f(b - a)].
421// This can only improve the cut since f(b) >= f(b - a) + f(a)
422int ApplyWithPotentialBump(const std::function<IntegerValue(IntegerValue)>& f,
423 const IntegerValue divisor, CutData* cut) {
424 int bump_index = -1;
425 double bump_score = -1.0;
426 IntegerValue bump_coeff;
427 const IntegerValue remainder = PositiveRemainder(cut->rhs, divisor);
428 const IntegerValue f_remainder = f(remainder);
429 cut->rhs = ApplyToInt128(f, divisor, cut->rhs);
430 for (int i = 0; i < cut->terms.size(); ++i) {
431 CutTerm& entry = cut->terms[i];
432 const IntegerValue f_coeff = f(entry.coeff);
433 if (entry.bound_diff == 1) {
434 // TODO(user): we probably don't need int128 here, but we need
435 // t * (remainder - entry.coeff) not to overflow, and we can't really be
436 // sure.
437 const IntegerValue alternative =
438 entry.coeff > 0
439 ? f_remainder - f(remainder - entry.coeff)
440 : f_remainder - IntegerValue(static_cast<int64_t>(ApplyToInt128(
441 f, divisor,
442 absl::int128(remainder.value()) -
443 absl::int128(entry.coeff.value()))));
444 DCHECK_GE(alternative, f_coeff);
445 if (alternative > f_coeff) {
446 const double score = ToDouble(alternative - f_coeff) * entry.lp_value;
447 if (score > bump_score) {
448 bump_index = i;
449 bump_score = score;
450 bump_coeff = alternative;
451 }
452 }
453 }
454 entry.coeff = f_coeff;
455 }
456 if (bump_index >= 0) {
457 cut->terms[bump_index].coeff = bump_coeff;
458 return 1;
459 }
460 return 0;
461}
462
463} // namespace
464
465// Compute the larger t <= max_t such that t * rhs_remainder >= divisor / 2.
466//
467// This is just a separate function as it is slightly faster to compute the
468// result only once.
469IntegerValue GetFactorT(IntegerValue rhs_remainder, IntegerValue divisor,
470 IntegerValue max_magnitude) {
471 // Make sure that when we multiply the rhs or the coefficient by a factor t,
472 // we do not have an integer overflow. Note that the rhs should be counted
473 // in max_magnitude since we will apply f() on it.
474 IntegerValue max_t(std::numeric_limits<int64_t>::max());
475 if (max_magnitude != 0) {
476 max_t = max_t / max_magnitude;
477 }
478 return rhs_remainder == 0
479 ? max_t
480 : std::min(max_t, CeilRatio(divisor / 2, rhs_remainder));
481}
482
483std::function<IntegerValue(IntegerValue)> GetSuperAdditiveRoundingFunction(
484 IntegerValue rhs_remainder, IntegerValue divisor, IntegerValue t,
485 IntegerValue max_scaling) {
486 DCHECK_GE(max_scaling, 1);
487 DCHECK_GE(t, 1);
488
489 // Adjust after the multiplication by t.
490 rhs_remainder *= t;
491 DCHECK_LT(rhs_remainder, divisor);
492
493 // Make sure we don't have an integer overflow below. Note that we assume that
494 // divisor and the maximum coeff magnitude are not too different (maybe a
495 // factor 1000 at most) so that the final result will never overflow.
496 max_scaling =
497 std::min(max_scaling, std::numeric_limits<int64_t>::max() / divisor);
498
499 const IntegerValue size = divisor - rhs_remainder;
500 if (max_scaling == 1 || size == 1) {
501 // TODO(user): Use everywhere a two step computation to avoid overflow?
502 // First divide by divisor, then multiply by t. For now, we limit t so that
503 // we never have an overflow instead.
504 return [t, divisor](IntegerValue coeff) {
505 return FloorRatio(t * coeff, divisor);
506 };
507 } else if (size <= max_scaling) {
508 return [size, rhs_remainder, t, divisor](IntegerValue coeff) {
509 const IntegerValue t_coeff = t * coeff;
510 const IntegerValue ratio = FloorRatio(t_coeff, divisor);
511 const IntegerValue remainder = PositiveRemainder(t_coeff, divisor);
512 const IntegerValue diff = remainder - rhs_remainder;
513 return size * ratio + std::max(IntegerValue(0), diff);
514 };
515 } else if (max_scaling.value() * rhs_remainder.value() < divisor) {
516 // Because of our max_t limitation, the rhs_remainder might stay small.
517 //
518 // If it is "too small" we cannot use the code below because it will not be
519 // valid. So we just divide divisor into max_scaling bucket. The
520 // rhs_remainder will be in the bucket 0.
521 //
522 // Note(user): This seems the same as just increasing t, modulo integer
523 // overflows. Maybe we should just always do the computation like this so
524 // that we can use larger t even if coeff is close to kint64max.
525 return [t, divisor, max_scaling](IntegerValue coeff) {
526 const IntegerValue t_coeff = t * coeff;
527 const IntegerValue ratio = FloorRatio(t_coeff, divisor);
528 const IntegerValue remainder = PositiveRemainder(t_coeff, divisor);
529 const IntegerValue bucket = FloorRatio(remainder * max_scaling, divisor);
530 return max_scaling * ratio + bucket;
531 };
532 } else {
533 // We divide (size = divisor - rhs_remainder) into (max_scaling - 1) buckets
534 // and increase the function by 1 / max_scaling for each of them.
535 //
536 // Note that for different values of max_scaling, we get a family of
537 // functions that do not dominate each others. So potentially, a max scaling
538 // as low as 2 could lead to the better cut (this is exactly the Letchford &
539 // Lodi function).
540 //
541 // Another interesting fact, is that if we want to compute the maximum alpha
542 // for a constraint with 2 terms like:
543 // divisor * Y + (ratio * divisor + remainder) * X
544 // <= rhs_ratio * divisor + rhs_remainder
545 // so that we have the cut:
546 // Y + (ratio + alpha) * X <= rhs_ratio
547 // This is the same as computing the maximum alpha such that for all integer
548 // X > 0 we have CeilRatio(alpha * divisor * X, divisor)
549 // <= CeilRatio(remainder * X - rhs_remainder, divisor).
550 // We can prove that this alpha is of the form (n - 1) / n, and it will
551 // be reached by such function for a max_scaling of n.
552 //
553 // TODO(user): This function is not always maximal when
554 // size % (max_scaling - 1) == 0. Improve?
555 return [size, rhs_remainder, t, divisor, max_scaling](IntegerValue coeff) {
556 const IntegerValue t_coeff = t * coeff;
557 const IntegerValue ratio = FloorRatio(t_coeff, divisor);
558 const IntegerValue remainder = PositiveRemainder(t_coeff, divisor);
559 const IntegerValue diff = remainder - rhs_remainder;
560 const IntegerValue bucket =
561 diff > 0 ? CeilRatio(diff * (max_scaling - 1), size)
562 : IntegerValue(0);
563 return max_scaling * ratio + bucket;
564 };
565 }
566}
567
568std::function<IntegerValue(IntegerValue)> GetSuperAdditiveStrengtheningFunction(
569 IntegerValue positive_rhs, IntegerValue min_magnitude) {
570 CHECK_GT(positive_rhs, 0);
571 CHECK_GT(min_magnitude, 0);
572
573 if (min_magnitude >= CeilRatio(positive_rhs, 2)) {
574 return [positive_rhs](IntegerValue v) {
575 if (v >= 0) return IntegerValue(0);
576 if (v > -positive_rhs) return IntegerValue(-1);
577 return IntegerValue(-2);
578 };
579 }
580
581 // The transformation only work if 2 * second_threshold >= positive_rhs.
582 //
583 // TODO(user): Limit the number of value used with scaling like above.
584 min_magnitude = std::min(min_magnitude, FloorRatio(positive_rhs, 2));
585 const IntegerValue second_threshold = positive_rhs - min_magnitude;
586 return [positive_rhs, min_magnitude, second_threshold](IntegerValue v) {
587 if (v >= 0) return IntegerValue(0);
588 if (v <= -positive_rhs) return -positive_rhs;
589 if (v <= -second_threshold) return -second_threshold;
590
591 // This should actually never happen by the definition of min_magnitude.
592 // But with it, the function is supper-additive even if min_magnitude is not
593 // correct.
594 if (v >= -min_magnitude) return -min_magnitude;
595
596 // TODO(user): we might want to intoduce some step to reduce the final
597 // magnitude of the cut.
598 return v;
599 };
600}
601
602std::function<IntegerValue(IntegerValue)>
604 IntegerValue scaling) {
605 if (scaling >= positive_rhs) {
606 // Simple case, no scaling required.
607 return [positive_rhs](IntegerValue v) {
608 if (v >= 0) return IntegerValue(0);
609 if (v <= -positive_rhs) return -positive_rhs;
610 return v;
611 };
612 }
613
614 // We need to scale.
615 scaling =
616 std::min(scaling, IntegerValue(std::numeric_limits<int64_t>::max()) /
617 positive_rhs);
618 if (scaling == 1) {
619 return [](IntegerValue v) {
620 if (v >= 0) return IntegerValue(0);
621 return IntegerValue(-1);
622 };
623 }
624
625 // We divide [-positive_rhs + 1, 0] into (scaling - 1) bucket.
626 return [positive_rhs, scaling](IntegerValue v) {
627 if (v >= 0) return IntegerValue(0);
628 if (v <= -positive_rhs) return -scaling;
629 return FloorRatio(v * (scaling - 1), (positive_rhs - 1));
630 };
631}
632
634 if (!VLOG_IS_ON(1)) return;
635 if (shared_stats_ == nullptr) return;
636 std::vector<std::pair<std::string, int64_t>> stats;
637 stats.push_back({"rounding_cut/num_initial_ibs_", total_num_initial_ibs_});
638 stats.push_back(
639 {"rounding_cut/num_initial_merges_", total_num_initial_merges_});
640 stats.push_back({"rounding_cut/num_pos_lifts", total_num_pos_lifts_});
641 stats.push_back({"rounding_cut/num_neg_lifts", total_num_neg_lifts_});
642 stats.push_back(
643 {"rounding_cut/num_post_complements", total_num_post_complements_});
644 stats.push_back({"rounding_cut/num_overflows", total_num_overflow_abort_});
645 stats.push_back({"rounding_cut/num_adjusts", total_num_coeff_adjust_});
646 stats.push_back({"rounding_cut/num_merges", total_num_merges_});
647 stats.push_back({"rounding_cut/num_bumps", total_num_bumps_});
648 stats.push_back(
649 {"rounding_cut/num_final_complements", total_num_final_complements_});
650 stats.push_back({"rounding_cut/num_dominating_f", total_num_dominating_f_});
651 shared_stats_->AddStats(stats);
652}
653
654double IntegerRoundingCutHelper::GetScaledViolation(
655 IntegerValue divisor, IntegerValue max_scaling,
656 IntegerValue remainder_threshold, const CutData& cut) {
657 absl::int128 rhs = cut.rhs;
658 IntegerValue max_magnitude = cut.max_magnitude;
659 const IntegerValue initial_rhs_remainder = PositiveRemainder(rhs, divisor);
660 if (initial_rhs_remainder < remainder_threshold) return 0.0;
661
662 // We will adjust coefficient that are just under an exact multiple of
663 // divisor to an exact multiple. This is meant to get rid of small errors
664 // that appears due to rounding error in our exact computation of the
665 // initial constraint given to this class.
666 //
667 // Each adjustement will cause the initial_rhs_remainder to increase, and we
668 // do not want to increase it above divisor. Our threshold below guarantees
669 // this. Note that the higher the rhs_remainder becomes, the more the
670 // function f() has a chance to reduce the violation, so it is not always a
671 // good idea to use all the slack we have between initial_rhs_remainder and
672 // divisor.
673 //
674 // TODO(user): We could see if for a fixed function f, the increase is
675 // interesting?
676 // before: f(rhs) - f(coeff) * lp_value
677 // after: f(rhs + increase * bound_diff) - f(coeff + increase) * lp_value.
678 adjusted_coeffs_.clear();
679 const IntegerValue adjust_threshold =
680 (divisor - initial_rhs_remainder - 1) /
681 IntegerValue(std::max(1000, cut.num_relevant_entries));
682 if (adjust_threshold > 0) {
683 // Even before we finish the adjust, we can have a lower bound on the
684 // activily loss using this divisor, and so we can abort early. This is
685 // similar to what is done below.
686 double max_violation = static_cast<double>(initial_rhs_remainder.value());
687 for (int i = 0; i < cut.num_relevant_entries; ++i) {
688 const CutTerm& entry = cut.terms[i];
689 const IntegerValue remainder = PositiveRemainder(entry.coeff, divisor);
690 if (remainder == 0) continue;
691 if (remainder <= initial_rhs_remainder) {
692 // We do not know exactly f() yet, but it will always round to the
693 // floor of the division by divisor in this case.
694 max_violation -=
695 static_cast<double>(remainder.value()) * entry.lp_value;
696 if (max_violation <= 1e-3) return 0.0;
697 continue;
698 }
699
700 // Adjust coeff of the form k * divisor - epsilon.
701 const IntegerValue adjust = divisor - remainder;
702 const IntegerValue prod = CapProdI(adjust, entry.bound_diff);
703 if (prod <= adjust_threshold) {
704 rhs += absl::int128(prod.value());
705 const IntegerValue new_coeff = entry.coeff + adjust;
706 adjusted_coeffs_.push_back({i, new_coeff});
707 max_magnitude = std::max(max_magnitude, IntTypeAbs(new_coeff));
708 }
709 }
710 }
711
712 const IntegerValue rhs_remainder = PositiveRemainder(rhs, divisor);
713 const IntegerValue t = GetFactorT(rhs_remainder, divisor, max_magnitude);
714 const auto f =
715 GetSuperAdditiveRoundingFunction(rhs_remainder, divisor, t, max_scaling);
716
717 // As we round coefficients, we will compute the loss compared to the
718 // current scaled constraint activity. As soon as this loss crosses the
719 // slack, then we known that there is no violation and we can abort early.
720 //
721 // TODO(user): modulo the scaling, we could compute the exact threshold
722 // using our current best cut. Note that we also have to account the change
723 // in slack due to the adjust code above.
724 const double scaling = ToDouble(f(divisor)) / ToDouble(divisor);
725 double max_violation = scaling * ToDouble(rhs_remainder);
726
727 // Apply f() to the cut and compute the cut violation. Note that it is
728 // okay to just look at the relevant indices since the other have a lp
729 // value which is almost zero. Doing it like this is faster, and even if
730 // the max_magnitude might be off it should still be relevant enough.
731 double violation = -static_cast<double>(ApplyToInt128(f, divisor, rhs));
732 double l2_norm = 0.0;
733 int adjusted_coeffs_index = 0;
734 for (int i = 0; i < cut.num_relevant_entries; ++i) {
735 const CutTerm& entry = cut.terms[i];
736
737 // Adjust coeff according to our previous computation if needed.
738 IntegerValue coeff = entry.coeff;
739 if (adjusted_coeffs_index < adjusted_coeffs_.size() &&
740 adjusted_coeffs_[adjusted_coeffs_index].first == i) {
741 coeff = adjusted_coeffs_[adjusted_coeffs_index].second;
742 adjusted_coeffs_index++;
743 }
744
745 if (coeff == 0) continue;
746 const IntegerValue new_coeff = f(coeff);
747 const double new_coeff_double = ToDouble(new_coeff);
748 const double lp_value = entry.lp_value;
749
750 // TODO(user): Shall we compute the norm after slack are substituted back?
751 // it might be widely different. Another reason why this might not be
752 // the best measure.
753 l2_norm += new_coeff_double * new_coeff_double;
754 violation += new_coeff_double * lp_value;
755 max_violation -= (scaling * ToDouble(coeff) - new_coeff_double) * lp_value;
756 if (max_violation <= 1e-3) return 0.0;
757 }
758 if (l2_norm == 0.0) return 0.0;
759
760 // Here we scale by the L2 norm over the "relevant" positions. This seems
761 // to work slighly better in practice.
762 //
763 // Note(user): The non-relevant position have an LP value of zero. If their
764 // coefficient is positive, it seems good not to take it into account in the
765 // norm since the larger this coeff is, the stronger the cut. If the coeff
766 // is negative though, a large coeff means a small increase from zero of the
767 // lp value will make the cut satisfied, so we might want to look at them.
768 return violation / sqrt(l2_norm);
769}
770
771// TODO(user): This is slow, 50% of run time on a2c1s1.pb.gz. Optimize!
773 RoundingOptions options, const CutData& base_ct,
774 ImpliedBoundsProcessor* ib_processor) {
775 // Try IB before heuristic?
776 // This should be better except it can mess up the norm and the divisors.
777 cut_ = base_ct;
778 if (options.use_ib_before_heuristic && ib_processor != nullptr) {
779 std::vector<CutTerm>* new_bool_terms =
780 ib_processor->ClearedMutableTempTerms();
781 for (CutTerm& term : cut_.terms) {
782 if (term.bound_diff <= 1) continue;
783 if (!term.HasRelevantLpValue()) continue;
784
785 if (options.prefer_positive_ib && term.coeff < 0) {
786 // We complement the term before trying the implied bound.
787 term.Complement(&cut_.rhs);
788 if (ib_processor->TryToExpandWithLowerImpliedbound(
789 IntegerValue(1),
790 /*complement=*/true, &term, &cut_.rhs, new_bool_terms)) {
791 ++total_num_initial_ibs_;
792 continue;
793 }
794 term.Complement(&cut_.rhs);
795 }
796
797 if (ib_processor->TryToExpandWithLowerImpliedbound(
798 IntegerValue(1),
799 /*complement=*/true, &term, &cut_.rhs, new_bool_terms)) {
800 ++total_num_initial_ibs_;
801 }
802 }
803
804 // TODO(user): We assume that this is called with and without the option
805 // use_ib_before_heuristic, so that we can abort if no IB has been applied
806 // since then we will redo the computation. This is not really clean.
807 if (new_bool_terms->empty()) return false;
808 total_num_initial_merges_ +=
810 absl::MakeSpan(*new_bool_terms), IntegerValue(1), &cut_);
811 }
812
813 // Our heuristic will try to generate a few different cuts, and we will keep
814 // the most violated one scaled by the l2 norm of the relevant position.
815 //
816 // TODO(user): Experiment for the best value of this initial violation
817 // threshold. Note also that we use the l2 norm on the restricted position
818 // here. Maybe we should change that? On that note, the L2 norm usage seems
819 // a bit weird to me since it grows with the number of term in the cut. And
820 // often, we already have a good cut, and we make it stronger by adding
821 // extra terms that do not change its activity.
822 //
823 // The discussion above only concern the best_scaled_violation initial
824 // value. The remainder_threshold allows to not consider cuts for which the
825 // final efficacity is clearly lower than 1e-3 (it is a bound, so we could
826 // generate cuts with a lower efficacity than this).
827 //
828 // TODO(user): If the rhs is small and close to zero, we might want to
829 // consider different way of complementing the variables.
830 cut_.SortRelevantEntries();
831 const IntegerValue remainder_threshold(
832 std::max(IntegerValue(1), cut_.max_magnitude / 1000));
833 if (cut_.rhs >= 0 && cut_.rhs < remainder_threshold.value()) {
834 return false;
835 }
836
837 // There is no point trying twice the same divisor or a divisor that is too
838 // small. Note that we use a higher threshold than the remainder_threshold
839 // because we can boost the remainder thanks to our adjusting heuristic
840 // below and also because this allows to have cuts with a small range of
841 // coefficients.
842 divisors_.clear();
843 for (const CutTerm& entry : cut_.terms) {
844 // Note that because of the slacks, initial coeff are here too.
845 const IntegerValue magnitude = IntTypeAbs(entry.coeff);
846 if (magnitude <= remainder_threshold) continue;
847 divisors_.push_back(magnitude);
848
849 // If we have too many divisor to try, restrict to the first ones which
850 // should correspond to the highest lp values.
851 if (divisors_.size() > 50) break;
852 }
853 if (divisors_.empty()) return false;
854 gtl::STLSortAndRemoveDuplicates(&divisors_, std::greater<IntegerValue>());
855
856 // Note that most of the time is spend here since we call this function on
857 // many linear equation, and just a few of them have a good enough scaled
858 // violation. We can spend more time afterwards to tune the cut.
859 //
860 // TODO(user): Avoid quadratic algorithm? Note that we are quadratic in
861 // relevant positions not the full cut size, but this is still too much on
862 // some problems.
863 IntegerValue best_divisor(0);
864 double best_scaled_violation = 1e-3;
865 for (const IntegerValue divisor : divisors_) {
866 // Note that the function will abort right away if PositiveRemainder() is
867 // not good enough, so it is quick for bad divisor.
868 const double violation = GetScaledViolation(divisor, options.max_scaling,
869 remainder_threshold, cut_);
870 if (violation > best_scaled_violation) {
871 best_scaled_violation = violation;
872 best_adjusted_coeffs_ = adjusted_coeffs_;
873 best_divisor = divisor;
874 }
875 }
876 if (best_divisor == 0) return false;
877
878 // Try best_divisor divided by small number.
879 for (int div = 2; div < 9; ++div) {
880 const IntegerValue divisor = best_divisor / IntegerValue(div);
881 if (divisor <= 1) continue;
882 const double violation = GetScaledViolation(divisor, options.max_scaling,
883 remainder_threshold, cut_);
884 if (violation > best_scaled_violation) {
885 best_scaled_violation = violation;
886 best_adjusted_coeffs_ = adjusted_coeffs_;
887 best_divisor = divisor;
888 }
889 }
890
891 // Re try complementation on the transformed cut.
892 // TODO(user): This can be quadratic! we don't want to try too much of them.
893 // Or optimize the algo, we should be able to be more incremental here.
894 // see on g200x740.pb.gz for instance.
895 for (CutTerm& entry : cut_.terms) {
896 if (!entry.HasRelevantLpValue()) break;
897 if (entry.coeff % best_divisor == 0) continue;
898
899 // Temporary complement this variable.
900 entry.Complement(&cut_.rhs);
901
902 const double violation = GetScaledViolation(
903 best_divisor, options.max_scaling, remainder_threshold, cut_);
904 if (violation > best_scaled_violation) {
905 // keep the change.
906 ++total_num_post_complements_;
907 best_scaled_violation = violation;
908 best_adjusted_coeffs_ = adjusted_coeffs_;
909 } else {
910 // Restore.
911 entry.Complement(&cut_.rhs);
912 }
913 }
914
915 // Adjust coefficients as computed by the best GetScaledViolation().
916 for (const auto [index, new_coeff] : best_adjusted_coeffs_) {
917 ++total_num_coeff_adjust_;
918 CutTerm& entry = cut_.terms[index];
919 const IntegerValue remainder = new_coeff - entry.coeff;
920 CHECK_GT(remainder, 0);
921 entry.coeff = new_coeff;
922 cut_.rhs += absl::int128(remainder.value()) *
923 absl::int128(entry.bound_diff.value());
924 cut_.max_magnitude = std::max(cut_.max_magnitude, IntTypeAbs(new_coeff));
925 }
926
927 // Create the base super-additive function f().
928 const IntegerValue rhs_remainder = PositiveRemainder(cut_.rhs, best_divisor);
929 IntegerValue factor_t =
930 GetFactorT(rhs_remainder, best_divisor, cut_.max_magnitude);
931 auto f = GetSuperAdditiveRoundingFunction(rhs_remainder, best_divisor,
932 factor_t, options.max_scaling);
933
934 // Look amongst all our possible function f() for one that dominate greedily
935 // our current best one. Note that we prefer lower scaling factor since that
936 // result in a cut with lower coefficients.
937 //
938 // We only look at relevant position and ignore the other. Not sure this is
939 // the best approach.
940 remainders_.clear();
941 for (const CutTerm& entry : cut_.terms) {
942 if (!entry.HasRelevantLpValue()) break;
943 const IntegerValue coeff = entry.coeff;
944 const IntegerValue r = PositiveRemainder(coeff, best_divisor);
945 if (r > rhs_remainder) remainders_.push_back(r);
946 }
948 if (remainders_.size() <= 100) {
949 best_rs_.clear();
950 for (const IntegerValue r : remainders_) {
951 best_rs_.push_back(f(r));
952 }
953 IntegerValue best_d = f(best_divisor);
954
955 // Note that the complexity seems high 100 * 2 * options.max_scaling, but
956 // this only run on cuts that are already efficient and the inner loop tend
957 // to abort quickly. I didn't see this code in the cpu profile so far.
958 for (const IntegerValue t :
959 {IntegerValue(1),
960 GetFactorT(rhs_remainder, best_divisor, cut_.max_magnitude)}) {
961 for (IntegerValue s(2); s <= options.max_scaling; ++s) {
962 const auto g =
963 GetSuperAdditiveRoundingFunction(rhs_remainder, best_divisor, t, s);
964 int num_strictly_better = 0;
965 rs_.clear();
966 const IntegerValue d = g(best_divisor);
967 for (int i = 0; i < best_rs_.size(); ++i) {
968 const IntegerValue temp = g(remainders_[i]);
969 if (temp * best_d < best_rs_[i] * d) break;
970 if (temp * best_d > best_rs_[i] * d) num_strictly_better++;
971 rs_.push_back(temp);
972 }
973 if (rs_.size() == best_rs_.size() && num_strictly_better > 0) {
974 ++total_num_dominating_f_;
975 f = g;
976 factor_t = t;
977 best_rs_ = rs_;
978 best_d = d;
979 }
980 }
981 }
982 }
983
984 // Use implied bounds to "lift" Booleans into the cut.
985 // This should lead to stronger cuts even if the norms might be worse.
986 num_ib_used_ = 0;
987 if (ib_processor != nullptr) {
988 const auto [num_lb, num_ub, num_merges] =
989 ib_processor->PostprocessWithImpliedBound(f, factor_t, &cut_);
990 total_num_pos_lifts_ += num_lb;
991 total_num_neg_lifts_ += num_ub;
992 total_num_merges_ += num_merges;
993 num_ib_used_ = num_lb + num_ub;
994 }
995
996 // More complementation, but for the same f.
997 // If we can do that, it probably means our heuristics above are not great.
998 for (int i = 0; i < 3; ++i) {
999 const int64_t saved = total_num_final_complements_;
1000 for (CutTerm& entry : cut_.terms) {
1001 // Complementing an entry gives:
1002 // [a * X <= b] -> [-a * (diff - X) <= b - a * diff]
1003 //
1004 // We will compare what happen when we apply f:
1005 // [f(b) - f(a) * lp(X)] -> [f(b - a * diff) - f(-a) * (diff - lp(X))].
1006 //
1007 // If lp(X) is zero, then the transformation is always worse.
1008 // Because f(b - a * diff) >= f(b) + f(-a) * diff by super-additivity.
1009 //
1010 // However the larger is X, the better it gets since at diff, we have
1011 // f(b) >= f(b - a * diff) + f(a * diff) >= f(b - a * diff) + f(a) * diff.
1012 //
1013 // TODO(user): It is still unclear if we have a * X + b * (1 - X) <= rhs
1014 // for a Boolean X, what is the best way to apply f and if we should merge
1015 // the terms. If there is no other terms, best is probably
1016 // f(rhs - a) * X + f(rhs - b) * (1 - X).
1017 if (entry.coeff % best_divisor == 0) continue;
1018 if (!entry.HasRelevantLpValue()) continue;
1019
1020 // Avoid potential overflow here.
1021 const IntegerValue prod(CapProdI(entry.bound_diff, entry.coeff));
1022 const IntegerValue remainder = PositiveRemainder(cut_.rhs, best_divisor);
1023 if (ProdOverflow(factor_t, prod)) continue;
1024 if (ProdOverflow(factor_t, CapSubI(remainder, prod))) continue;
1025
1026 const double lp1 =
1027 ToDouble(f(remainder)) - ToDouble(f(entry.coeff)) * entry.lp_value;
1028 const double lp2 = ToDouble(f(remainder - prod)) -
1029 ToDouble(f(-entry.coeff)) *
1030 (ToDouble(entry.bound_diff) - entry.lp_value);
1031 if (lp2 + 1e-2 < lp1) {
1032 entry.Complement(&cut_.rhs);
1033 ++total_num_final_complements_;
1034 }
1035 }
1036 if (total_num_final_complements_ == saved) break;
1037 }
1038
1039 total_num_bumps_ += ApplyWithPotentialBump(f, best_divisor, &cut_);
1040 return true;
1041}
1042
1044 if (!VLOG_IS_ON(1)) return;
1045 if (shared_stats_ == nullptr) return;
1046
1047 std::vector<std::pair<std::string, int64_t>> stats;
1048 const auto add_stats = [&stats](absl::string_view name, const CutStats& s) {
1049 stats.push_back(
1050 {absl::StrCat(name, "num_overflows"), s.num_overflow_aborts});
1051 stats.push_back({absl::StrCat(name, "num_lifting"), s.num_lifting});
1052 stats.push_back({absl::StrCat(name, "num_initial_ib"), s.num_initial_ibs});
1053 stats.push_back({absl::StrCat(name, "num_implied_lb"), s.num_lb_ibs});
1054 stats.push_back({absl::StrCat(name, "num_implied_ub"), s.num_ub_ibs});
1055 stats.push_back({absl::StrCat(name, "num_bumps"), s.num_bumps});
1056 stats.push_back({absl::StrCat(name, "num_cuts"), s.num_cuts});
1057 stats.push_back({absl::StrCat(name, "num_merges"), s.num_merges});
1058 };
1059 add_stats("cover_cut/", cover_stats_);
1060 add_stats("flow_cut/", flow_stats_);
1061 add_stats("ls_cut/", ls_stats_);
1062 shared_stats_->AddStats(stats);
1063}
1064
1065namespace {
1066
1067struct LargeCoeffFirst {
1068 bool operator()(const CutTerm& a, const CutTerm& b) const {
1069 if (a.coeff == b.coeff) {
1070 return a.LpDistToMaxValue() > b.LpDistToMaxValue();
1071 }
1072 return a.coeff > b.coeff;
1073 }
1074};
1075
1076struct SmallContribFirst {
1077 bool operator()(const CutTerm& a, const CutTerm& b) const {
1078 const double contrib_a = a.lp_value * static_cast<double>(a.coeff.value());
1079 const double contrib_b = b.lp_value * static_cast<double>(b.coeff.value());
1080 return contrib_a < contrib_b;
1081 }
1082};
1083
1084struct LargeContribFirst {
1085 bool operator()(const CutTerm& a, const CutTerm& b) const {
1086 const double contrib_a = a.lp_value * static_cast<double>(a.coeff.value());
1087 const double contrib_b = b.lp_value * static_cast<double>(b.coeff.value());
1088 return contrib_a > contrib_b;
1089 }
1090};
1091
1092struct LargeLpValueFirst {
1093 bool operator()(const CutTerm& a, const CutTerm& b) const {
1094 if (a.lp_value == b.lp_value) {
1095 // Prefer high coefficients if the distance is the same.
1096 // We have more chance to get a cover this way.
1097 return a.coeff > b.coeff;
1098 }
1099 return a.lp_value > b.lp_value;
1100 }
1101};
1102
1103// When minimizing a cover we want to remove bad score (large dist) divided by
1104// item size. Note that here we assume item are "boolean" fully taken or not.
1105// for general int we use (lp_dist / bound_diff) / (coeff * bound_diff) which
1106// lead to the same formula as for Booleans.
1107struct KnapsackAdd {
1108 bool operator()(const CutTerm& a, const CutTerm& b) const {
1109 const double contrib_a =
1110 a.LpDistToMaxValue() / static_cast<double>(a.coeff.value());
1111 const double contrib_b =
1112 b.LpDistToMaxValue() / static_cast<double>(b.coeff.value());
1113 return contrib_a < contrib_b;
1114 }
1115};
1116struct KnapsackRemove {
1117 bool operator()(const CutTerm& a, const CutTerm& b) const {
1118 const double contrib_a =
1119 a.LpDistToMaxValue() / static_cast<double>(a.coeff.value());
1120 const double contrib_b =
1121 b.LpDistToMaxValue() / static_cast<double>(b.coeff.value());
1122 return contrib_a > contrib_b;
1123 }
1124};
1125
1126} // namespace.
1127
1128// Transform to a minimal cover. We want to greedily remove the largest coeff
1129// first, so we have more chance for the "lifting" below which can increase
1130// the cut violation. If the coeff are the same, we prefer to remove high
1131// distance from upper bound first.
1132template <class Compare>
1133int CoverCutHelper::MinimizeCover(int cover_size, absl::int128 slack) {
1134 CHECK_GT(slack, 0);
1135 absl::Span<CutTerm> terms = absl::MakeSpan(cut_.terms);
1136 std::sort(terms.begin(), terms.begin() + cover_size, Compare());
1137 for (int i = 0; i < cover_size;) {
1138 const CutTerm& t = terms[i];
1139 const absl::int128 contrib =
1140 absl::int128(t.bound_diff.value()) * absl::int128(t.coeff.value());
1141 if (contrib < slack) {
1142 slack -= contrib;
1143 std::swap(terms[i], terms[--cover_size]);
1144 } else {
1145 ++i;
1146 }
1147 }
1148 DCHECK_GT(cover_size, 0);
1149 return cover_size;
1150}
1151
1152template <class CompareAdd, class CompareRemove>
1153int CoverCutHelper::GetCoverSize(int relevant_size) {
1154 if (relevant_size == 0) return 0;
1155 absl::Span<CutTerm> terms = absl::MakeSpan(cut_.terms);
1156
1157 // Take first all at variable at upper bound, and ignore the one at lower
1158 // bound.
1159 int part1 = 0;
1160 for (int i = 0; i < relevant_size;) {
1161 CutTerm& term = terms[i];
1162 const double dist = term.LpDistToMaxValue();
1163 if (dist < 1e-6) {
1164 // Move to part 1.
1165 std::swap(term, terms[part1]);
1166 ++i;
1167 ++part1;
1168 } else if (term.lp_value > 1e-6) {
1169 // Keep in part 2.
1170 ++i;
1171 } else {
1172 // Exclude entirely (part 3).
1173 --relevant_size;
1174 std::swap(term, terms[relevant_size]);
1175 }
1176 }
1177 std::sort(terms.begin() + part1, terms.begin() + relevant_size, CompareAdd());
1178
1179 // We substract the initial rhs to avoid overflow.
1180 DCHECK_GE(cut_.rhs, 0);
1181 absl::int128 max_shifted_activity = -cut_.rhs;
1182 absl::int128 shifted_round_up = -cut_.rhs;
1183 int cover_size = 0;
1184 for (; cover_size < relevant_size; ++cover_size) {
1185 if (max_shifted_activity > 0) break;
1186 const CutTerm& term = terms[cover_size];
1187 max_shifted_activity += absl::int128(term.coeff.value()) *
1188 absl::int128(term.bound_diff.value());
1189 shifted_round_up += absl::int128(term.coeff.value()) *
1190 std::min(absl::int128(term.bound_diff.value()),
1191 absl::int128(std::ceil(term.lp_value - 1e-6)));
1192 }
1193
1194 DCHECK_GE(cover_size, 0);
1195 if (shifted_round_up <= 0) {
1196 return 0;
1197 }
1198 return MinimizeCover<CompareRemove>(cover_size, max_shifted_activity);
1199}
1200
1201// Try a simple cover heuristic.
1202// Look for violated CUT of the form: sum (UB - X) or (X - LB) >= 1.
1203int CoverCutHelper::GetCoverSizeForBooleans() {
1204 absl::Span<CutTerm> terms = absl::MakeSpan(cut_.terms);
1205
1206 // Sorting can be slow, so we start by splitting the vector in 3 parts
1207 // - Can always be in cover
1208 // - Candidates that needs sorting
1209 // - At most one can be in cover (we keep the max).
1210 int part1 = 0;
1211 int relevant_size = terms.size();
1212 int best_in_part3 = -1;
1213 const double threshold = 1.0 - 1.0 / static_cast<double>(terms.size());
1214 for (int i = 0; i < relevant_size;) {
1215 const double lp_value = terms[i].lp_value;
1216
1217 // Exclude non-Boolean.
1218 if (terms[i].bound_diff > 1) {
1219 --relevant_size;
1220 std::swap(terms[i], terms[relevant_size]);
1221 continue;
1222 }
1223
1224 if (lp_value >= threshold) {
1225 // Move to part 1.
1226 std::swap(terms[i], terms[part1]);
1227 ++i;
1228 ++part1;
1229 } else if (lp_value > 0.5) {
1230 // Keep in part 2.
1231 ++i;
1232 } else {
1233 // Only keep the max (part 3).
1234 --relevant_size;
1235 std::swap(terms[i], terms[relevant_size]);
1236
1237 if (best_in_part3 == -1 ||
1238 LargeLpValueFirst()(terms[relevant_size], terms[best_in_part3])) {
1239 best_in_part3 = relevant_size;
1240 }
1241 }
1242 }
1243
1244 if (best_in_part3 != -1) {
1245 std::swap(terms[relevant_size], terms[best_in_part3]);
1246 ++relevant_size;
1247 }
1248
1249 // Sort by decreasing Lp value.
1250 std::sort(terms.begin() + part1, terms.begin() + relevant_size,
1251 LargeLpValueFirst());
1252
1253 double activity = 0.0;
1254 int cover_size = relevant_size;
1255 absl::int128 slack = -cut_.rhs;
1256 for (int i = 0; i < relevant_size; ++i) {
1257 const CutTerm& term = terms[i];
1258 activity += term.LpDistToMaxValue();
1259
1260 // As an heuristic we select all the term so that the sum of distance
1261 // to the upper bound is <= 1.0. If the corresponding slack is positive,
1262 // then we will have a cut of violation at least 0.0. Note that this
1263 // violation can be improved by the lifting.
1264 //
1265 // TODO(user): experiment with different threshold (even greater than one).
1266 // Or come up with an algo that incorporate the lifting into the heuristic.
1267 if (activity > 0.9999) {
1268 cover_size = i; // before this entry.
1269 break;
1270 }
1271
1272 // TODO(user): Stop if we overflow int128 max! Note that because we scale
1273 // things so that the max coeff is 2^52, this is unlikely.
1274 slack += absl::int128(term.coeff.value()) *
1275 absl::int128(term.bound_diff.value());
1276 }
1277
1278 // If the rhs is now negative, we have a cut.
1279 //
1280 // Note(user): past this point, now that a given "base" cover has been chosen,
1281 // we basically compute the cut (of the form sum X <= bound) with the maximum
1282 // possible violation. Note also that we lift as much as possible, so we don't
1283 // necessarily optimize for the cut efficacity though. But we do get a
1284 // stronger cut.
1285 if (slack <= 0) {
1286 return 0;
1287 }
1288 if (cover_size == 0) return 0;
1289 return MinimizeCover<LargeCoeffFirst>(cover_size, slack);
1290}
1291
1292void CoverCutHelper::InitializeCut(const CutData& input_ct) {
1293 num_lifting_ = 0;
1294 cut_ = input_ct;
1295
1296 // We should have dealt with an infeasible constraint before.
1297 // Note that because of our scaling, it is unlikely we will overflow int128.
1298 CHECK_GE(cut_.rhs, 0);
1299 DCHECK(cut_.AllCoefficientsArePositive());
1300}
1301
1303 ImpliedBoundsProcessor* ib_processor) {
1304 InitializeCut(input_ct);
1305
1306 // Tricky: This only work because the cut absl128 rhs is not changed by these
1307 // operations.
1308 if (ib_processor != nullptr) {
1309 std::vector<CutTerm>* new_bool_terms =
1310 ib_processor->ClearedMutableTempTerms();
1311 for (CutTerm& term : cut_.terms) {
1312 // We only look at non-Boolean with an lp value not close to the upper
1313 // bound.
1314 if (term.bound_diff <= 1) continue;
1315 if (term.lp_value + 1e-4 > AsDouble(term.bound_diff)) continue;
1316
1317 if (ib_processor->TryToExpandWithLowerImpliedbound(
1318 IntegerValue(1),
1319 /*complement=*/false, &term, &cut_.rhs, new_bool_terms)) {
1320 ++cover_stats_.num_initial_ibs;
1321 }
1322 }
1323
1325 absl::MakeSpan(*new_bool_terms), IntegerValue(1), &cut_);
1326 }
1327
1328 bool has_relevant_int = false;
1329 for (const CutTerm& term : cut_.terms) {
1330 if (term.HasRelevantLpValue() && term.bound_diff > 1) {
1331 has_relevant_int = true;
1332 break;
1333 }
1334 }
1335
1336 const int base_size = static_cast<int>(cut_.terms.size());
1337 const int cover_size =
1338 has_relevant_int
1339 ? GetCoverSize<LargeContribFirst, LargeCoeffFirst>(base_size)
1340 : GetCoverSizeForBooleans();
1341 if (!has_relevant_int && ib_processor == nullptr) {
1342 // If some implied bound substitution are possible, we do not cache anything
1343 // currently because the logic is currently sighlty different between the
1344 // two code. Fix?
1345 has_bool_base_ct_ = true;
1346 bool_cover_size_ = cover_size;
1347 if (cover_size == 0) return false;
1348 bool_base_ct_ = cut_;
1349 }
1350 if (cover_size == 0) return false;
1351
1352 // The cut is just obtained by complementing the variable in the cover and
1353 // applying the MIR super additive function.
1354 //
1355 // Note that since all coeff in the cover will now be negative. If we do no
1356 // scaling, and if we use max_coeff_in_cover to construct f(), they will be
1357 // mapped by f() to -1 and we get the classical cover inequality. With scaling
1358 // we can get a strictly dominating cut though.
1359 //
1360 // TODO(user): we don't have to pick max_coeff_in_cover and could use the
1361 // coefficient of the most fractional variable. Or like in the MIR code try
1362 // a few of them. Currently, the cut in the test is worse if we don't take
1363 // the max_coeff_in_cover though, so we need more understanding.
1364 //
1365 // TODO(user): It seems we could use a more advanced lifting function
1366 // described later in the paper. Investigate.
1367 IntegerValue best_coeff = 0;
1368 double best_score = -1.0;
1369 IntegerValue max_coeff_in_cover(0);
1370 for (int i = 0; i < cover_size; ++i) {
1371 CutTerm& term = cut_.terms[i];
1372 max_coeff_in_cover = std::max(max_coeff_in_cover, term.coeff);
1373 const double score =
1374 std::abs(term.lp_value - std::floor(term.lp_value + 1e-6)) *
1375 ToDouble(term.coeff);
1376 if (score > best_score) {
1377 best_score = score;
1378 best_coeff = term.coeff;
1379 }
1380 term.Complement(&cut_.rhs);
1381 }
1382 CHECK_LT(cut_.rhs, 0); // Because we complemented a cover.
1383
1384 // TODO(user): Experiment without this line that basically disable scoring.
1385 best_coeff = max_coeff_in_cover;
1386
1387 // TODO(user): experiment with different value of scaling and param t.
1388 std::function<IntegerValue(IntegerValue)> f;
1389 {
1390 IntegerValue max_magnitude = 0;
1391 for (const CutTerm& term : cut_.terms) {
1392 max_magnitude = std::max(max_magnitude, IntTypeAbs(term.coeff));
1393 }
1394 const IntegerValue max_scaling(std::min(
1395 IntegerValue(6000), FloorRatio(kMaxIntegerValue, max_magnitude)));
1396 const IntegerValue remainder = PositiveRemainder(cut_.rhs, best_coeff);
1397 f = GetSuperAdditiveRoundingFunction(remainder, best_coeff, IntegerValue(1),
1398 max_scaling);
1399 }
1400
1401 if (ib_processor != nullptr) {
1402 const auto [num_lb, num_ub, num_merges] =
1403 ib_processor->PostprocessWithImpliedBound(f, /*factor_t=*/1, &cut_);
1404 cover_stats_.num_lb_ibs += num_lb;
1405 cover_stats_.num_ub_ibs += num_ub;
1406 cover_stats_.num_merges += num_merges;
1407 }
1408
1409 cover_stats_.num_bumps += ApplyWithPotentialBump(f, best_coeff, &cut_);
1410
1411 // Update counters.
1412 for (int i = cover_size; i < cut_.terms.size(); ++i) {
1413 if (cut_.terms[i].coeff != 0) ++num_lifting_;
1414 }
1415 cover_stats_.num_lifting += num_lifting_;
1416 ++cover_stats_.num_cuts;
1417 return true;
1418}
1419
1421 ImpliedBoundsProcessor* ib_processor) {
1422 InitializeCut(input_ct);
1423
1424 // TODO(user): Change the heuristic to depends on the lp_value of the implied
1425 // bounds. This way we can exactly match what happen in the old
1426 // FlowCoverCutHelper.
1427 const int base_size = static_cast<int>(cut_.terms.size());
1428 const int cover_size = GetCoverSize<KnapsackAdd, KnapsackRemove>(base_size);
1429 if (cover_size == 0) return false;
1430
1431 // After complementing the term in the cover, we have
1432 // sum -ci.X + other_terms <= -slack;
1433 for (int i = 0; i < cover_size; ++i) {
1434 cut_.terms[i].Complement(&cut_.rhs);
1435
1436 // We do not support complex terms, but we shouldn't get any.
1437 if (cut_.terms[i].expr_coeffs[1] != 0) return false;
1438 }
1439
1440 // The algorithm goes as follow:
1441 // - Compute heuristically a minimal cover.
1442 // - We have sum_cover ci.Xi >= slack where Xi is distance to upper bound.
1443 // - Apply coefficient strenghtening if ci > slack.
1444 //
1445 // Using implied bound we have two cases (all coeffs positive):
1446 // 1/ ci.Xi = ci.fi.Bi + ci.Si : always good.
1447 // 2/ ci.Xi = ci.di.Bi - ci.Si <= di.Bi: good if Si lp_value is zero.
1448 //
1449 // Note that if everything is Boolean, we just get a normal cover and coeff
1450 // strengthening just result in all coeff at 1, so worse than our cover
1451 // heuristic.
1452 CHECK_LT(cut_.rhs, 0);
1453 if (cut_.rhs <= absl::int128(std::numeric_limits<int64_t>::min())) {
1454 return false;
1455 }
1456
1457 bool has_large_coeff = false;
1458 for (const CutTerm& term : cut_.terms) {
1459 if (IntTypeAbs(term.coeff) > 1'000'000) {
1460 has_large_coeff = true;
1461 break;
1462 }
1463 }
1464
1465 // TODO(user): Shouldn't we just use rounding f() with maximum coeff to allows
1466 // lift of all other terms? but then except for the heuristic the cut is
1467 // really similar to the cover cut.
1468 const IntegerValue positive_rhs = -static_cast<int64_t>(cut_.rhs);
1469 IntegerValue min_magnitude = kMaxIntegerValue;
1470 for (int i = 0; i < cover_size; ++i) {
1471 const IntegerValue magnitude = IntTypeAbs(cut_.terms[i].coeff);
1472 min_magnitude = std::min(min_magnitude, magnitude);
1473 }
1474 const bool use_scaling =
1475 has_large_coeff || min_magnitude == 1 || min_magnitude >= positive_rhs;
1476 auto f = use_scaling ? GetSuperAdditiveStrengtheningMirFunction(
1477 positive_rhs, /*scaling=*/6000)
1479 min_magnitude);
1480
1481 if (ib_processor != nullptr) {
1482 const auto [num_lb, num_ub, num_merges] =
1483 ib_processor->PostprocessWithImpliedBound(f, /*factor_t=*/1, &cut_);
1484 flow_stats_.num_lb_ibs += num_lb;
1485 flow_stats_.num_ub_ibs += num_ub;
1486 flow_stats_.num_merges += num_merges;
1487 }
1488
1489 // Lifting.
1490 {
1491 IntegerValue period = positive_rhs;
1492 for (const CutTerm& term : cut_.terms) {
1493 if (term.coeff > 0) continue;
1494 period = std::max(period, -term.coeff);
1495 }
1496
1497 // Compute good period.
1498 // We don't want to extend it in the simpler case where f(x)=-1 if x < 0.
1499 //
1500 // TODO(user): If the Mir*() function is used, we don't need to extend that
1501 // much the period. Fix.
1502 if (f(-period + FloorRatio(period, 2)) != f(-period)) {
1503 // TODO(user): do exact binary search to find highest x in
1504 // [-max_neg_magnitude, 0] such that f(x) == f(-max_neg_magnitude) ? not
1505 // really needed though since we know that we have this equality:
1506 CHECK_EQ(f(-period), f(-positive_rhs));
1507 period = std::max(period, CapProdI(2, positive_rhs) - 1);
1508 }
1509
1510 f = ExtendNegativeFunction(f, period);
1511 }
1512
1513 // Generate the cut.
1514 cut_.rhs = absl::int128(f(-positive_rhs).value());
1515 for (CutTerm& term : cut_.terms) {
1516 const IntegerValue old_coeff = term.coeff;
1517 term.coeff = f(term.coeff);
1518 if (old_coeff > 0 && term.coeff != 0) ++flow_stats_.num_lifting;
1519 }
1520 ++flow_stats_.num_cuts;
1521 return true;
1522}
1523
1525 const CutData& input_ct, ImpliedBoundsProcessor* ib_processor) {
1526 int cover_size;
1527 if (has_bool_base_ct_) {
1528 // We already called GetCoverSizeForBooleans() and ib_processor was nullptr,
1529 // so reuse that info.
1530 CHECK(ib_processor == nullptr);
1531 cover_size = bool_cover_size_;
1532 if (cover_size == 0) return false;
1533 InitializeCut(bool_base_ct_);
1534 } else {
1535 InitializeCut(input_ct);
1536
1537 // Perform IB expansion with no restriction, all coeff should still be
1538 // positive.
1539 //
1540 // TODO(user): Merge Boolean terms that are complement of each other.
1541 if (ib_processor != nullptr) {
1542 std::vector<CutTerm>* new_bool_terms =
1543 ib_processor->ClearedMutableTempTerms();
1544 for (CutTerm& term : cut_.terms) {
1545 if (term.bound_diff <= 1) continue;
1546 if (ib_processor->TryToExpandWithLowerImpliedbound(
1547 IntegerValue(1),
1548 /*complement=*/false, &term, &cut_.rhs, new_bool_terms)) {
1549 ++ls_stats_.num_initial_ibs;
1550 }
1551 }
1552
1554 absl::MakeSpan(*new_bool_terms), IntegerValue(1), &cut_);
1555 }
1556
1557 // TODO(user): we currently only deal with Boolean in the cover. Fix.
1558 cover_size = GetCoverSizeForBooleans();
1559 }
1560 if (cover_size == 0) return false;
1561
1562 // We don't support big rhs here.
1563 // Note however than since this only deal with Booleans, it is less likely.
1564 if (cut_.rhs > absl::int128(std::numeric_limits<int64_t>::max())) {
1565 ++ls_stats_.num_overflow_aborts;
1566 return false;
1567 }
1568 const IntegerValue rhs = static_cast<int64_t>(cut_.rhs);
1569
1570 // Collect the weight in the cover.
1571 IntegerValue sum(0);
1572 std::vector<IntegerValue> cover_weights;
1573 for (int i = 0; i < cover_size; ++i) {
1574 CHECK_EQ(cut_.terms[i].bound_diff, 1);
1575 CHECK_GT(cut_.terms[i].coeff, 0);
1576 cover_weights.push_back(cut_.terms[i].coeff);
1577 sum = CapAddI(sum, cut_.terms[i].coeff);
1578 }
1579 if (AtMinOrMaxInt64(sum.value())) {
1580 ++ls_stats_.num_overflow_aborts;
1581 return false;
1582 }
1583 CHECK_GT(sum, rhs);
1584
1585 // Compute the correct threshold so that if we round down larger weights to
1586 // p/q. We have sum of the weight in cover == base_rhs.
1587 IntegerValue p(0);
1588 IntegerValue q(0);
1589 IntegerValue previous_sum(0);
1590 std::sort(cover_weights.begin(), cover_weights.end());
1591 for (int i = 0; i < cover_size; ++i) {
1592 q = IntegerValue(cover_weights.size() - i);
1593 if (previous_sum + cover_weights[i] * q > rhs) {
1594 p = rhs - previous_sum;
1595 break;
1596 }
1597 previous_sum += cover_weights[i];
1598 }
1599 CHECK_GE(q, 1);
1600
1601 // Compute thresholds.
1602 // For the first q values, thresholds[i] is the smallest integer such that
1603 // q * threshold[i] > p * (i + 1).
1604 std::vector<IntegerValue> thresholds;
1605 for (int i = 0; i < q; ++i) {
1606 // TODO(user): compute this in an overflow-safe way.
1607 if (CapProd(p.value(), i + 1) >= std::numeric_limits<int64_t>::max() - 1) {
1608 ++ls_stats_.num_overflow_aborts;
1609 return false;
1610 }
1611 thresholds.push_back(CeilRatio(p * (i + 1) + 1, q));
1612 }
1613
1614 // For the other values, we just add the weights.
1615 std::reverse(cover_weights.begin(), cover_weights.end());
1616 for (int i = q.value(); i < cover_size; ++i) {
1617 thresholds.push_back(thresholds.back() + cover_weights[i]);
1618 }
1619 CHECK_EQ(thresholds.back(), rhs + 1);
1620
1621 // Generate the cut.
1622 //
1623 // Our algo is quadratic in worst case, but large coefficients should be
1624 // rare, and in practice we don't really see this.
1625 //
1626 // Note that this work for non-Boolean since we can just "theorically" split
1627 // them as a sum of Booleans :) Probably a cleaner proof exist by just using
1628 // the super-additivity of the lifting function on [0, rhs].
1629 temp_cut_.rhs = cover_size - 1;
1630 temp_cut_.terms.clear();
1631
1632 const int base_size = static_cast<int>(cut_.terms.size());
1633 for (int i = 0; i < base_size; ++i) {
1634 const CutTerm& term = cut_.terms[i];
1635 const IntegerValue coeff = term.coeff;
1636 IntegerValue cut_coeff(1);
1637 if (coeff < thresholds[0]) {
1638 if (i >= cover_size) continue;
1639 } else {
1640 // Find the largest index <= coeff.
1641 //
1642 // TODO(user): For exact multiple of p/q we can increase the coeff by 1/2.
1643 // See section in the paper on getting maximal super additive function.
1644 for (int i = 1; i < cover_size; ++i) {
1645 if (coeff < thresholds[i]) break;
1646 cut_coeff = IntegerValue(i + 1);
1647 }
1648 if (cut_coeff != 0 && i >= cover_size) ++ls_stats_.num_lifting;
1649 if (cut_coeff > 1 && i < cover_size) ++ls_stats_.num_lifting; // happen?
1650 }
1651
1652 temp_cut_.terms.push_back(term);
1653 temp_cut_.terms.back().coeff = cut_coeff;
1654 }
1655
1656 cut_ = temp_cut_;
1657 ++ls_stats_.num_cuts;
1658 return true;
1659}
1660
1662 if (!VLOG_IS_ON(1)) return;
1663 std::vector<std::pair<std::string, int64_t>> stats;
1664 stats.push_back({"bool_rlt/num_tried", num_tried_});
1665 stats.push_back({"bool_rlt/num_tried_factors", num_tried_factors_});
1666 shared_stats_->AddStats(stats);
1667}
1668
1669void BoolRLTCutHelper::Initialize(absl::Span<const IntegerVariable> lp_vars) {
1670 product_detector_->InitializeBooleanRLTCuts(lp_vars, *lp_values_);
1671 enabled_ = !product_detector_->BoolRLTCandidates().empty();
1672}
1673
1674// TODO(user): do less work, add more stats.
1676 if (!enabled_) return false;
1677
1678 ++num_tried_;
1679 DCHECK(input_ct.AllCoefficientsArePositive());
1680
1681 // We will list the interesting "factor" to try to multiply + linearize the
1682 // input constraint with.
1683 absl::flat_hash_set<IntegerVariable> to_try_set;
1684 std::vector<IntegerVariable> to_try;
1685
1686 // We can look at the linearized factor * term and bound the activity delta
1687 // rescaled by 1 / factor.
1688 //
1689 // CASE linearized_term delta = term/factor - previous
1690 //
1691 // DROP, 0 0 - X
1692 // MC_CORMICK, factor * ub - (ub - X) (ub - X) * (1 - 1/factor) <= 0
1693 // SQUARE, factor=X 1 - X
1694 // RLT, factor - P 1 - X - P/X <= 1 - X
1695 //
1696 // TODO(user): detect earlier that a factor is not worth checking because
1697 // we already loose too much with the DROP/MC_CORMICK cases ? Filter more ?
1698 // I think we can probably evaluate the factor efficiency during the first
1699 // loop which usually have a small complexity compared to num_factor_to_try
1700 // times num filtered terms.
1701 filtered_input_.terms.clear();
1702 filtered_input_.rhs = input_ct.rhs;
1703
1704 const auto& candidates = product_detector_->BoolRLTCandidates();
1705 for (const CutTerm& term : input_ct.terms) {
1706 // The only options are DROP or MC_CORMICK, but the later will unlikely win.
1707 //
1708 // TODO(user): we never use factor with lp value < 1e-4, but we could use a
1709 // factor equal to 1.0 I think. Double check.
1710 if (!term.IsBoolean() && term.lp_value <= 1e-6) {
1711 continue;
1712 }
1713
1714 // Here the MC_CORMICK will not loose much. And SQUARE or RLT cannot win
1715 // much, so we can assume there is no loss and just look for violated
1716 // subconstraint.
1717 if (term.LpDistToMaxValue() <= 1e-6) {
1718 filtered_input_.rhs -= absl::int128(term.coeff.value()) *
1719 absl::int128(term.bound_diff.value());
1720 continue;
1721 }
1722
1723 // Convert to var or -var (to mean 1 - var).
1724 //
1725 // TODO(user): We could keep for each factor the max gain, so that we
1726 // can decided if it is not even worth trying a factor.
1727 const IntegerVariable var = term.GetUnderlyingLiteralOrNone();
1728 if (var != kNoIntegerVariable && candidates.contains(NegationOf(var))) {
1729 for (const IntegerVariable factor : candidates.at(NegationOf(var))) {
1730 if (to_try_set.insert(factor).second) to_try.push_back(factor);
1731 }
1732 }
1733 filtered_input_.terms.push_back(term);
1734 }
1735
1736 // TODO(user): Avoid constructing the cut just to evaluate its efficacy.
1737 double best_efficacy = 1e-3;
1738 IntegerVariable best_factor = kNoIntegerVariable;
1739 for (const IntegerVariable factor : to_try) {
1740 ++num_tried_factors_;
1741 if (!TryProduct(factor, filtered_input_)) continue;
1742 const double efficacy = cut_.ComputeEfficacy();
1743 if (efficacy > best_efficacy) {
1744 best_efficacy = efficacy;
1745 best_factor = factor;
1746 }
1747 }
1748
1749 // If we found a good factor, applies it to the non-filtered base constraint.
1750 if (best_factor != kNoIntegerVariable) {
1751 return TryProduct(best_factor, input_ct);
1752 }
1753 return false;
1754}
1755
1756namespace {
1757
1758// Each bool * term can be linearized in a couple of way.
1759// We will choose the best one.
1760enum class LinearizationOption {
1761 DROP,
1762 MC_CORMICK,
1763 RLT,
1764 SQUARE,
1765};
1766
1767} // namespace
1768
1769double BoolRLTCutHelper::GetLiteralLpValue(IntegerVariable var) const {
1770 if (VariableIsPositive(var)) return (*lp_values_)[var];
1771 return 1.0 - (*lp_values_)[PositiveVariable(var)];
1772}
1773
1774bool BoolRLTCutHelper::TryProduct(IntegerVariable factor,
1775 const CutData& input) {
1776 cut_.terms.clear();
1777 cut_.rhs = 0;
1778 absl::int128 old_rhs = input.rhs;
1779
1780 const double factor_lp = GetLiteralLpValue(factor);
1781
1782 // Consider each term in sequence and linearize them.
1783 for (CutTerm term : input.terms) {
1784 LinearizationOption best_option = LinearizationOption::DROP;
1785
1786 // Recover the "IntegerVariable" literal if any.
1787 const IntegerVariable var = term.GetUnderlyingLiteralOrNone();
1788 const bool is_literal = var != kNoIntegerVariable;
1789
1790 // First option is if factor == var.
1791 if (factor == var) {
1792 // The term can be left unchanged.
1793 // Note that we "win" (factor_lp - factor_lp ^ 2) * coeff activity.
1794 best_option = LinearizationOption::SQUARE;
1795 cut_.terms.push_back(term);
1796 continue;
1797 }
1798
1799 // If factor == NegationOf(var) our best linearization is unfortunately
1800 // just product >= 0.
1801 if (NegationOf(factor) == var) {
1802 best_option = LinearizationOption::DROP;
1803 continue;
1804 }
1805
1806 // TODO(user): If l implies x and y, we have x * y >= l.
1807 // We have to choose l as high as possible if multiple choices.
1808
1809 // We start by the lp value for the drop option: simply dropping the term
1810 // since we know it is >= 0. We will choose the option with the highest
1811 // lp value, which is the one that "loose" the least activity.
1812 double best_lp = 0.0;
1813
1814 // Second option, is complement it and use x * (1 - y) <= (1 - y):
1815 // x * [1 - (1 - y)] = x - x * (1 - y) >= x - (1 - y)
1816 const double complement_lp =
1817 static_cast<double>(term.bound_diff.value()) - term.lp_value;
1818 const double mc_cormick_lp = factor_lp - complement_lp;
1819 if (mc_cormick_lp > best_lp) {
1820 best_option = LinearizationOption::MC_CORMICK;
1821 best_lp = mc_cormick_lp;
1822 }
1823
1824 // Last option is complement it and use a relation x * (1 - y) <= u.
1825 // so the lp is x - u. Note that this can be higher than x * y if the
1826 // bilinear relation is violated by the lp solution.
1827 if (is_literal) {
1828 // TODO(user): only consider variable within current lp.
1829 const IntegerVariable ub_lit =
1830 product_detector_->LiteralProductUpperBound(factor, NegationOf(var));
1831 if (ub_lit != kNoIntegerVariable) {
1832 const double lit_lp = GetLiteralLpValue(ub_lit);
1833 if (factor_lp - lit_lp > best_lp) {
1834 // We do it right away since we have all we need.
1835 best_option = LinearizationOption::RLT;
1836
1837 // First complement to update rhs.
1838 term.Complement(&old_rhs);
1839
1840 // Now we replace the term data.
1841 term.lp_value = lit_lp;
1842 term.ReplaceExpressionByLiteral(ub_lit);
1843 cut_.terms.push_back(term);
1844 continue;
1845 }
1846 }
1847 }
1848
1849 if (best_option == LinearizationOption::DROP) continue;
1850
1851 // We just keep the complemented term.
1852 CHECK(best_option == LinearizationOption::MC_CORMICK);
1853 term.Complement(&old_rhs);
1854 cut_.terms.push_back(term);
1855 }
1856
1857 // Finally we add the - factor * old_rhs term.
1858 // We can only do that if the old_rhs is not too big.
1859 //
1860 // TODO(user): Avoid right away large constraint coming from gomory...
1861 const absl::int128 limit(int64_t{1} << 50);
1862 if (old_rhs > limit || old_rhs < -limit) return false;
1863
1864 CutTerm term;
1865 term.coeff = -IntegerValue(static_cast<int64_t>(old_rhs));
1866 term.lp_value = factor_lp;
1867 term.bound_diff = 1;
1868 term.ReplaceExpressionByLiteral(factor);
1869 cut_.terms.push_back(term);
1870
1871 return true;
1872}
1873
1877 int linearization_level,
1878 Model* model) {
1879 CutGenerator result;
1880 if (z.var != kNoIntegerVariable) result.vars.push_back(z.var);
1881 if (x.var != kNoIntegerVariable) result.vars.push_back(x.var);
1882 if (y.var != kNoIntegerVariable) result.vars.push_back(y.var);
1883
1884 IntegerTrail* const integer_trail = model->GetOrCreate<IntegerTrail>();
1885 Trail* trail = model->GetOrCreate<Trail>();
1886
1887 result.generate_cuts = [z, x, y, linearization_level, model, trail,
1888 integer_trail](LinearConstraintManager* manager) {
1889 if (trail->CurrentDecisionLevel() > 0 && linearization_level == 1) {
1890 return true;
1891 }
1892 const int64_t x_lb = integer_trail->LevelZeroLowerBound(x).value();
1893 const int64_t x_ub = integer_trail->LevelZeroUpperBound(x).value();
1894 const int64_t y_lb = integer_trail->LevelZeroLowerBound(y).value();
1895 const int64_t y_ub = integer_trail->LevelZeroUpperBound(y).value();
1896
1897 // if x or y are fixed, the McCormick equations are exact.
1898 if (x_lb == x_ub || y_lb == y_ub) return true;
1899
1900 // Check for overflow with the product of expression bounds and the
1901 // product of one expression bound times the constant part of the other
1902 // expression.
1903 const int64_t x_max_amp = std::max(std::abs(x_lb), std::abs(x_ub));
1904 const int64_t y_max_amp = std::max(std::abs(y_lb), std::abs(y_ub));
1905 constexpr int64_t kMaxSafeInteger = (int64_t{1} << 53) - 1;
1906 if (CapProd(y_max_amp, x_max_amp) > kMaxSafeInteger) return true;
1907 if (CapProd(y_max_amp, std::abs(x.constant.value())) > kMaxSafeInteger) {
1908 return true;
1909 }
1910 if (CapProd(x_max_amp, std::abs(y.constant.value())) > kMaxSafeInteger) {
1911 return true;
1912 }
1913
1914 const auto& lp_values = manager->LpValues();
1915 const double x_lp_value = x.LpValue(lp_values);
1916 const double y_lp_value = y.LpValue(lp_values);
1917 const double z_lp_value = z.LpValue(lp_values);
1918
1919 // TODO(user): As the bounds change monotonically, these cuts
1920 // dominate any previous one. try to keep a reference to the cut and
1921 // replace it. Alternatively, add an API for a level-zero bound change
1922 // callback.
1923
1924 // Cut -z + x_coeff * x + y_coeff* y <= rhs
1925 auto try_add_above_cut = [&](int64_t x_coeff, int64_t y_coeff,
1926 int64_t rhs) {
1927 if (-z_lp_value + x_lp_value * x_coeff + y_lp_value * y_coeff >=
1928 rhs + kMinCutViolation) {
1929 LinearConstraintBuilder cut(model, /*lb=*/kMinIntegerValue,
1930 /*ub=*/IntegerValue(rhs));
1931 cut.AddTerm(z, IntegerValue(-1));
1932 if (x_coeff != 0) cut.AddTerm(x, IntegerValue(x_coeff));
1933 if (y_coeff != 0) cut.AddTerm(y, IntegerValue(y_coeff));
1934 manager->AddCut(cut.Build(), "PositiveProduct");
1935 }
1936 };
1937
1938 // Cut -z + x_coeff * x + y_coeff* y >= rhs
1939 auto try_add_below_cut = [&](int64_t x_coeff, int64_t y_coeff,
1940 int64_t rhs) {
1941 if (-z_lp_value + x_lp_value * x_coeff + y_lp_value * y_coeff <=
1942 rhs - kMinCutViolation) {
1943 LinearConstraintBuilder cut(model, /*lb=*/IntegerValue(rhs),
1944 /*ub=*/kMaxIntegerValue);
1945 cut.AddTerm(z, IntegerValue(-1));
1946 if (x_coeff != 0) cut.AddTerm(x, IntegerValue(x_coeff));
1947 if (y_coeff != 0) cut.AddTerm(y, IntegerValue(y_coeff));
1948 manager->AddCut(cut.Build(), "PositiveProduct");
1949 }
1950 };
1951
1952 // McCormick relaxation of bilinear constraints. These 4 cuts are the
1953 // exact facets of the x * y polyhedron for a bounded x and y.
1954 //
1955 // Each cut correspond to plane that contains two of the line
1956 // (x=x_lb), (x=x_ub), (y=y_lb), (y=y_ub). The easiest to
1957 // understand them is to draw the x*y curves and see the 4
1958 // planes that correspond to the convex hull of the graph.
1959 try_add_above_cut(y_lb, x_lb, x_lb * y_lb);
1960 try_add_above_cut(y_ub, x_ub, x_ub * y_ub);
1961 try_add_below_cut(y_ub, x_lb, x_lb * y_ub);
1962 try_add_below_cut(y_lb, x_ub, x_ub * y_lb);
1963 return true;
1964 };
1965
1966 return result;
1967}
1968
1970 AffineExpression square,
1971 IntegerValue x_lb,
1972 IntegerValue x_ub, Model* model) {
1973 const IntegerValue above_slope = x_ub + x_lb;
1974 LinearConstraintBuilder above_hyperplan(model, kMinIntegerValue,
1975 -x_lb * x_ub);
1976 above_hyperplan.AddTerm(square, 1);
1977 above_hyperplan.AddTerm(x, -above_slope);
1978 return above_hyperplan.Build();
1979}
1980
1982 AffineExpression square,
1983 IntegerValue x_value,
1984 Model* model) {
1985 const IntegerValue below_slope = 2 * x_value + 1;
1986 LinearConstraintBuilder below_hyperplan(model, -x_value - x_value * x_value,
1988 below_hyperplan.AddTerm(square, 1);
1989 below_hyperplan.AddTerm(x, -below_slope);
1990 return below_hyperplan.Build();
1991}
1992
1994 int linearization_level, Model* model) {
1995 CutGenerator result;
1996 if (x.var != kNoIntegerVariable) result.vars.push_back(x.var);
1997 if (y.var != kNoIntegerVariable) result.vars.push_back(y.var);
1998
1999 Trail* trail = model->GetOrCreate<Trail>();
2000 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
2001 result.generate_cuts = [y, x, linearization_level, trail, integer_trail,
2002 model](LinearConstraintManager* manager) {
2003 if (trail->CurrentDecisionLevel() > 0 && linearization_level == 1) {
2004 return true;
2005 }
2006 const IntegerValue x_ub = integer_trail->LevelZeroUpperBound(x);
2007 const IntegerValue x_lb = integer_trail->LevelZeroLowerBound(x);
2008 DCHECK_GE(x_lb, 0);
2009
2010 if (x_lb == x_ub) return true;
2011
2012 // Check for potential overflows.
2013 if (x_ub > (int64_t{1} << 31)) return true;
2014 DCHECK_GE(x_lb, 0);
2015 manager->AddCut(ComputeHyperplanAboveSquare(x, y, x_lb, x_ub, model),
2016 "SquareUpper");
2017
2018 const IntegerValue x_floor =
2019 static_cast<int64_t>(std::floor(x.LpValue(manager->LpValues())));
2020 manager->AddCut(ComputeHyperplanBelowSquare(x, y, x_floor, model),
2021 "SquareLower");
2022 return true;
2023 };
2024
2025 return result;
2026}
2027
2028ImpliedBoundsProcessor::BestImpliedBoundInfo
2030 auto it = cache_.find(var);
2031 if (it != cache_.end()) {
2032 BestImpliedBoundInfo result = it->second;
2033 if (result.bool_var == kNoIntegerVariable) return BestImpliedBoundInfo();
2034 if (integer_trail_->IsFixed(result.bool_var)) return BestImpliedBoundInfo();
2035 return result;
2036 }
2037 return BestImpliedBoundInfo();
2038}
2039
2041ImpliedBoundsProcessor::ComputeBestImpliedBound(
2042 IntegerVariable var,
2044 auto it = cache_.find(var);
2045 if (it != cache_.end()) return it->second;
2046 BestImpliedBoundInfo result;
2047 double result_slack_lp_value = std::numeric_limits<double>::infinity();
2048 const IntegerValue lb = integer_trail_->LevelZeroLowerBound(var);
2049 for (const ImpliedBoundEntry& entry :
2050 implied_bounds_->GetImpliedBounds(var)) {
2051 // Only process entries with a Boolean variable currently part of the LP
2052 // we are considering for this cut.
2053 //
2054 // TODO(user): the more we use cuts, the less it make sense to have a
2055 // lot of small independent LPs.
2056 if (!lp_vars_.contains(PositiveVariable(entry.literal_view))) {
2057 continue;
2058 }
2059
2060 // The equation is X = lb + diff * Bool + Slack where Bool is in [0, 1]
2061 // and slack in [0, ub - lb].
2062 const IntegerValue diff = entry.lower_bound - lb;
2063 CHECK_GE(diff, 0);
2064 const double bool_lp_value = entry.is_positive
2065 ? lp_values[entry.literal_view]
2066 : 1.0 - lp_values[entry.literal_view];
2067 const double slack_lp_value =
2068 lp_values[var] - ToDouble(lb) - bool_lp_value * ToDouble(diff);
2069
2070 // If the implied bound equation is not respected, we just add it
2071 // to implied_bound_cuts, and skip the entry for now.
2072 if (slack_lp_value < -1e-4) {
2073 LinearConstraint ib_cut;
2074 ib_cut.lb = kMinIntegerValue;
2075 std::vector<std::pair<IntegerVariable, IntegerValue>> terms;
2076 if (entry.is_positive) {
2077 // X >= Indicator * (bound - lb) + lb
2078 terms.push_back({entry.literal_view, diff});
2079 terms.push_back({var, IntegerValue(-1)});
2080 ib_cut.ub = -lb;
2081 } else {
2082 // X >= -Indicator * (bound - lb) + bound
2083 terms.push_back({entry.literal_view, -diff});
2084 terms.push_back({var, IntegerValue(-1)});
2085 ib_cut.ub = -entry.lower_bound;
2086 }
2087 CleanTermsAndFillConstraint(&terms, &ib_cut);
2088 ib_cut_pool_.AddCut(std::move(ib_cut), "IB", lp_values);
2089 continue;
2090 }
2091
2092 // We look for tight implied bounds, and amongst the tightest one, we
2093 // prefer larger coefficient in front of the Boolean.
2094 if (slack_lp_value + 1e-4 < result_slack_lp_value ||
2095 (slack_lp_value < result_slack_lp_value + 1e-4 &&
2096 entry.lower_bound > result.implied_bound)) {
2097 result_slack_lp_value = slack_lp_value;
2098 result.var_lp_value = lp_values[var];
2099 result.bool_lp_value = bool_lp_value;
2100 result.implied_bound = entry.lower_bound;
2101 result.is_positive = entry.is_positive;
2102 result.bool_var = entry.literal_view;
2103 }
2104 }
2105 cache_[var] = result;
2106 return result;
2107}
2108
2111 cache_.clear();
2112 for (const IntegerVariable var :
2113 implied_bounds_->VariablesWithImpliedBounds()) {
2114 if (!lp_vars_.contains(PositiveVariable(var))) continue;
2115 ComputeBestImpliedBound(var, lp_values);
2116 }
2117}
2118
2120 const CutTerm& term, IntegerValue factor_t, CutTerm& bool_term,
2121 CutTerm& slack_term) {
2122 // We only want to expand non-Boolean and non-slack term!
2123 if (term.bound_diff <= 1) return false;
2124 if (!term.IsSimple()) return false;
2125 DCHECK_EQ(IntTypeAbs(term.expr_coeffs[0]), 1);
2126
2127 // Try lower bounded direction for implied bound.
2128 // This kind should always be beneficial if it exists:
2129 //
2130 // Because X = bound_diff * B + S
2131 // We can replace coeff * X by the expression before applying f:
2132 // = f(coeff * bound_diff) * B + f(coeff) * [X - bound_diff * B]
2133 // = f(coeff) * X + (f(coeff * bound_diff) - f(coeff) * bound_diff] * B
2134 // So we can "lift" B into the cut with a non-negative coefficient.
2135 //
2136 // Note that this lifting is really the same as if we used that implied
2137 // bound before since in f(coeff * bound_diff) * B + f(coeff) * S, if we
2138 // replace S by its value [X - bound_diff * B] we get the same result.
2139 //
2140 // TODO(user): Ignore if bound_diff == 1 ? But we can still merge B with
2141 // another entry if it exists, so it can still be good in this case.
2142 //
2143 // TODO(user): Only do it if coeff_b > 0 ? But again we could still merge
2144 // B with an existing Boolean for a better cut even if coeff_b == 0.
2145 if (term.cached_implied_lb < 0) return false;
2146 const BestImpliedBoundInfo info = cached_data_[term.cached_implied_lb];
2147 const IntegerValue lb = -term.expr_offset;
2148 const IntegerValue bound_diff = info.implied_bound - lb;
2149 if (bound_diff <= 0) return false;
2150 if (ProdOverflow(factor_t, CapProdI(term.coeff, bound_diff))) return false;
2151
2152 // We have X/-X = info.diff * Boolean + slack.
2153 bool_term.coeff = term.coeff * bound_diff;
2154 bool_term.expr_vars[0] = info.bool_var;
2155 bool_term.expr_coeffs[1] = 0;
2156 bool_term.bound_diff = IntegerValue(1);
2157 bool_term.lp_value = info.bool_lp_value;
2158 if (info.is_positive) {
2159 bool_term.expr_coeffs[0] = IntegerValue(1);
2160 bool_term.expr_offset = IntegerValue(0);
2161 } else {
2162 bool_term.expr_coeffs[0] = IntegerValue(-1);
2163 bool_term.expr_offset = IntegerValue(1);
2164 }
2165
2166 // Create slack.
2167 // The expression is term.exp - bound_diff * bool_term
2168 // The variable shouldn't be the same.
2169 DCHECK_NE(term.expr_vars[0], bool_term.expr_vars[0]);
2170 slack_term.expr_vars[0] = term.expr_vars[0];
2171 slack_term.expr_coeffs[0] = term.expr_coeffs[0];
2172 slack_term.expr_vars[1] = bool_term.expr_vars[0];
2173 slack_term.expr_coeffs[1] = -bound_diff * bool_term.expr_coeffs[0];
2174 slack_term.expr_offset =
2175 term.expr_offset - bound_diff * bool_term.expr_offset;
2176
2177 slack_term.lp_value = info.SlackLpValue(lb);
2178 slack_term.coeff = term.coeff;
2179 slack_term.bound_diff = term.bound_diff;
2180
2181 return true;
2182}
2183
2184// We use the fact that calling DecomposeWithImpliedLowerBound() with
2185// term.Complement() give us almost what we want. You have
2186// -complement(X) = -diff.B - slack
2187// - (diff - X) = -diff.(1 -(1- B)) - slack
2188// X = diff.(1 - B) - slack;
2190 const CutTerm& term, IntegerValue factor_t, CutTerm& bool_term,
2191 CutTerm& slack_term) {
2192 absl::int128 unused = 0;
2193 CutTerm complement = term;
2194 complement.Complement(&unused);
2195 if (!DecomposeWithImpliedLowerBound(complement, factor_t, bool_term,
2196 slack_term)) {
2197 return false;
2198 }
2199 // This is required not to have a constant term which might mess up our cut
2200 // heuristics.
2201 if (IntTypeAbs(bool_term.coeff) !=
2203 return false;
2204 }
2205 bool_term.Complement(&unused);
2206 CHECK_EQ(unused, absl::int128(0));
2207 return true;
2208}
2209
2211 const std::function<IntegerValue(IntegerValue)>& f, IntegerValue factor_t,
2212 CutData* cut) {
2213 int num_applied_lb = 0;
2214 int num_applied_ub = 0;
2215
2216 CutTerm bool_term;
2217 CutTerm slack_term;
2218 CutTerm ub_bool_term;
2219 CutTerm ub_slack_term;
2220
2221 tmp_terms_.clear();
2222 for (CutTerm& term : cut->terms) {
2223 if (term.bound_diff <= 1) continue;
2224 if (!term.IsSimple()) continue;
2225
2226 // Score is just the final lp value.
2227 // Higher is better since it is a <= constraint.
2228 double base_score;
2229 bool expand = false;
2230 if (DecomposeWithImpliedLowerBound(term, factor_t, bool_term, slack_term)) {
2231 // This side is always good.
2232 // c.X = c.d.B + c.S
2233 // applying f to the result we have f(c.d).B + f(c).[X - d.B]
2234 // which give f(c).X + [f(c.d) - f(c).d].B
2235 // and the second term is always positive by super-additivity.
2236 expand = true;
2237 base_score = AsDouble(f(bool_term.coeff)) * bool_term.lp_value +
2238 AsDouble(f(slack_term.coeff)) * slack_term.lp_value;
2239 } else {
2240 base_score = AsDouble(f(term.coeff)) * term.lp_value;
2241 }
2242
2243 // Test if it is better to use this "bad" side.
2244 //
2245 // Use the implied bound on (-X) if it is beneficial to do so.
2246 // Like complementing, this is not always good.
2247 //
2248 // We have comp(X) = diff - X = diff * B + S
2249 // X = diff * (1 - B) - S.
2250 // So if we applies f, we will get:
2251 // f(coeff * diff) * (1 - B) + f(-coeff) * S
2252 // and substituing S = diff * (1 - B) - X, we get:
2253 // -f(-coeff) * X + [f(coeff * diff) + f(-coeff) * diff] (1 - B).
2254 //
2255 // TODO(user): Note that while the violation might be higher, if the slack
2256 // becomes large this will result in a less powerfull cut. Shall we do
2257 // that? It is a bit the same problematic with complementing.
2258 //
2259 // TODO(user): If the slack is close to zero, then this transformation
2260 // will always increase the violation. So we could potentially do it in
2261 // Before our divisor selection heuristic. But the norm of the final cut
2262 // will increase too.
2263 if (DecomposeWithImpliedUpperBound(term, factor_t, ub_bool_term,
2264 ub_slack_term)) {
2265 const double score =
2266 AsDouble(f(ub_bool_term.coeff)) * ub_bool_term.lp_value +
2267 AsDouble(f(ub_slack_term.coeff)) * ub_slack_term.lp_value;
2268 // Note that because the slack is of the opposite sign, we might
2269 // loose more, so we prefer to be a bit defensive.
2270 if (score > base_score + 1e-2) {
2271 ++num_applied_ub;
2272 term = ub_slack_term;
2273 tmp_terms_.push_back(ub_bool_term);
2274 continue;
2275 }
2276 }
2277
2278 if (expand) {
2279 ++num_applied_lb;
2280 term = slack_term;
2281 tmp_terms_.push_back(bool_term);
2282 }
2283 }
2284
2285 const int num_merges = cut_builder_.AddOrMergeBooleanTerms(
2286 absl::MakeSpan(tmp_terms_), factor_t, cut);
2287
2288 return {num_applied_lb, num_applied_ub, num_merges};
2289}
2290
2292 IntegerValue factor_t, bool complement, CutTerm* term, absl::int128* rhs,
2293 std::vector<CutTerm>* new_bool_terms) {
2294 CutTerm bool_term;
2295 CutTerm slack_term;
2296 if (!DecomposeWithImpliedLowerBound(*term, factor_t, bool_term, slack_term)) {
2297 return false;
2298 }
2299
2300 // It should be good to use IB, but sometime we have things like
2301 // 7.3 = 2 * bool@1 + 5.3 and the expanded Boolean is at its upper bound.
2302 // It is always good to complement such variable.
2303 //
2304 // Note that here we do more and just complement anything closer to UB.
2305 if (complement) {
2306 if (bool_term.lp_value > 0.5) {
2307 bool_term.Complement(rhs);
2308 }
2309 if (slack_term.lp_value > 0.5 * AsDouble(slack_term.bound_diff)) {
2310 slack_term.Complement(rhs);
2311 }
2312 }
2313
2314 *term = slack_term;
2315 new_bool_terms->push_back(bool_term);
2316 return true;
2317}
2318
2319bool ImpliedBoundsProcessor::CacheDataForCut(IntegerVariable first_slack,
2320 CutData* cut) {
2321 cached_data_.clear();
2322
2323 const int size = cut->terms.size();
2324 for (int i = 0; i < size; ++i) {
2325 const CutTerm& term = cut->terms[i];
2326 if (!term.IsSimple()) continue;
2327 if (term.IsBoolean()) continue;
2328 if (term.expr_vars[0] >= first_slack) continue;
2329
2330 // Cache the BestImpliedBoundInfo if relevant.
2331 const IntegerVariable ib_var = term.expr_coeffs[0] > 0
2332 ? term.expr_vars[0]
2333 : NegationOf(term.expr_vars[0]);
2335 if (lb_info.bool_var != kNoIntegerVariable) {
2336 cut->terms[i].cached_implied_lb = cached_data_.size();
2337 cached_data_.emplace_back(std::move(lb_info));
2338 }
2339 BestImpliedBoundInfo ub_info =
2341 if (ub_info.bool_var != kNoIntegerVariable) {
2342 cut->terms[i].cached_implied_ub = cached_data_.size();
2343 cached_data_.emplace_back(std::move(ub_info));
2344 }
2345 }
2346
2347 return !cached_data_.empty();
2348}
2349
2351 min_values_.clear();
2352 expr_mins_.clear();
2353}
2354
2355void SumOfAllDiffLowerBounder::Add(const AffineExpression& expr, int num_exprs,
2356 const IntegerTrail& integer_trail) {
2357 expr_mins_.push_back(integer_trail.LevelZeroLowerBound(expr).value());
2358
2359 if (integer_trail.IsFixed(expr)) {
2360 min_values_.insert(integer_trail.FixedValue(expr));
2361 } else {
2362 if (expr.coeff > 0) {
2363 int count = 0;
2364 for (const IntegerValue value :
2365 integer_trail.InitialVariableDomain(expr.var).Values()) {
2366 min_values_.insert(expr.ValueAt(value));
2367 if (++count >= num_exprs) break;
2368 }
2369 } else {
2370 int count = 0;
2371 for (const IntegerValue value :
2372 integer_trail.InitialVariableDomain(expr.var).Negation().Values()) {
2373 min_values_.insert(-expr.ValueAt(value));
2374 if (++count >= num_exprs) break;
2375 }
2376 }
2377 }
2378}
2379
2381 int count = 0;
2382 IntegerValue sum = 0;
2383 for (const IntegerValue value : min_values_) {
2384 sum = CapAddI(sum, value);
2385 if (++count >= expr_mins_.size()) return sum;
2386 }
2387 return sum;
2388}
2389
2391 std::sort(expr_mins_.begin(), expr_mins_.end());
2392 IntegerValue tmp_value = kMinIntegerValue;
2393 IntegerValue result = 0;
2394 for (const IntegerValue value : expr_mins_) {
2395 // Make sure values are different.
2396 tmp_value = std::max(tmp_value + 1, value);
2397 result += tmp_value;
2398 }
2399 return result;
2400}
2401
2402IntegerValue SumOfAllDiffLowerBounder::GetBestLowerBound(std::string& suffix) {
2403 const IntegerValue domain_bound = SumOfMinDomainValues();
2404 const IntegerValue alldiff_bound = SumOfDifferentMins();
2405 if (domain_bound > alldiff_bound) {
2406 suffix = "d";
2407 return domain_bound;
2408 }
2409 suffix = alldiff_bound > domain_bound ? "a" : "e";
2410 return alldiff_bound;
2411}
2412
2413namespace {
2414
2415void TryToGenerateAllDiffCut(
2416 absl::Span<const std::pair<double, AffineExpression>> sorted_exprs_lp,
2417 const IntegerTrail& integer_trail,
2419 TopNCuts& top_n_cuts, Model* model) {
2420 const int num_exprs = sorted_exprs_lp.size();
2421
2422 std::vector<AffineExpression> current_set_exprs;
2423 SumOfAllDiffLowerBounder diff_mins;
2424 SumOfAllDiffLowerBounder negated_diff_maxes;
2425
2426 double sum = 0.0;
2427
2428 for (const auto& [expr_lp, expr] : sorted_exprs_lp) {
2429 sum += expr_lp;
2430 diff_mins.Add(expr, num_exprs, integer_trail);
2431 negated_diff_maxes.Add(expr.Negated(), num_exprs, integer_trail);
2432 current_set_exprs.push_back(expr);
2433 CHECK_EQ(current_set_exprs.size(), diff_mins.size());
2434 CHECK_EQ(current_set_exprs.size(), negated_diff_maxes.size());
2435 std::string min_suffix;
2436 const IntegerValue required_min_sum =
2437 diff_mins.GetBestLowerBound(min_suffix);
2438 std::string max_suffix;
2439 const IntegerValue required_max_sum =
2440 -negated_diff_maxes.GetBestLowerBound(max_suffix);
2441 if (required_max_sum == std::numeric_limits<IntegerValue>::max()) continue;
2442 DCHECK_LE(required_min_sum, required_max_sum);
2443 if (sum < ToDouble(required_min_sum) - kMinCutViolation ||
2444 sum > ToDouble(required_max_sum) + kMinCutViolation) {
2445 LinearConstraintBuilder cut(model, required_min_sum, required_max_sum);
2446 for (AffineExpression expr : current_set_exprs) {
2447 cut.AddTerm(expr, IntegerValue(1));
2448 }
2449 top_n_cuts.AddCut(cut.Build(),
2450 absl::StrCat("AllDiff_", min_suffix, max_suffix),
2451 lp_values);
2452
2453 // NOTE: We can extend the current set but it is more helpful to generate
2454 // the cut on a different set of variables so we reset the counters.
2455 sum = 0.0;
2456 current_set_exprs.clear();
2457 diff_mins.Clear();
2458 negated_diff_maxes.Clear();
2459 }
2460 }
2461}
2462
2463} // namespace
2464
2466 absl::Span<const AffineExpression> exprs, Model* model) {
2467 CutGenerator result;
2468 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
2469
2470 for (const AffineExpression& expr : exprs) {
2471 if (!integer_trail->IsFixed(expr)) {
2472 result.vars.push_back(expr.var);
2473 }
2474 }
2476
2477 Trail* trail = model->GetOrCreate<Trail>();
2478 result.generate_cuts =
2479 [exprs = std::vector<AffineExpression>(exprs.begin(), exprs.end()),
2480 integer_trail, trail, model](LinearConstraintManager* manager) {
2481 // These cuts work at all levels but the generator adds too many cuts on
2482 // some instances and degrade the performance so we only use it at level
2483 // 0.
2484 if (trail->CurrentDecisionLevel() > 0) return true;
2485 const auto& lp_values = manager->LpValues();
2486 std::vector<std::pair<double, AffineExpression>> sorted_exprs;
2487 for (const AffineExpression expr : exprs) {
2488 if (integer_trail->LevelZeroLowerBound(expr) ==
2489 integer_trail->LevelZeroUpperBound(expr)) {
2490 continue;
2491 }
2492 sorted_exprs.push_back(std::make_pair(expr.LpValue(lp_values), expr));
2493 }
2494
2495 TopNCuts top_n_cuts(5);
2496 std::sort(sorted_exprs.begin(), sorted_exprs.end(),
2497 [](std::pair<double, AffineExpression>& a,
2498 const std::pair<double, AffineExpression>& b) {
2499 return a.first < b.first;
2500 });
2501 TryToGenerateAllDiffCut(sorted_exprs, *integer_trail, lp_values,
2502 top_n_cuts, model);
2503 // Other direction.
2504 std::reverse(sorted_exprs.begin(), sorted_exprs.end());
2505 TryToGenerateAllDiffCut(sorted_exprs, *integer_trail, lp_values,
2506 top_n_cuts, model);
2507 top_n_cuts.TransferToManager(manager);
2508 return true;
2509 };
2510 VLOG(2) << "Created all_diff cut generator of size: " << exprs.size();
2511 return result;
2512}
2513
2514namespace {
2515// Returns max((w2i - w1i)*Li, (w2i - w1i)*Ui).
2516IntegerValue MaxCornerDifference(const IntegerVariable var,
2517 const IntegerValue w1_i,
2518 const IntegerValue w2_i,
2519 const IntegerTrail& integer_trail) {
2520 const IntegerValue lb = integer_trail.LevelZeroLowerBound(var);
2521 const IntegerValue ub = integer_trail.LevelZeroUpperBound(var);
2522 return std::max((w2_i - w1_i) * lb, (w2_i - w1_i) * ub);
2523}
2524
2525// This is the coefficient of zk in the cut, where k = max_index.
2526// MPlusCoefficient_ki = max((wki - wI(i)i) * Li,
2527// (wki - wI(i)i) * Ui)
2528// = max corner difference for variable i,
2529// target expr I(i), max expr k.
2530// The coefficient of zk is Sum(i=1..n)(MPlusCoefficient_ki) + bk
2531IntegerValue MPlusCoefficient(
2532 absl::Span<const IntegerVariable> x_vars,
2533 absl::Span<const LinearExpression> exprs,
2534 const util_intops::StrongVector<IntegerVariable, int>& variable_partition,
2535 const int max_index, const IntegerTrail& integer_trail) {
2536 IntegerValue coeff = exprs[max_index].offset;
2537 // TODO(user): This algo is quadratic since GetCoefficientOfPositiveVar()
2538 // is linear. This can be optimized (better complexity) if needed.
2539 for (const IntegerVariable var : x_vars) {
2540 const int target_index = variable_partition[var];
2541 if (max_index != target_index) {
2542 coeff += MaxCornerDifference(
2543 var, GetCoefficientOfPositiveVar(var, exprs[target_index]),
2544 GetCoefficientOfPositiveVar(var, exprs[max_index]), integer_trail);
2545 }
2546 }
2547 return coeff;
2548}
2549
2550// Compute the value of
2551// rhs = wI(i)i * xi + Sum(k=1..d)(MPlusCoefficient_ki * zk)
2552// for variable xi for given target index I(i).
2553double ComputeContribution(
2554 const IntegerVariable xi_var, absl::Span<const IntegerVariable> z_vars,
2555 absl::Span<const LinearExpression> exprs,
2556 const util_intops::StrongVector<IntegerVariable, double>& lp_values,
2557 const IntegerTrail& integer_trail, const int target_index) {
2558 CHECK_GE(target_index, 0);
2559 CHECK_LT(target_index, exprs.size());
2560 const LinearExpression& target_expr = exprs[target_index];
2561 const double xi_value = lp_values[xi_var];
2562 const IntegerValue wt_i = GetCoefficientOfPositiveVar(xi_var, target_expr);
2563 double contrib = ToDouble(wt_i) * xi_value;
2564 for (int expr_index = 0; expr_index < exprs.size(); ++expr_index) {
2565 if (expr_index == target_index) continue;
2566 const LinearExpression& max_expr = exprs[expr_index];
2567 const double z_max_value = lp_values[z_vars[expr_index]];
2568 const IntegerValue corner_value = MaxCornerDifference(
2569 xi_var, wt_i, GetCoefficientOfPositiveVar(xi_var, max_expr),
2570 integer_trail);
2571 contrib += ToDouble(corner_value) * z_max_value;
2572 }
2573 return contrib;
2574}
2575} // namespace
2576
2577CutGenerator CreateLinMaxCutGenerator(const IntegerVariable target,
2578 absl::Span<const LinearExpression> exprs,
2579 absl::Span<const IntegerVariable> z_vars,
2580 Model* model) {
2581 CutGenerator result;
2582 std::vector<IntegerVariable> x_vars;
2583 result.vars = {target};
2584 const int num_exprs = exprs.size();
2585 for (int i = 0; i < num_exprs; ++i) {
2586 result.vars.push_back(z_vars[i]);
2587 x_vars.insert(x_vars.end(), exprs[i].vars.begin(), exprs[i].vars.end());
2588 }
2590 // All expressions should only contain positive variables.
2591 DCHECK(std::all_of(x_vars.begin(), x_vars.end(), [](IntegerVariable var) {
2592 return VariableIsPositive(var);
2593 }));
2594 result.vars.insert(result.vars.end(), x_vars.begin(), x_vars.end());
2595
2596 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
2597 result.generate_cuts =
2598 [x_vars,
2599 z_vars = std::vector<IntegerVariable>(z_vars.begin(), z_vars.end()),
2600 target, num_exprs,
2601 exprs = std::vector<LinearExpression>(exprs.begin(), exprs.end()),
2602 integer_trail, model](LinearConstraintManager* manager) {
2603 const auto& lp_values = manager->LpValues();
2605 lp_values.size(), -1);
2607 variable_partition_contrib(lp_values.size(),
2608 std::numeric_limits<double>::infinity());
2609 for (int expr_index = 0; expr_index < num_exprs; ++expr_index) {
2610 for (const IntegerVariable var : x_vars) {
2611 const double contribution = ComputeContribution(
2612 var, z_vars, exprs, lp_values, *integer_trail, expr_index);
2613 const double prev_contribution = variable_partition_contrib[var];
2614 if (contribution < prev_contribution) {
2615 variable_partition[var] = expr_index;
2616 variable_partition_contrib[var] = contribution;
2617 }
2618 }
2619 }
2620
2621 LinearConstraintBuilder cut(model, /*lb=*/IntegerValue(0),
2622 /*ub=*/kMaxIntegerValue);
2623 double violation = lp_values[target];
2624 cut.AddTerm(target, IntegerValue(-1));
2625
2626 for (const IntegerVariable xi_var : x_vars) {
2627 const int input_index = variable_partition[xi_var];
2628 const LinearExpression& expr = exprs[input_index];
2629 const IntegerValue coeff = GetCoefficientOfPositiveVar(xi_var, expr);
2630 if (coeff != IntegerValue(0)) {
2631 cut.AddTerm(xi_var, coeff);
2632 }
2633 violation -= ToDouble(coeff) * lp_values[xi_var];
2634 }
2635 for (int expr_index = 0; expr_index < num_exprs; ++expr_index) {
2636 const IntegerVariable z_var = z_vars[expr_index];
2637 const IntegerValue z_coeff = MPlusCoefficient(
2638 x_vars, exprs, variable_partition, expr_index, *integer_trail);
2639 if (z_coeff != IntegerValue(0)) {
2640 cut.AddTerm(z_var, z_coeff);
2641 }
2642 violation -= ToDouble(z_coeff) * lp_values[z_var];
2643 }
2644 if (violation > 1e-2) {
2645 manager->AddCut(cut.Build(), "LinMax");
2646 }
2647 return true;
2648 };
2649 return result;
2650}
2651
2652namespace {
2653
2654IntegerValue EvaluateMaxAffine(
2655 absl::Span<const std::pair<IntegerValue, IntegerValue>> affines,
2656 IntegerValue x) {
2657 IntegerValue y = kMinIntegerValue;
2658 for (const auto& p : affines) {
2659 y = std::max(y, x * p.first + p.second);
2660 }
2661 return y;
2662}
2663
2664} // namespace
2665
2667 const LinearExpression& target, IntegerVariable var,
2668 absl::Span<const std::pair<IntegerValue, IntegerValue>> affines,
2669 Model* model, LinearConstraintBuilder* builder) {
2670 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
2671 const IntegerValue x_min = integer_trail->LevelZeroLowerBound(var);
2672 const IntegerValue x_max = integer_trail->LevelZeroUpperBound(var);
2673
2674 const IntegerValue y_at_min = EvaluateMaxAffine(affines, x_min);
2675 const IntegerValue y_at_max = EvaluateMaxAffine(affines, x_max);
2676
2677 const IntegerValue delta_x = x_max - x_min;
2678 const IntegerValue delta_y = y_at_max - y_at_min;
2679
2680 // target <= y_at_min + (delta_y / delta_x) * (var - x_min)
2681 // delta_x * target <= delta_x * y_at_min + delta_y * (var - x_min)
2682 // -delta_y * var + delta_x * target <= delta_x * y_at_min - delta_y * x_min
2683 //
2684 // Checks the rhs for overflows.
2685 if (ProdOverflow(delta_x, y_at_min) || ProdOverflow(delta_x, y_at_max) ||
2686 ProdOverflow(delta_y, x_min) || ProdOverflow(delta_y, x_max)) {
2687 return false;
2688 }
2689
2690 // Checks target * delta_x for overflow.
2691 int64_t abs_magnitude = std::abs(target.offset.value());
2692 for (int i = 0; i < target.vars.size(); ++i) {
2693 const IntegerVariable var = target.vars[i];
2694 const IntegerValue var_min = integer_trail->LevelZeroLowerBound(var);
2695 const IntegerValue var_max = integer_trail->LevelZeroUpperBound(var);
2696 abs_magnitude = CapAdd(
2697 CapProd(std::max(std::abs(var_min.value()), std::abs(var_max.value())),
2698 std::abs(target.coeffs[i].value())),
2699 abs_magnitude);
2700 }
2701 if (AtMinOrMaxInt64(abs_magnitude) ||
2702 AtMinOrMaxInt64(CapProd(abs_magnitude, delta_x.value()))) {
2703 return false;
2704 }
2705
2706 builder->ResetBounds(kMinIntegerValue, delta_x * y_at_min - delta_y * x_min);
2707 builder->AddLinearExpression(target, delta_x);
2708 builder->AddTerm(var, -delta_y);
2709
2710 // Prevent to create constraints that can overflow.
2711 if (!ValidateLinearConstraintForOverflow(builder->Build(), *integer_trail)) {
2712 VLOG(2) << "Linear constraint can cause overflow: " << builder->Build();
2713
2714 return false;
2715 }
2716
2717 return true;
2718}
2719
2721 LinearExpression target, IntegerVariable var,
2722 std::vector<std::pair<IntegerValue, IntegerValue>> affines,
2723 const std::string cut_name, Model* model) {
2724 CutGenerator result;
2725 result.vars = target.vars;
2726 result.vars.push_back(var);
2728
2729 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
2730 result.generate_cuts = [target, var, affines, cut_name, integer_trail,
2731 model](LinearConstraintManager* manager) {
2732 if (integer_trail->IsFixed(var)) return true;
2733 LinearConstraintBuilder builder(model);
2734 if (BuildMaxAffineUpConstraint(target, var, affines, model, &builder)) {
2735 manager->AddCut(builder.Build(), cut_name);
2736 }
2737 return true;
2738 };
2739 return result;
2740}
2741
2743 absl::Span<const IntegerVariable> base_variables, Model* model) {
2744 // Filter base_variables to only keep the one with a literal view, and
2745 // do the conversion.
2746 std::vector<IntegerVariable> variables;
2747 std::vector<Literal> literals;
2748 absl::flat_hash_map<LiteralIndex, IntegerVariable> positive_map;
2749 absl::flat_hash_map<LiteralIndex, IntegerVariable> negative_map;
2750 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
2751 auto* encoder = model->GetOrCreate<IntegerEncoder>();
2752 for (const IntegerVariable var : base_variables) {
2753 if (integer_trail->LowerBound(var) != IntegerValue(0)) continue;
2754 if (integer_trail->UpperBound(var) != IntegerValue(1)) continue;
2755 const LiteralIndex literal_index = encoder->GetAssociatedLiteral(
2756 IntegerLiteral::GreaterOrEqual(var, IntegerValue(1)));
2757 if (literal_index != kNoLiteralIndex) {
2758 variables.push_back(var);
2759 literals.push_back(Literal(literal_index));
2760 positive_map[literal_index] = var;
2761 negative_map[Literal(literal_index).NegatedIndex()] = var;
2762 }
2763 }
2764 CutGenerator result;
2765 result.vars = variables;
2766 auto* implication_graph = model->GetOrCreate<BinaryImplicationGraph>();
2767 result.only_run_at_level_zero = true;
2768 result.generate_cuts = [variables, literals, implication_graph, positive_map,
2769 negative_map,
2770 model](LinearConstraintManager* manager) {
2771 std::vector<double> packed_values;
2772 std::vector<double> packed_reduced_costs;
2773 const auto& lp_values = manager->LpValues();
2774 const auto& reduced_costs = manager->ReducedCosts();
2775 for (int i = 0; i < literals.size(); ++i) {
2776 packed_values.push_back(lp_values[variables[i]]);
2777 packed_reduced_costs.push_back(reduced_costs[variables[i]]);
2778 }
2779 const std::vector<std::vector<Literal>> at_most_ones =
2780 implication_graph->GenerateAtMostOnesWithLargeWeight(
2781 literals, packed_values, packed_reduced_costs);
2782
2783 for (const std::vector<Literal>& at_most_one : at_most_ones) {
2784 // We need to express such "at most one" in term of the initial
2785 // variables, so we do not use the
2786 // LinearConstraintBuilder::AddLiteralTerm() here.
2788 model, IntegerValue(std::numeric_limits<int64_t>::min()),
2789 IntegerValue(1));
2790 for (const Literal l : at_most_one) {
2791 if (positive_map.contains(l.Index())) {
2792 builder.AddTerm(positive_map.at(l.Index()), IntegerValue(1));
2793 } else {
2794 // Add 1 - X to the linear constraint.
2795 builder.AddTerm(negative_map.at(l.Index()), IntegerValue(-1));
2796 builder.AddConstant(IntegerValue(1));
2797 }
2798 }
2799
2800 manager->AddCut(builder.Build(), "Clique");
2801 }
2802 return true;
2803 };
2804 return result;
2805}
2806
2807} // namespace sat
2808} // namespace operations_research
DomainIteratorBeginEnd Values() const &
bool TrySimpleSeparation(const CutData &input_ct)
Tries RLT separation of the input constraint. Returns true on success.
Definition cuts.cc:1675
void Initialize(absl::Span< const IntegerVariable > lp_vars)
Definition cuts.cc:1669
bool TrySimpleKnapsack(const CutData &input_ct, ImpliedBoundsProcessor *ib_processor=nullptr)
Definition cuts.cc:1302
bool TryWithLetchfordSouliLifting(const CutData &input_ct, ImpliedBoundsProcessor *ib_processor=nullptr)
Definition cuts.cc:1524
bool TrySingleNodeFlow(const CutData &input_ct, ImpliedBoundsProcessor *ib_processor=nullptr)
Definition cuts.cc:1420
Stores temporaries used to build or manipulate a CutData.
Definition cuts.h:160
bool ConvertToLinearConstraint(const CutData &cut, LinearConstraint *output)
Returns false if we encounter an integer overflow.
Definition cuts.cc:351
int AddOrMergeBooleanTerms(absl::Span< CutTerm > terms, IntegerValue t, CutData *cut)
Definition cuts.cc:298
std::vector< CutTerm > * ClearedMutableTempTerms()
Definition cuts.h:249
void RecomputeCacheAndSeparateSomeImpliedBoundCuts(const util_intops::StrongVector< IntegerVariable, double > &lp_values)
Definition cuts.cc:2109
bool CacheDataForCut(IntegerVariable first_slack, CutData *cut)
Definition cuts.cc:2319
bool TryToExpandWithLowerImpliedbound(IntegerValue factor_t, bool complement, CutTerm *term, absl::int128 *rhs, std::vector< CutTerm > *new_bool_terms)
Definition cuts.cc:2291
BestImpliedBoundInfo GetCachedImpliedBoundInfo(IntegerVariable var) const
Definition cuts.cc:2029
CutDataBuilder * MutableCutBuilder()
This can be used to share the hash-map memory.
Definition cuts.h:245
bool DecomposeWithImpliedLowerBound(const CutTerm &term, IntegerValue factor_t, CutTerm &bool_term, CutTerm &slack_term)
Definition cuts.cc:2119
std::tuple< int, int, int > PostprocessWithImpliedBound(const std::function< IntegerValue(IntegerValue)> &f, IntegerValue factor_t, CutData *cut)
Definition cuts.cc:2210
bool DecomposeWithImpliedUpperBound(const CutTerm &term, IntegerValue factor_t, CutTerm &bool_term, CutTerm &slack_term)
Definition cuts.cc:2189
bool ComputeCut(RoundingOptions options, const CutData &base_ct, ImpliedBoundsProcessor *ib_processor=nullptr)
Returns true on success. The cut can be accessed via cut().
Definition cuts.cc:772
IntegerValue FixedValue(IntegerVariable i) const
Checks that the variable is fixed and returns its value.
Definition integer.h:1313
bool IsFixed(IntegerVariable i) const
Checks if the variable is fixed.
Definition integer.h:1309
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
Definition integer.h:1401
const Domain & InitialVariableDomain(IntegerVariable var) const
Definition integer.cc:877
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Returns globally valid lower/upper bound on the given integer variable.
Definition integer.h:1396
void ResetBounds(IntegerValue lb, IntegerValue ub)
Reset the bounds passed at construction time.
void AddTerm(IntegerVariable var, IntegerValue coeff)
void AddLinearExpression(const LinearExpression &expr)
void AddConstant(IntegerValue value)
Adds the corresponding term to the current linear expression.
LiteralIndex NegatedIndex() const
Definition sat_base.h:92
Utility class for the AllDiff cut generator.
Definition cuts.h:724
IntegerValue GetBestLowerBound(std::string &suffix)
Definition cuts.cc:2402
void Add(const AffineExpression &expr, int num_expr, const IntegerTrail &integer_trail)
Definition cuts.cc:2355
IntegerValue SumOfMinDomainValues()
Return int_max if the sum overflows.
Definition cuts.cc:2380
void TransferToManager(LinearConstraintManager *manager)
Empty the local pool and add all its content to the manager.
void AddCut(LinearConstraint ct, absl::string_view name, const util_intops::StrongVector< IntegerVariable, double > &lp_solution)
Adds a cut to the local pool.
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:58
bool AddProductTo(IntegerValue a, IntegerValue b, IntegerValue *result)
Computes result += a * b, and return false iff there is an overflow.
void DivideByGCD(LinearConstraint *constraint)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
bool BuildMaxAffineUpConstraint(const LinearExpression &target, IntegerVariable var, absl::Span< const std::pair< IntegerValue, IntegerValue > > affines, Model *model, LinearConstraintBuilder *builder)
Definition cuts.cc:2666
IntegerValue GetFactorT(IntegerValue rhs_remainder, IntegerValue divisor, IntegerValue max_magnitude)
Definition cuts.cc:469
IntType IntTypeAbs(IntType t)
CutGenerator CreatePositiveMultiplicationCutGenerator(AffineExpression z, AffineExpression x, AffineExpression y, int linearization_level, Model *model)
A cut generator for z = x * y (x and y >= 0).
Definition cuts.cc:1874
bool ProdOverflow(IntegerValue t, IntegerValue value)
const LiteralIndex kNoLiteralIndex(-1)
LinearConstraint ComputeHyperplanBelowSquare(AffineExpression x, AffineExpression square, IntegerValue x_value, Model *model)
Definition cuts.cc:1981
std::vector< IntegerVariable > NegationOf(absl::Span< const IntegerVariable > vars)
Returns the vector of the negated variables.
Definition integer.cc:52
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
const IntegerVariable kNoIntegerVariable(-1)
CutGenerator CreateLinMaxCutGenerator(const IntegerVariable target, absl::Span< const LinearExpression > exprs, absl::Span< const IntegerVariable > z_vars, Model *model)
Definition cuts.cc:2577
void CleanTermsAndFillConstraint(std::vector< std::pair< IntegerVariable, IntegerValue > > *terms, LinearExpression *output)
std::function< IntegerValue(IntegerValue)> GetSuperAdditiveStrengtheningFunction(IntegerValue positive_rhs, IntegerValue min_magnitude)
Definition cuts.cc:568
CutGenerator CreateAllDifferentCutGenerator(absl::Span< const AffineExpression > exprs, Model *model)
Definition cuts.cc:2465
CutGenerator CreateSquareCutGenerator(AffineExpression y, AffineExpression x, int linearization_level, Model *model)
Definition cuts.cc:1993
CutGenerator CreateCliqueCutGenerator(absl::Span< const IntegerVariable > base_variables, Model *model)
Definition cuts.cc:2742
IntegerVariable PositiveVariable(IntegerVariable i)
IntegerValue PositiveRemainder(IntegerValue dividend, IntegerValue positive_divisor)
CutGenerator CreateMaxAffineCutGenerator(LinearExpression target, IntegerVariable var, std::vector< std::pair< IntegerValue, IntegerValue > > affines, const std::string cut_name, Model *model)
Definition cuts.cc:2720
IntegerValue CapAddI(IntegerValue a, IntegerValue b)
LinearConstraint ComputeHyperplanAboveSquare(AffineExpression x, AffineExpression square, IntegerValue x_lb, IntegerValue x_ub, Model *model)
Definition cuts.cc:1969
IntegerValue GetCoefficientOfPositiveVar(const IntegerVariable var, const LinearExpression &expr)
bool ValidateLinearConstraintForOverflow(const LinearConstraint &constraint, const IntegerTrail &integer_trail)
std::function< IntegerValue(IntegerValue)> ExtendNegativeFunction(std::function< IntegerValue(IntegerValue)> base_f, IntegerValue period)
Definition cuts.h:373
std::function< IntegerValue(IntegerValue)> GetSuperAdditiveStrengtheningMirFunction(IntegerValue positive_rhs, IntegerValue scaling)
Definition cuts.cc:603
bool VariableIsPositive(IntegerVariable i)
IntegerValue CapSubI(IntegerValue a, IntegerValue b)
bool AtMinOrMaxInt64I(IntegerValue t)
IntegerValue CapProdI(IntegerValue a, IntegerValue b)
Overflows and saturated arithmetic.
double ToDouble(IntegerValue value)
std::function< IntegerValue(IntegerValue)> GetSuperAdditiveRoundingFunction(IntegerValue rhs_remainder, IntegerValue divisor, IntegerValue t, IntegerValue max_scaling)
Definition cuts.cc:483
In SWIG mode, we don't want anything besides these top-level includes.
bool AtMinOrMaxInt64(int64_t x)
Checks if x is equal to the min or the max value of an int64_t.
int64_t CapAdd(int64_t x, int64_t y)
int64_t FloorRatio(int64_t value, int64_t positive_coeff)
int64_t CeilRatio(int64_t value, int64_t positive_coeff)
int64_t CapProd(int64_t x, int64_t y)
if(!yyg->yy_init)
Definition parser.yy.cc:965
static int input(yyscan_t yyscanner)
IntegerValue ValueAt(IntegerValue var_value) const
Returns the value of this affine expression given its variable value.
double LpValue(const util_intops::StrongVector< IntegerVariable, double > &lp_values) const
Returns the affine expression value under a given LP solution.
Our cut are always of the form linear_expression <= rhs.
Definition cuts.h:116
bool FillFromLinearConstraint(const LinearConstraint &base_ct, const util_intops::StrongVector< IntegerVariable, double > &lp_values, IntegerTrail *integer_trail)
Definition cuts.cc:172
bool AllCoefficientsArePositive() const
These functions transform the cut by complementation.
Definition cuts.cc:230
std::vector< CutTerm > terms
Definition cuts.h:152
bool FillFromParallelVectors(IntegerValue ub, absl::Span< const IntegerVariable > vars, absl::Span< const IntegerValue > coeffs, absl::Span< const double > lp_values, absl::Span< const IntegerValue > lower_bounds, absl::Span< const IntegerValue > upper_bounds)
Definition cuts.cc:190
double ComputeEfficacy() const
Definition cuts.cc:263
std::string DebugString() const
Definition cuts.cc:74
double ComputeViolation() const
Computes and returns the cut violation.
Definition cuts.cc:255
IntegerValue max_magnitude
Only filled after SortRelevantEntries().
Definition cuts.h:155
bool AppendOneTerm(IntegerVariable var, IntegerValue coeff, double lp_value, IntegerValue lb, IntegerValue ub)
Definition cuts.cc:135
absl::AnyInvocable< bool(LinearConstraintManager *manager)> generate_cuts
Definition cuts.h:59
std::vector< IntegerVariable > vars
Definition cuts.h:58
IntegerVariable GetUnderlyingLiteralOrNone() const
Definition cuts.cc:115
std::string DebugString() const
Definition cuts.cc:66
int cached_implied_lb
Refer to cached_data_ in ImpliedBoundsProcessor.
Definition cuts.h:111
double LpDistToMaxValue() const
Definition cuts.h:72
void ReplaceExpressionByLiteral(IntegerVariable var)
Definition cuts.cc:101
void Complement(absl::int128 *rhs)
Definition cuts.cc:82
std::array< IntegerVariable, 2 > expr_vars
Definition cuts.h:107
std::array< IntegerValue, 2 > expr_coeffs
Definition cuts.h:108
bool HasRelevantLpValue() const
Definition cuts.h:68
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
std::unique_ptr< IntegerValue[]> coeffs
std::unique_ptr< IntegerVariable[]> vars