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"
62double AsDouble(IntegerValue v) {
return static_cast<double>(v.value()); }
67 return absl::StrCat(
"coeff=",
coeff.value(),
" lp=",
lp_value,
75 std::string result = absl::StrCat(
"CutData rhs=",
rhs,
"\n");
77 absl::StrAppend(&result, term.DebugString(),
"\n");
88 for (
int i = 0;
i < 2; ++
i) {
136 double lp_value, IntegerValue lb, IntegerValue ub) {
137 if (coeff == 0)
return true;
138 const IntegerValue bound_diff = ub - lb;
141 const bool complement = coeff < 0;
144 rhs -= absl::int128(coeff.value()) *
145 absl::int128(complement ? ub.value() : lb.value());
149 if (bound_diff == 0)
return true;
159 entry.
coeff = -coeff;
160 entry.
lp_value =
static_cast<double>(ub.value()) - lp_value;
166 entry.
lp_value = lp_value -
static_cast<double>(lb.value());
168 terms.push_back(entry);
176 rhs = absl::int128(base_ct.
ub.value());
179 for (
int i = 0;
i < num_terms; ++
i) {
180 const IntegerVariable var = base_ct.
vars[
i];
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());
198 const int size = lp_values.size();
199 if (size == 0)
return true;
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);
206 for (
int i = 0;
i < size; ++
i) {
218 if (term.coeff >= 0)
continue;
219 term.Complement(&
rhs);
225 if (term.lp_value <= term.LpDistToMaxValue())
continue;
226 term.Complement(&
rhs);
232 if (term.coeff < 0)
return false;
242 if (entry.HasRelevantLpValue()) {
251 return a.lp_value > b.lp_value;
256 double violation = -
static_cast<double>(
rhs);
258 violation += term.lp_value *
static_cast<double>(term.coeff.value());
264 double violation = -
static_cast<double>(
rhs);
267 const double coeff =
static_cast<double>(term.coeff.value());
268 violation += term.lp_value * coeff;
269 norm += coeff * coeff;
271 return violation / std::sqrt(norm);
279bool CutDataBuilder::MergeIfPossible(IntegerValue t,
CutTerm& to_add,
290 target.
coeff = new_coeff;
299 IntegerValue t,
CutData* cut) {
300 if (new_terms.empty())
return 0;
303 secondary_bool_index_.clear();
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});
313 if (MergeIfPossible(t, term, new_terms[it->second])) {
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;
334 if (MergeIfPossible(t, new_terms[it->second], term)) {
340 for (
const CutTerm& term : new_terms) {
341 if (term.
coeff == 0)
continue;
342 cut->
terms.push_back(term);
350ABSL_DEPRECATED(
"Only used in tests, this will be removed.")
354 if (cut.rhs > absl::int128(std::numeric_limits<int64_t>::max()) ||
355 cut.rhs < absl::int128(std::numeric_limits<int64_t>::min())) {
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]])) {
373 output->ub = new_rhs;
374 output->resize(tmp_map_.size());
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;
382 output->resize(new_size);
391const double kMinCutViolation = 1e-4;
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;
402absl::int128 ApplyToInt128(
const std::function<IntegerValue(IntegerValue)>& f,
403 IntegerValue divisor, absl::int128 value) {
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());
422int ApplyWithPotentialBump(
const std::function<IntegerValue(IntegerValue)>& f,
423 const IntegerValue divisor, CutData* cut) {
425 double bump_score = -1.0;
426 IntegerValue bump_coeff;
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) {
437 const IntegerValue alternative =
439 ? f_remainder - f(remainder - entry.coeff)
440 : f_remainder - IntegerValue(static_cast<int64_t>(ApplyToInt128(
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) {
450 bump_coeff = alternative;
454 entry.coeff = f_coeff;
456 if (bump_index >= 0) {
457 cut->terms[bump_index].coeff = bump_coeff;
469IntegerValue
GetFactorT(IntegerValue rhs_remainder, IntegerValue divisor,
470 IntegerValue max_magnitude) {
474 IntegerValue max_t(std::numeric_limits<int64_t>::max());
475 if (max_magnitude != 0) {
476 max_t = max_t / max_magnitude;
478 return rhs_remainder == 0
480 : std::min(max_t,
CeilRatio(divisor / 2, rhs_remainder));
484 IntegerValue rhs_remainder, IntegerValue divisor, IntegerValue t,
485 IntegerValue max_scaling) {
486 DCHECK_GE(max_scaling, 1);
491 DCHECK_LT(rhs_remainder, divisor);
497 std::min(max_scaling, std::numeric_limits<int64_t>::max() / divisor);
499 const IntegerValue size = divisor - rhs_remainder;
500 if (max_scaling == 1 || size == 1) {
504 return [t, divisor](IntegerValue coeff) {
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);
512 const IntegerValue diff = remainder - rhs_remainder;
513 return size * ratio + std::max(IntegerValue(0), diff);
515 }
else if (max_scaling.value() * rhs_remainder.value() < divisor) {
525 return [t, divisor, max_scaling](IntegerValue coeff) {
526 const IntegerValue t_coeff = t * coeff;
527 const IntegerValue ratio =
FloorRatio(t_coeff, divisor);
529 const IntegerValue bucket =
FloorRatio(remainder * max_scaling, divisor);
530 return max_scaling * ratio + bucket;
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);
559 const IntegerValue diff = remainder - rhs_remainder;
560 const IntegerValue bucket =
561 diff > 0 ?
CeilRatio(diff * (max_scaling - 1), size)
563 return max_scaling * ratio + bucket;
569 IntegerValue positive_rhs, IntegerValue min_magnitude) {
570 CHECK_GT(positive_rhs, 0);
571 CHECK_GT(min_magnitude, 0);
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);
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;
594 if (v >= -min_magnitude)
return -min_magnitude;
602std::function<IntegerValue(IntegerValue)>
604 IntegerValue scaling) {
605 if (scaling >= positive_rhs) {
607 return [positive_rhs](IntegerValue v) {
608 if (v >= 0)
return IntegerValue(0);
609 if (v <= -positive_rhs)
return -positive_rhs;
616 std::min(scaling, IntegerValue(std::numeric_limits<int64_t>::max()) /
619 return [](IntegerValue v) {
620 if (v >= 0)
return IntegerValue(0);
621 return IntegerValue(-1);
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));
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_});
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_});
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_});
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);
654double IntegerRoundingCutHelper::GetScaledViolation(
655 IntegerValue divisor, IntegerValue max_scaling,
656 IntegerValue remainder_threshold,
const CutData& cut) {
657 absl::int128 rhs = cut.
rhs;
660 if (initial_rhs_remainder < remainder_threshold)
return 0.0;
678 adjusted_coeffs_.clear();
679 const IntegerValue adjust_threshold =
680 (divisor - initial_rhs_remainder - 1) /
682 if (adjust_threshold > 0) {
686 double max_violation =
static_cast<double>(initial_rhs_remainder.value());
690 if (remainder == 0)
continue;
691 if (remainder <= initial_rhs_remainder) {
695 static_cast<double>(remainder.value()) * entry.
lp_value;
696 if (max_violation <= 1e-3)
return 0.0;
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));
713 const IntegerValue t =
GetFactorT(rhs_remainder, divisor, max_magnitude);
725 double max_violation = scaling *
ToDouble(rhs_remainder);
731 double violation = -
static_cast<double>(ApplyToInt128(f, divisor, rhs));
732 double l2_norm = 0.0;
733 int adjusted_coeffs_index = 0;
735 const CutTerm& entry = cut.
terms[
i];
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++;
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;
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;
758 if (l2_norm == 0.0)
return 0.0;
768 return violation / sqrt(l2_norm);
779 std::vector<CutTerm>* new_bool_terms =
781 for (
CutTerm& term : cut_.terms) {
790 true, &term, &cut_.rhs, new_bool_terms)) {
791 ++total_num_initial_ibs_;
799 true, &term, &cut_.rhs, new_bool_terms)) {
800 ++total_num_initial_ibs_;
807 if (new_bool_terms->empty())
return false;
808 total_num_initial_merges_ +=
810 absl::MakeSpan(*new_bool_terms), IntegerValue(1), &cut_);
831 const IntegerValue remainder_threshold(
832 std::max(IntegerValue(1), cut_.max_magnitude / 1000));
833 if (cut_.rhs >= 0 && cut_.rhs < remainder_threshold.value()) {
843 for (
const CutTerm& entry : cut_.terms) {
846 if (magnitude <= remainder_threshold)
continue;
847 divisors_.push_back(magnitude);
851 if (divisors_.size() > 50)
break;
853 if (divisors_.empty())
return false;
863 IntegerValue best_divisor(0);
864 double best_scaled_violation = 1e-3;
865 for (
const IntegerValue divisor : divisors_) {
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;
876 if (best_divisor == 0)
return false;
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;
895 for (
CutTerm& entry : cut_.terms) {
897 if (entry.
coeff % best_divisor == 0)
continue;
902 const double violation = GetScaledViolation(
903 best_divisor, options.
max_scaling, remainder_threshold, cut_);
904 if (violation > best_scaled_violation) {
906 ++total_num_post_complements_;
907 best_scaled_violation = violation;
908 best_adjusted_coeffs_ = adjusted_coeffs_;
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()) *
924 cut_.max_magnitude = std::max(cut_.max_magnitude,
IntTypeAbs(new_coeff));
929 IntegerValue factor_t =
930 GetFactorT(rhs_remainder, best_divisor, cut_.max_magnitude);
941 for (
const CutTerm& entry : cut_.terms) {
943 const IntegerValue coeff = entry.
coeff;
945 if (r > rhs_remainder) remainders_.push_back(r);
948 if (remainders_.size() <= 100) {
950 for (
const IntegerValue r : remainders_) {
951 best_rs_.push_back(f(r));
953 IntegerValue best_d = f(best_divisor);
958 for (
const IntegerValue t :
960 GetFactorT(rhs_remainder, best_divisor, cut_.max_magnitude)}) {
961 for (IntegerValue s(2); s <= options.
max_scaling; ++s) {
964 int num_strictly_better = 0;
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++;
973 if (rs_.size() == best_rs_.size() && num_strictly_better > 0) {
974 ++total_num_dominating_f_;
987 if (ib_processor !=
nullptr) {
988 const auto [num_lb, num_ub, num_merges] =
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;
998 for (
int i = 0;
i < 3; ++
i) {
999 const int64_t saved = total_num_final_complements_;
1000 for (
CutTerm& entry : cut_.terms) {
1017 if (entry.
coeff % best_divisor == 0)
continue;
1028 const double lp2 =
ToDouble(f(remainder - prod)) -
1031 if (lp2 + 1e-2 < lp1) {
1033 ++total_num_final_complements_;
1036 if (total_num_final_complements_ == saved)
break;
1039 total_num_bumps_ += ApplyWithPotentialBump(f, best_divisor, &cut_);
1044 if (!VLOG_IS_ON(1))
return;
1045 if (shared_stats_ ==
nullptr)
return;
1047 std::vector<std::pair<std::string, int64_t>> stats;
1048 const auto add_stats = [&stats](absl::string_view name,
const CutStats& s) {
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});
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);
1067struct LargeCoeffFirst {
1069 if (a.
coeff ==
b.coeff) {
1072 return a.
coeff > b.coeff;
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;
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;
1092struct LargeLpValueFirst {
1093 bool operator()(
const CutTerm& a,
const CutTerm& b)
const {
1094 if (a.lp_value ==
b.lp_value) {
1097 return a.coeff >
b.coeff;
1099 return a.lp_value >
b.lp_value;
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;
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;
1132template <
class Compare>
1133int CoverCutHelper::MinimizeCover(
int cover_size, absl::int128 slack) {
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) {
1143 std::swap(terms[i], terms[--cover_size]);
1148 DCHECK_GT(cover_size, 0);
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);
1160 for (
int i = 0;
i < relevant_size;) {
1161 CutTerm& term = terms[
i];
1162 const double dist = term.LpDistToMaxValue();
1165 std::swap(term, terms[part1]);
1168 }
else if (term.lp_value > 1e-6) {
1174 std::swap(term, terms[relevant_size]);
1177 std::sort(terms.begin() + part1, terms.begin() + relevant_size, CompareAdd());
1180 DCHECK_GE(cut_.rhs, 0);
1181 absl::int128 max_shifted_activity = -cut_.rhs;
1182 absl::int128 shifted_round_up = -cut_.rhs;
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)));
1194 DCHECK_GE(cover_size, 0);
1195 if (shifted_round_up <= 0) {
1198 return MinimizeCover<CompareRemove>(cover_size, max_shifted_activity);
1203int CoverCutHelper::GetCoverSizeForBooleans() {
1204 absl::Span<CutTerm> terms = absl::MakeSpan(cut_.terms);
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;
1218 if (terms[i].bound_diff > 1) {
1220 std::swap(terms[i], terms[relevant_size]);
1224 if (lp_value >= threshold) {
1226 std::swap(terms[i], terms[part1]);
1229 }
else if (lp_value > 0.5) {
1235 std::swap(terms[i], terms[relevant_size]);
1237 if (best_in_part3 == -1 ||
1238 LargeLpValueFirst()(terms[relevant_size], terms[best_in_part3])) {
1239 best_in_part3 = relevant_size;
1244 if (best_in_part3 != -1) {
1245 std::swap(terms[relevant_size], terms[best_in_part3]);
1250 std::sort(terms.begin() + part1, terms.begin() + relevant_size,
1251 LargeLpValueFirst());
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();
1267 if (activity > 0.9999) {
1274 slack += absl::int128(term.coeff.value()) *
1275 absl::int128(term.bound_diff.value());
1288 if (cover_size == 0)
return 0;
1289 return MinimizeCover<LargeCoeffFirst>(cover_size, slack);
1292void CoverCutHelper::InitializeCut(
const CutData& input_ct) {
1298 CHECK_GE(cut_.rhs, 0);
1299 DCHECK(cut_.AllCoefficientsArePositive());
1304 InitializeCut(input_ct);
1308 if (ib_processor !=
nullptr) {
1309 std::vector<CutTerm>* new_bool_terms =
1311 for (
CutTerm& term : cut_.terms) {
1319 false, &term, &cut_.rhs, new_bool_terms)) {
1320 ++cover_stats_.num_initial_ibs;
1325 absl::MakeSpan(*new_bool_terms), IntegerValue(1), &cut_);
1328 bool has_relevant_int =
false;
1329 for (
const CutTerm& term : cut_.terms) {
1331 has_relevant_int =
true;
1336 const int base_size =
static_cast<int>(cut_.terms.size());
1337 const int cover_size =
1339 ? GetCoverSize<LargeContribFirst, LargeCoeffFirst>(base_size)
1340 : GetCoverSizeForBooleans();
1341 if (!has_relevant_int && ib_processor ==
nullptr) {
1345 has_bool_base_ct_ =
true;
1346 bool_cover_size_ = cover_size;
1347 if (cover_size == 0)
return false;
1348 bool_base_ct_ = cut_;
1350 if (cover_size == 0)
return false;
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) {
1372 max_coeff_in_cover = std::max(max_coeff_in_cover, term.
coeff);
1373 const double score =
1376 if (score > best_score) {
1378 best_coeff = term.
coeff;
1382 CHECK_LT(cut_.rhs, 0);
1385 best_coeff = max_coeff_in_cover;
1388 std::function<IntegerValue(IntegerValue)> f;
1390 IntegerValue max_magnitude = 0;
1391 for (
const CutTerm& term : cut_.terms) {
1394 const IntegerValue max_scaling(std::min(
1401 if (ib_processor !=
nullptr) {
1402 const auto [num_lb, num_ub, num_merges] =
1404 cover_stats_.num_lb_ibs += num_lb;
1405 cover_stats_.num_ub_ibs += num_ub;
1406 cover_stats_.num_merges += num_merges;
1409 cover_stats_.num_bumps += ApplyWithPotentialBump(f, best_coeff, &cut_);
1412 for (
int i = cover_size;
i < cut_.terms.size(); ++
i) {
1413 if (cut_.terms[
i].coeff != 0) ++num_lifting_;
1415 cover_stats_.num_lifting += num_lifting_;
1416 ++cover_stats_.num_cuts;
1422 InitializeCut(input_ct);
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;
1433 for (
int i = 0;
i < cover_size; ++
i) {
1434 cut_.terms[
i].Complement(&cut_.rhs);
1437 if (cut_.terms[
i].expr_coeffs[1] != 0)
return false;
1452 CHECK_LT(cut_.rhs, 0);
1453 if (cut_.rhs <= absl::int128(std::numeric_limits<int64_t>::min())) {
1457 bool has_large_coeff =
false;
1458 for (
const CutTerm& term : cut_.terms) {
1460 has_large_coeff =
true;
1468 const IntegerValue positive_rhs = -
static_cast<int64_t
>(cut_.rhs);
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);
1474 const bool use_scaling =
1475 has_large_coeff || min_magnitude == 1 || min_magnitude >= positive_rhs;
1481 if (ib_processor !=
nullptr) {
1482 const auto [num_lb, num_ub, num_merges] =
1484 flow_stats_.num_lb_ibs += num_lb;
1485 flow_stats_.num_ub_ibs += num_ub;
1486 flow_stats_.num_merges += num_merges;
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);
1502 if (f(-period +
FloorRatio(period, 2)) != f(-period)) {
1506 CHECK_EQ(f(-period), f(-positive_rhs));
1507 period = std::max(period,
CapProdI(2, positive_rhs) - 1);
1514 cut_.rhs = absl::int128(f(-positive_rhs).value());
1515 for (
CutTerm& term : cut_.terms) {
1516 const IntegerValue old_coeff = term.
coeff;
1518 if (old_coeff > 0 && term.
coeff != 0) ++flow_stats_.num_lifting;
1520 ++flow_stats_.num_cuts;
1527 if (has_bool_base_ct_) {
1530 CHECK(ib_processor ==
nullptr);
1531 cover_size = bool_cover_size_;
1532 if (cover_size == 0)
return false;
1533 InitializeCut(bool_base_ct_);
1535 InitializeCut(input_ct);
1541 if (ib_processor !=
nullptr) {
1542 std::vector<CutTerm>* new_bool_terms =
1544 for (
CutTerm& term : cut_.terms) {
1548 false, &term, &cut_.rhs, new_bool_terms)) {
1549 ++ls_stats_.num_initial_ibs;
1554 absl::MakeSpan(*new_bool_terms), IntegerValue(1), &cut_);
1558 cover_size = GetCoverSizeForBooleans();
1560 if (cover_size == 0)
return false;
1564 if (cut_.rhs > absl::int128(std::numeric_limits<int64_t>::max())) {
1565 ++ls_stats_.num_overflow_aborts;
1568 const IntegerValue rhs =
static_cast<int64_t
>(cut_.rhs);
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);
1580 ++ls_stats_.num_overflow_aborts;
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;
1597 previous_sum += cover_weights[
i];
1604 std::vector<IntegerValue> thresholds;
1605 for (
int i = 0;
i < q; ++
i) {
1607 if (
CapProd(p.value(),
i + 1) >= std::numeric_limits<int64_t>::max() - 1) {
1608 ++ls_stats_.num_overflow_aborts;
1611 thresholds.push_back(
CeilRatio(p * (
i + 1) + 1, q));
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]);
1619 CHECK_EQ(thresholds.back(), rhs + 1);
1629 temp_cut_.rhs = cover_size - 1;
1630 temp_cut_.terms.clear();
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;
1644 for (
int i = 1;
i < cover_size; ++
i) {
1645 if (coeff < thresholds[
i])
break;
1646 cut_coeff = IntegerValue(
i + 1);
1648 if (cut_coeff != 0 &&
i >= cover_size) ++ls_stats_.num_lifting;
1649 if (cut_coeff > 1 &&
i < cover_size) ++ls_stats_.num_lifting;
1652 temp_cut_.terms.push_back(term);
1653 temp_cut_.terms.back().coeff = cut_coeff;
1657 ++ls_stats_.num_cuts;
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);
1670 product_detector_->InitializeBooleanRLTCuts(lp_vars, *lp_values_);
1671 enabled_ = !product_detector_->BoolRLTCandidates().empty();
1676 if (!enabled_)
return false;
1683 absl::flat_hash_set<IntegerVariable> to_try_set;
1684 std::vector<IntegerVariable> to_try;
1701 filtered_input_.terms.clear();
1702 filtered_input_.rhs = input_ct.
rhs;
1704 const auto& candidates = product_detector_->BoolRLTCandidates();
1718 filtered_input_.rhs -= absl::int128(term.
coeff.value()) *
1729 for (
const IntegerVariable factor : candidates.at(
NegationOf(var))) {
1730 if (to_try_set.insert(factor).second) to_try.push_back(factor);
1733 filtered_input_.terms.push_back(term);
1737 double best_efficacy = 1e-3;
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;
1751 return TryProduct(best_factor, input_ct);
1760enum class LinearizationOption {
1769double BoolRLTCutHelper::GetLiteralLpValue(IntegerVariable var)
const {
1770 if (VariableIsPositive(var))
return (*lp_values_)[var];
1771 return 1.0 - (*lp_values_)[PositiveVariable(var)];
1774bool BoolRLTCutHelper::TryProduct(IntegerVariable factor,
1775 const CutData&
input) {
1778 absl::int128 old_rhs =
input.rhs;
1780 const double factor_lp = GetLiteralLpValue(factor);
1783 for (CutTerm term :
input.terms) {
1784 LinearizationOption best_option = LinearizationOption::DROP;
1787 const IntegerVariable var = term.GetUnderlyingLiteralOrNone();
1791 if (factor == var) {
1794 best_option = LinearizationOption::SQUARE;
1795 cut_.terms.push_back(term);
1802 best_option = LinearizationOption::DROP;
1812 double best_lp = 0.0;
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;
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) {
1835 best_option = LinearizationOption::RLT;
1838 term.Complement(&old_rhs);
1841 term.lp_value = lit_lp;
1842 term.ReplaceExpressionByLiteral(ub_lit);
1843 cut_.terms.push_back(term);
1849 if (best_option == LinearizationOption::DROP)
continue;
1852 CHECK(best_option == LinearizationOption::MC_CORMICK);
1853 term.Complement(&old_rhs);
1854 cut_.terms.push_back(term);
1861 const absl::int128 limit(int64_t{1} << 50);
1862 if (old_rhs > limit || old_rhs < -limit)
return false;
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);
1877 int linearization_level,
1887 result.
generate_cuts = [z, x, y, linearization_level, model, trail,
1889 if (trail->CurrentDecisionLevel() > 0 && linearization_level == 1) {
1898 if (x_lb == x_ub || y_lb == y_ub)
return true;
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) {
1910 if (
CapProd(x_max_amp, std::abs(y.
constant.value())) > kMaxSafeInteger) {
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);
1925 auto try_add_above_cut = [&](int64_t x_coeff, int64_t y_coeff,
1927 if (-z_lp_value + x_lp_value * x_coeff + y_lp_value * y_coeff >=
1928 rhs + kMinCutViolation) {
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");
1939 auto try_add_below_cut = [&](int64_t x_coeff, int64_t y_coeff,
1941 if (-z_lp_value + x_lp_value * x_coeff + y_lp_value * y_coeff <=
1942 rhs - kMinCutViolation) {
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");
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);
1972 IntegerValue x_ub,
Model* model) {
1973 const IntegerValue above_slope = x_ub + x_lb;
1976 above_hyperplan.
AddTerm(square, 1);
1977 above_hyperplan.
AddTerm(x, -above_slope);
1978 return above_hyperplan.
Build();
1983 IntegerValue x_value,
1985 const IntegerValue below_slope = 2 * x_value + 1;
1988 below_hyperplan.
AddTerm(square, 1);
1989 below_hyperplan.
AddTerm(x, -below_slope);
1990 return below_hyperplan.
Build();
1994 int linearization_level,
Model* model) {
2001 result.
generate_cuts = [y, x, linearization_level, trail, integer_trail,
2006 const IntegerValue x_ub = integer_trail->LevelZeroUpperBound(x);
2007 const IntegerValue x_lb = integer_trail->LevelZeroLowerBound(x);
2010 if (x_lb == x_ub)
return true;
2013 if (x_ub > (int64_t{1} << 31))
return true;
2018 const IntegerValue x_floor =
2019 static_cast<int64_t
>(std::floor(x.LpValue(manager->LpValues())));
2028ImpliedBoundsProcessor::BestImpliedBoundInfo
2030 auto it = cache_.find(var);
2031 if (it != cache_.end()) {
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);
2050 implied_bounds_->GetImpliedBounds(var)) {
2062 const IntegerValue diff = entry.lower_bound - lb;
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);
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) {
2078 terms.push_back({entry.literal_view, diff});
2079 terms.push_back({var, IntegerValue(-1)});
2083 terms.push_back({entry.literal_view, -diff});
2084 terms.push_back({var, IntegerValue(-1)});
2085 ib_cut.ub = -entry.lower_bound;
2088 ib_cut_pool_.AddCut(std::move(ib_cut),
"IB", lp_values);
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;
2105 cache_[var] = result;
2112 for (
const IntegerVariable var :
2113 implied_bounds_->VariablesWithImpliedBounds()) {
2115 ComputeBestImpliedBound(var, lp_values);
2124 if (!term.
IsSimple())
return false;
2149 if (bound_diff <= 0)
return false;
2192 absl::int128 unused = 0;
2206 CHECK_EQ(unused, absl::int128(0));
2211 const std::function<IntegerValue(IntegerValue)>& f, IntegerValue factor_t,
2213 int num_applied_lb = 0;
2214 int num_applied_ub = 0;
2229 bool expand =
false;
2237 base_score = AsDouble(f(bool_term.
coeff)) * bool_term.
lp_value +
2265 const double score =
2270 if (score > base_score + 1e-2) {
2272 term = ub_slack_term;
2273 tmp_terms_.push_back(ub_bool_term);
2281 tmp_terms_.push_back(bool_term);
2285 const int num_merges = cut_builder_.AddOrMergeBooleanTerms(
2286 absl::MakeSpan(tmp_terms_), factor_t, cut);
2288 return {num_applied_lb, num_applied_ub, num_merges};
2292 IntegerValue factor_t,
bool complement,
CutTerm* term, absl::int128* rhs,
2293 std::vector<CutTerm>* new_bool_terms) {
2315 new_bool_terms->push_back(bool_term);
2321 cached_data_.clear();
2323 const int size = cut->
terms.size();
2324 for (
int i = 0;
i < size; ++
i) {
2328 if (term.
expr_vars[0] >= first_slack)
continue;
2331 const IntegerVariable ib_var = term.
expr_coeffs[0] > 0
2336 cut->
terms[
i].cached_implied_lb = cached_data_.size();
2337 cached_data_.emplace_back(std::move(lb_info));
2342 cut->
terms[
i].cached_implied_ub = cached_data_.size();
2343 cached_data_.emplace_back(std::move(ub_info));
2347 return !cached_data_.empty();
2351 min_values_.clear();
2359 if (integer_trail.
IsFixed(expr)) {
2360 min_values_.insert(integer_trail.
FixedValue(expr));
2362 if (expr.
coeff > 0) {
2364 for (
const IntegerValue value :
2366 min_values_.insert(expr.
ValueAt(value));
2367 if (++count >= num_exprs)
break;
2371 for (
const IntegerValue value :
2373 min_values_.insert(-expr.
ValueAt(value));
2374 if (++count >= num_exprs)
break;
2382 IntegerValue sum = 0;
2383 for (
const IntegerValue value : min_values_) {
2385 if (++count >= expr_mins_.size())
return sum;
2391 std::sort(expr_mins_.begin(), expr_mins_.end());
2393 IntegerValue result = 0;
2394 for (
const IntegerValue value : expr_mins_) {
2396 tmp_value = std::max(tmp_value + 1, value);
2397 result += tmp_value;
2405 if (domain_bound > alldiff_bound) {
2407 return domain_bound;
2409 suffix = alldiff_bound > domain_bound ?
"a" :
"e";
2410 return alldiff_bound;
2415void TryToGenerateAllDiffCut(
2416 absl::Span<
const std::pair<double, AffineExpression>> sorted_exprs_lp,
2420 const int num_exprs = sorted_exprs_lp.size();
2422 std::vector<AffineExpression> current_set_exprs;
2428 for (
const auto& [expr_lp, expr] : sorted_exprs_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 =
2438 std::string max_suffix;
2439 const IntegerValue required_max_sum =
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) {
2447 cut.AddTerm(expr, IntegerValue(1));
2449 top_n_cuts.
AddCut(cut.Build(),
2450 absl::StrCat(
"AllDiff_", min_suffix, max_suffix),
2456 current_set_exprs.clear();
2458 negated_diff_maxes.
Clear();
2466 absl::Span<const AffineExpression> exprs,
Model* model) {
2471 if (!integer_trail->
IsFixed(expr)) {
2472 result.
vars.push_back(expr.var);
2479 [exprs = std::vector<AffineExpression>(exprs.begin(), exprs.end()),
2485 const auto& lp_values = manager->LpValues();
2486 std::vector<std::pair<double, AffineExpression>> sorted_exprs;
2492 sorted_exprs.push_back(std::make_pair(expr.LpValue(lp_values), expr));
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;
2501 TryToGenerateAllDiffCut(sorted_exprs, *integer_trail, lp_values,
2504 std::reverse(sorted_exprs.begin(), sorted_exprs.end());
2505 TryToGenerateAllDiffCut(sorted_exprs, *integer_trail, lp_values,
2510 VLOG(2) <<
"Created all_diff cut generator of size: " << exprs.size();
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);
2531IntegerValue MPlusCoefficient(
2532 absl::Span<const IntegerVariable> x_vars,
2533 absl::Span<const LinearExpression> exprs,
2535 const int max_index,
const IntegerTrail& integer_trail) {
2536 IntegerValue coeff = exprs[max_index].offset;
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);
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];
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(
2571 contrib +=
ToDouble(corner_value) * z_max_value;
2578 absl::Span<const LinearExpression> exprs,
2579 absl::Span<const IntegerVariable> z_vars,
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());
2591 DCHECK(std::all_of(x_vars.begin(), x_vars.end(), [](IntegerVariable var) {
2592 return VariableIsPositive(var);
2594 result.
vars.insert(result.
vars.end(), x_vars.begin(), x_vars.end());
2599 z_vars = std::vector<IntegerVariable>(z_vars.begin(), z_vars.end()),
2601 exprs = std::vector<LinearExpression>(exprs.begin(), exprs.end()),
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;
2623 double violation = lp_values[target];
2624 cut.
AddTerm(target, IntegerValue(-1));
2626 for (
const IntegerVariable xi_var : x_vars) {
2627 const int input_index = variable_partition[xi_var];
2630 if (coeff != IntegerValue(0)) {
2633 violation -=
ToDouble(coeff) * lp_values[xi_var];
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)) {
2642 violation -=
ToDouble(z_coeff) * lp_values[z_var];
2644 if (violation > 1e-2) {
2645 manager->AddCut(cut.
Build(),
"LinMax");
2654IntegerValue EvaluateMaxAffine(
2655 absl::Span<
const std::pair<IntegerValue, IntegerValue>> affines,
2657 IntegerValue y = kMinIntegerValue;
2658 for (
const auto& p : affines) {
2659 y = std::max(y, x * p.first + p.second);
2668 absl::Span<
const std::pair<IntegerValue, IntegerValue>> affines,
2672 const IntegerValue x_max = integer_trail->LevelZeroUpperBound(var);
2674 const IntegerValue y_at_min = EvaluateMaxAffine(affines, x_min);
2675 const IntegerValue y_at_max = EvaluateMaxAffine(affines, x_max);
2677 const IntegerValue delta_x = x_max - x_min;
2678 const IntegerValue delta_y = y_at_max - y_at_min;
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);
2697 CapProd(std::max(std::abs(var_min.value()), std::abs(var_max.value())),
2698 std::abs(target.
coeffs[
i].value())),
2708 builder->
AddTerm(var, -delta_y);
2712 VLOG(2) <<
"Linear constraint can cause overflow: " << builder->
Build();
2722 std::vector<std::pair<IntegerValue, IntegerValue>> affines,
2723 const std::string cut_name,
Model* model) {
2726 result.
vars.push_back(var);
2730 result.
generate_cuts = [target, var, affines, cut_name, integer_trail,
2732 if (integer_trail->
IsFixed(var))
return true;
2735 manager->AddCut(builder.
Build(), cut_name);
2743 absl::Span<const IntegerVariable> base_variables,
Model* model) {
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;
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(
2758 variables.push_back(var);
2759 literals.push_back(
Literal(literal_index));
2760 positive_map[literal_index] = var;
2765 result.
vars = variables;
2768 result.
generate_cuts = [variables, literals, implication_graph, positive_map,
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]]);
2779 const std::vector<std::vector<Literal>> at_most_ones =
2780 implication_graph->GenerateAtMostOnesWithLargeWeight(
2781 literals, packed_values, packed_reduced_costs);
2783 for (
const std::vector<Literal>& at_most_one : at_most_ones) {
2788 model, IntegerValue(std::numeric_limits<int64_t>::min()),
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));
2795 builder.
AddTerm(negative_map.at(l.Index()), IntegerValue(-1));
2800 manager->AddCut(builder.
Build(),
"Clique");
DomainIteratorBeginEnd Values() const &
bool TrySimpleSeparation(const CutData &input_ct)
Tries RLT separation of the input constraint. Returns true on success.
void Initialize(absl::Span< const IntegerVariable > lp_vars)
bool TrySimpleKnapsack(const CutData &input_ct, ImpliedBoundsProcessor *ib_processor=nullptr)
bool TryWithLetchfordSouliLifting(const CutData &input_ct, ImpliedBoundsProcessor *ib_processor=nullptr)
bool TrySingleNodeFlow(const CutData &input_ct, ImpliedBoundsProcessor *ib_processor=nullptr)
Stores temporaries used to build or manipulate a CutData.
bool ConvertToLinearConstraint(const CutData &cut, LinearConstraint *output)
Returns false if we encounter an integer overflow.
int AddOrMergeBooleanTerms(absl::Span< CutTerm > terms, IntegerValue t, CutData *cut)
std::vector< CutTerm > * ClearedMutableTempTerms()
void RecomputeCacheAndSeparateSomeImpliedBoundCuts(const util_intops::StrongVector< IntegerVariable, double > &lp_values)
bool CacheDataForCut(IntegerVariable first_slack, CutData *cut)
bool TryToExpandWithLowerImpliedbound(IntegerValue factor_t, bool complement, CutTerm *term, absl::int128 *rhs, std::vector< CutTerm > *new_bool_terms)
BestImpliedBoundInfo GetCachedImpliedBoundInfo(IntegerVariable var) const
CutDataBuilder * MutableCutBuilder()
This can be used to share the hash-map memory.
bool DecomposeWithImpliedLowerBound(const CutTerm &term, IntegerValue factor_t, CutTerm &bool_term, CutTerm &slack_term)
std::tuple< int, int, int > PostprocessWithImpliedBound(const std::function< IntegerValue(IntegerValue)> &f, IntegerValue factor_t, CutData *cut)
bool DecomposeWithImpliedUpperBound(const CutTerm &term, IntegerValue factor_t, CutTerm &bool_term, CutTerm &slack_term)
~IntegerRoundingCutHelper()
bool ComputeCut(RoundingOptions options, const CutData &base_ct, ImpliedBoundsProcessor *ib_processor=nullptr)
Returns true on success. The cut can be accessed via cut().
IntegerValue FixedValue(IntegerVariable i) const
Checks that the variable is fixed and returns its value.
bool IsFixed(IntegerVariable i) const
Checks if the variable is fixed.
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
const Domain & InitialVariableDomain(IntegerVariable var) const
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Returns globally valid lower/upper bound on the given integer variable.
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
Utility class for the AllDiff cut generator.
IntegerValue GetBestLowerBound(std::string &suffix)
void Add(const AffineExpression &expr, int num_expr, const IntegerTrail &integer_trail)
IntegerValue SumOfDifferentMins()
IntegerValue SumOfMinDomainValues()
Return int_max if the sum overflows.
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.
int CurrentDecisionLevel() const
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
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)
IntegerValue GetFactorT(IntegerValue rhs_remainder, IntegerValue divisor, IntegerValue max_magnitude)
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).
bool ProdOverflow(IntegerValue t, IntegerValue value)
const LiteralIndex kNoLiteralIndex(-1)
LinearConstraint ComputeHyperplanBelowSquare(AffineExpression x, AffineExpression square, IntegerValue x_value, Model *model)
std::vector< IntegerVariable > NegationOf(absl::Span< const IntegerVariable > vars)
Returns the vector of the negated variables.
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)
void CleanTermsAndFillConstraint(std::vector< std::pair< IntegerVariable, IntegerValue > > *terms, LinearExpression *output)
std::function< IntegerValue(IntegerValue)> GetSuperAdditiveStrengtheningFunction(IntegerValue positive_rhs, IntegerValue min_magnitude)
CutGenerator CreateAllDifferentCutGenerator(absl::Span< const AffineExpression > exprs, Model *model)
CutGenerator CreateSquareCutGenerator(AffineExpression y, AffineExpression x, int linearization_level, Model *model)
CutGenerator CreateCliqueCutGenerator(absl::Span< const IntegerVariable > base_variables, Model *model)
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)
IntegerValue CapAddI(IntegerValue a, IntegerValue b)
LinearConstraint ComputeHyperplanAboveSquare(AffineExpression x, AffineExpression square, IntegerValue x_lb, IntegerValue x_ub, Model *model)
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)
std::function< IntegerValue(IntegerValue)> GetSuperAdditiveStrengtheningMirFunction(IntegerValue positive_rhs, IntegerValue scaling)
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)
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)
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.
bool FillFromLinearConstraint(const LinearConstraint &base_ct, const util_intops::StrongVector< IntegerVariable, double > &lp_values, IntegerTrail *integer_trail)
bool AllCoefficientsArePositive() const
These functions transform the cut by complementation.
std::vector< CutTerm > terms
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)
double ComputeEfficacy() const
void ComplementForPositiveCoefficients()
std::string DebugString() const
double ComputeViolation() const
Computes and returns the cut violation.
IntegerValue max_magnitude
Only filled after SortRelevantEntries().
bool AppendOneTerm(IntegerVariable var, IntegerValue coeff, double lp_value, IntegerValue lb, IntegerValue ub)
void SortRelevantEntries()
void ComplementForSmallerLpValues()
absl::AnyInvocable< bool(LinearConstraintManager *manager)> generate_cuts
std::vector< IntegerVariable > vars
bool only_run_at_level_zero
IntegerVariable GetUnderlyingLiteralOrNone() const
std::string DebugString() const
int cached_implied_lb
Refer to cached_data_ in ImpliedBoundsProcessor.
double LpDistToMaxValue() const
void ReplaceExpressionByLiteral(IntegerVariable var)
void Complement(absl::int128 *rhs)
std::array< IntegerVariable, 2 > expr_vars
std::array< IntegerValue, 2 > expr_coeffs
bool HasRelevantLpValue() const
double SlackLpValue(IntegerValue lb) const
IntegerValue implied_bound
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
std::unique_ptr< IntegerValue[]> coeffs
std::unique_ptr< IntegerVariable[]> vars
std::vector< IntegerVariable > vars
std::vector< IntegerValue > coeffs
bool use_ib_before_heuristic