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/log/log.h"
34#include "absl/log/vlog_is_on.h"
35#include "absl/meta/type_traits.h"
36#include "absl/numeric/int128.h"
37#include "absl/strings/str_cat.h"
38#include "absl/strings/string_view.h"
39#include "absl/types/span.h"
64double AsDouble(IntegerValue v) {
return static_cast<double>(v.value()); }
69 return absl::StrCat(
"coeff=",
coeff.value(),
" lp=",
lp_value,
77 std::string result = absl::StrCat(
"CutData rhs=",
rhs,
"\n");
79 absl::StrAppend(&result, term.DebugString(),
"\n");
90 for (
int i = 0;
i < 2; ++
i) {
138 double lp_value, IntegerValue lb, IntegerValue ub) {
139 if (coeff == 0)
return true;
140 const IntegerValue bound_diff = ub - lb;
143 const bool complement = coeff < 0;
146 rhs -= absl::int128(coeff.value()) *
147 absl::int128(complement ? ub.value() : lb.value());
151 if (bound_diff == 0)
return true;
161 entry.
coeff = -coeff;
162 entry.
lp_value =
static_cast<double>(ub.value()) - lp_value;
168 entry.
lp_value = lp_value -
static_cast<double>(lb.value());
170 terms.push_back(entry);
178 rhs = absl::int128(base_ct.
ub.value());
181 for (
int i = 0;
i < num_terms; ++
i) {
182 const IntegerVariable var = base_ct.
vars[
i];
193 IntegerValue ub, absl::Span<const IntegerVariable> vars,
194 absl::Span<const IntegerValue> coeffs, absl::Span<const double> lp_values,
195 absl::Span<const IntegerValue> lower_bounds,
196 absl::Span<const IntegerValue> upper_bounds) {
197 rhs = absl::int128(ub.value());
200 const int size = lp_values.size();
201 if (size == 0)
return true;
203 CHECK_EQ(vars.size(), size);
204 CHECK_EQ(coeffs.size(), size);
205 CHECK_EQ(lower_bounds.size(), size);
206 CHECK_EQ(upper_bounds.size(), size);
208 for (
int i = 0;
i < size; ++
i) {
220 if (term.coeff >= 0)
continue;
221 term.Complement(&
rhs);
227 if (term.lp_value <= term.LpDistToMaxValue())
continue;
228 term.Complement(&
rhs);
234 if (term.coeff < 0)
return false;
244 if (entry.HasRelevantLpValue()) {
253 return a.lp_value > b.lp_value;
258 double violation = -
static_cast<double>(
rhs);
260 violation += term.lp_value *
static_cast<double>(term.coeff.value());
266 double violation = -
static_cast<double>(
rhs);
269 const double coeff =
static_cast<double>(term.coeff.value());
270 violation += term.lp_value * coeff;
271 norm += coeff * coeff;
273 return violation / std::sqrt(norm);
281bool CutDataBuilder::MergeIfPossible(IntegerValue t,
CutTerm& to_add,
292 target.
coeff = new_coeff;
301 IntegerValue t,
CutData* cut) {
302 if (new_terms.empty())
return 0;
305 secondary_bool_index_.clear();
310 for (
CutTerm& term : new_terms) {
311 const IntegerVariable var = term.expr_vars[0];
312 auto& map = term.expr_coeffs[0] > 0 ? bool_index_ : secondary_bool_index_;
313 const auto [it, inserted] = map.insert({var,
i});
315 if (MergeIfPossible(t, term, new_terms[it->second])) {
328 const IntegerVariable var = term.
expr_vars[0];
329 auto& map = term.
expr_coeffs[0] > 0 ? bool_index_ : secondary_bool_index_;
330 auto it = map.find(var);
331 if (it == map.end())
continue;
336 if (MergeIfPossible(t, new_terms[it->second], term)) {
342 for (
const CutTerm& term : new_terms) {
343 if (term.
coeff == 0)
continue;
344 cut->
terms.push_back(term);
352ABSL_DEPRECATED(
"Only used in tests, this will be removed.")
356 if (cut.rhs > absl::int128(std::numeric_limits<int64_t>::max()) ||
357 cut.rhs < absl::int128(std::numeric_limits<int64_t>::min())) {
360 IntegerValue new_rhs =
static_cast<int64_t
>(cut.rhs);
361 for (
const CutTerm& term : cut.terms) {
362 for (int i = 0; i < 2; ++i) {
363 if (term.expr_coeffs[i] == 0) continue;
364 if (!AddProductTo(term.coeff, term.expr_coeffs[i],
365 &tmp_map_[term.expr_vars[i]])) {
375 output->ub = new_rhs;
376 output->resize(tmp_map_.size());
378 for (
const auto [var, coeff] : tmp_map_) {
379 if (coeff == 0)
continue;
380 output->vars[new_size] = var;
381 output->coeffs[new_size] = coeff;
384 output->resize(new_size);
393const double kMinCutViolation = 1e-4;
395IntegerValue PositiveRemainder(absl::int128 dividend,
396 IntegerValue positive_divisor) {
397 DCHECK_GT(positive_divisor, 0);
398 const IntegerValue m =
399 static_cast<int64_t
>(dividend % absl::int128(positive_divisor.value()));
400 return m < 0 ? m + positive_divisor : m;
404absl::int128 ApplyToInt128(
const std::function<IntegerValue(IntegerValue)>& f,
405 IntegerValue divisor, absl::int128 value) {
407 const absl::int128 k =
408 (value - absl::int128(rest.value())) / absl::int128(divisor.value());
409 const absl::int128 result =
410 k * absl::int128(f(divisor).value()) + absl::int128(f(rest).value());
424int ApplyWithPotentialBump(
const std::function<IntegerValue(IntegerValue)>& f,
425 const IntegerValue divisor, CutData* cut) {
427 double bump_score = -1.0;
428 IntegerValue bump_coeff;
430 const IntegerValue f_remainder = f(remainder);
431 cut->rhs = ApplyToInt128(f, divisor, cut->rhs);
432 for (
int i = 0;
i < cut->terms.size(); ++
i) {
433 CutTerm& entry = cut->terms[
i];
434 const IntegerValue f_coeff = f(entry.coeff);
435 if (entry.bound_diff == 1) {
439 const IntegerValue alternative =
441 ? f_remainder - f(remainder - entry.coeff)
442 : f_remainder - IntegerValue(static_cast<int64_t>(ApplyToInt128(
444 absl::int128(remainder.value()) -
445 absl::int128(entry.coeff.value()))));
446 DCHECK_GE(alternative, f_coeff);
447 if (alternative > f_coeff) {
448 const double score =
ToDouble(alternative - f_coeff) * entry.lp_value;
449 if (score > bump_score) {
452 bump_coeff = alternative;
456 entry.coeff = f_coeff;
458 if (bump_index >= 0) {
459 cut->terms[bump_index].coeff = bump_coeff;
471IntegerValue
GetFactorT(IntegerValue rhs_remainder, IntegerValue divisor,
472 IntegerValue max_magnitude) {
476 IntegerValue max_t(std::numeric_limits<int64_t>::max());
477 if (max_magnitude != 0) {
478 max_t = max_t / max_magnitude;
480 return rhs_remainder == 0
482 : std::min(max_t,
CeilRatio(divisor / 2, rhs_remainder));
486 IntegerValue rhs_remainder, IntegerValue divisor, IntegerValue t,
487 IntegerValue max_scaling) {
488 DCHECK_GE(max_scaling, 1);
493 DCHECK_LT(rhs_remainder, divisor);
499 std::min(max_scaling, std::numeric_limits<int64_t>::max() / divisor);
501 const IntegerValue size = divisor - rhs_remainder;
502 if (max_scaling == 1 || size == 1) {
506 return [t, divisor](IntegerValue coeff) {
509 }
else if (size <= max_scaling) {
510 return [size, rhs_remainder, t, divisor](IntegerValue coeff) {
511 const IntegerValue t_coeff = t * coeff;
512 const IntegerValue ratio =
FloorRatio(t_coeff, divisor);
514 const IntegerValue diff = remainder - rhs_remainder;
515 return size * ratio + std::max(IntegerValue(0), diff);
517 }
else if (max_scaling.value() * rhs_remainder.value() < divisor) {
527 return [t, divisor, max_scaling](IntegerValue coeff) {
528 const IntegerValue t_coeff = t * coeff;
529 const IntegerValue ratio =
FloorRatio(t_coeff, divisor);
531 const IntegerValue bucket =
FloorRatio(remainder * max_scaling, divisor);
532 return max_scaling * ratio + bucket;
557 return [size, rhs_remainder, t, divisor, max_scaling](IntegerValue coeff) {
558 const IntegerValue t_coeff = t * coeff;
559 const IntegerValue ratio =
FloorRatio(t_coeff, divisor);
561 const IntegerValue diff = remainder - rhs_remainder;
562 const IntegerValue bucket =
563 diff > 0 ?
CeilRatio(diff * (max_scaling - 1), size)
565 return max_scaling * ratio + bucket;
571 IntegerValue positive_rhs, IntegerValue min_magnitude) {
572 CHECK_GT(positive_rhs, 0);
573 CHECK_GT(min_magnitude, 0);
575 if (min_magnitude >=
CeilRatio(positive_rhs, 2)) {
576 return [positive_rhs](IntegerValue v) {
577 if (v >= 0)
return IntegerValue(0);
578 if (v > -positive_rhs)
return IntegerValue(-1);
579 return IntegerValue(-2);
586 min_magnitude = std::min(min_magnitude,
FloorRatio(positive_rhs, 2));
587 const IntegerValue second_threshold = positive_rhs - min_magnitude;
588 return [positive_rhs, min_magnitude, second_threshold](IntegerValue v) {
589 if (v >= 0)
return IntegerValue(0);
590 if (v <= -positive_rhs)
return -positive_rhs;
591 if (v <= -second_threshold)
return -second_threshold;
596 if (v >= -min_magnitude)
return -min_magnitude;
604std::function<IntegerValue(IntegerValue)>
606 IntegerValue scaling) {
607 if (scaling >= positive_rhs) {
609 return [positive_rhs](IntegerValue v) {
610 if (v >= 0)
return IntegerValue(0);
611 if (v <= -positive_rhs)
return -positive_rhs;
618 std::min(scaling, IntegerValue(std::numeric_limits<int64_t>::max()) /
621 return [](IntegerValue v) {
622 if (v >= 0)
return IntegerValue(0);
623 return IntegerValue(-1);
628 return [positive_rhs, scaling](IntegerValue v) {
629 if (v >= 0)
return IntegerValue(0);
630 if (v <= -positive_rhs)
return -scaling;
631 return FloorRatio(v * (scaling - 1), (positive_rhs - 1));
636 if (!VLOG_IS_ON(1))
return;
637 if (shared_stats_ ==
nullptr)
return;
638 std::vector<std::pair<std::string, int64_t>> stats;
639 stats.push_back({
"rounding_cut/num_initial_ibs_", total_num_initial_ibs_});
641 {
"rounding_cut/num_initial_merges_", total_num_initial_merges_});
642 stats.push_back({
"rounding_cut/num_pos_lifts", total_num_pos_lifts_});
643 stats.push_back({
"rounding_cut/num_neg_lifts", total_num_neg_lifts_});
645 {
"rounding_cut/num_post_complements", total_num_post_complements_});
646 stats.push_back({
"rounding_cut/num_overflows", total_num_overflow_abort_});
647 stats.push_back({
"rounding_cut/num_adjusts", total_num_coeff_adjust_});
648 stats.push_back({
"rounding_cut/num_merges", total_num_merges_});
649 stats.push_back({
"rounding_cut/num_bumps", total_num_bumps_});
651 {
"rounding_cut/num_final_complements", total_num_final_complements_});
652 stats.push_back({
"rounding_cut/num_dominating_f", total_num_dominating_f_});
653 shared_stats_->AddStats(stats);
656double IntegerRoundingCutHelper::GetScaledViolation(
657 IntegerValue divisor, IntegerValue max_scaling,
658 IntegerValue remainder_threshold,
const CutData& cut) {
659 absl::int128 rhs = cut.
rhs;
662 if (initial_rhs_remainder < remainder_threshold)
return 0.0;
680 adjusted_coeffs_.clear();
681 const IntegerValue adjust_threshold =
682 (divisor - initial_rhs_remainder - 1) /
684 if (adjust_threshold > 0) {
688 double max_violation =
static_cast<double>(initial_rhs_remainder.value());
692 if (remainder == 0)
continue;
693 if (remainder <= initial_rhs_remainder) {
697 static_cast<double>(remainder.value()) * entry.
lp_value;
698 if (max_violation <= 1e-3)
return 0.0;
703 const IntegerValue adjust = divisor - remainder;
704 const IntegerValue prod = CapProdI(adjust, entry.
bound_diff);
705 if (prod <= adjust_threshold) {
706 rhs += absl::int128(prod.value());
707 const IntegerValue new_coeff = entry.
coeff + adjust;
708 adjusted_coeffs_.push_back({i, new_coeff});
709 max_magnitude = std::max(max_magnitude, IntTypeAbs(new_coeff));
715 const IntegerValue t =
GetFactorT(rhs_remainder, divisor, max_magnitude);
727 double max_violation = scaling *
ToDouble(rhs_remainder);
733 double violation = -
static_cast<double>(ApplyToInt128(f, divisor, rhs));
734 double l2_norm = 0.0;
735 int adjusted_coeffs_index = 0;
737 const CutTerm& entry = cut.
terms[
i];
740 IntegerValue coeff = entry.coeff;
741 if (adjusted_coeffs_index < adjusted_coeffs_.size() &&
742 adjusted_coeffs_[adjusted_coeffs_index].first == i) {
743 coeff = adjusted_coeffs_[adjusted_coeffs_index].second;
744 adjusted_coeffs_index++;
747 if (coeff == 0)
continue;
748 const IntegerValue new_coeff = f(coeff);
749 const double new_coeff_double =
ToDouble(new_coeff);
750 const double lp_value = entry.lp_value;
755 l2_norm += new_coeff_double * new_coeff_double;
756 violation += new_coeff_double * lp_value;
757 max_violation -= (scaling *
ToDouble(coeff) - new_coeff_double) * lp_value;
758 if (max_violation <= 1e-3)
return 0.0;
760 if (l2_norm == 0.0)
return 0.0;
770 return violation / sqrt(l2_norm);
781 std::vector<CutTerm>* new_bool_terms =
783 for (
CutTerm& term : cut_.terms) {
792 true, &term, &cut_.rhs, new_bool_terms)) {
793 ++total_num_initial_ibs_;
801 true, &term, &cut_.rhs, new_bool_terms)) {
802 ++total_num_initial_ibs_;
809 if (new_bool_terms->empty())
return false;
810 total_num_initial_merges_ +=
812 absl::MakeSpan(*new_bool_terms), IntegerValue(1), &cut_);
833 const IntegerValue remainder_threshold(
834 std::max(IntegerValue(1), cut_.max_magnitude / 1000));
835 if (cut_.rhs >= 0 && cut_.rhs < remainder_threshold.value()) {
845 for (
const CutTerm& entry : cut_.terms) {
848 if (magnitude <= remainder_threshold)
continue;
849 divisors_.push_back(magnitude);
853 if (divisors_.size() > 50)
break;
855 if (divisors_.empty())
return false;
865 IntegerValue best_divisor(0);
866 double best_scaled_violation = 1e-3;
867 for (
const IntegerValue divisor : divisors_) {
870 const double violation = GetScaledViolation(divisor, options.
max_scaling,
871 remainder_threshold, cut_);
872 if (violation > best_scaled_violation) {
873 best_scaled_violation = violation;
874 best_adjusted_coeffs_ = adjusted_coeffs_;
875 best_divisor = divisor;
878 if (best_divisor == 0)
return false;
881 for (
int div = 2; div < 9; ++div) {
882 const IntegerValue divisor = best_divisor / IntegerValue(div);
883 if (divisor <= 1)
continue;
884 const double violation = GetScaledViolation(divisor, options.
max_scaling,
885 remainder_threshold, cut_);
886 if (violation > best_scaled_violation) {
887 best_scaled_violation = violation;
888 best_adjusted_coeffs_ = adjusted_coeffs_;
889 best_divisor = divisor;
897 for (
CutTerm& entry : cut_.terms) {
899 if (entry.
coeff % best_divisor == 0)
continue;
904 const double violation = GetScaledViolation(
905 best_divisor, options.
max_scaling, remainder_threshold, cut_);
906 if (violation > best_scaled_violation) {
908 ++total_num_post_complements_;
909 best_scaled_violation = violation;
910 best_adjusted_coeffs_ = adjusted_coeffs_;
918 for (
const auto [index, new_coeff] : best_adjusted_coeffs_) {
919 ++total_num_coeff_adjust_;
920 CutTerm& entry = cut_.terms[index];
921 const IntegerValue remainder = new_coeff - entry.
coeff;
922 CHECK_GT(remainder, 0);
923 entry.
coeff = new_coeff;
924 cut_.rhs += absl::int128(remainder.value()) *
926 cut_.max_magnitude = std::max(cut_.max_magnitude,
IntTypeAbs(new_coeff));
931 IntegerValue factor_t =
932 GetFactorT(rhs_remainder, best_divisor, cut_.max_magnitude);
943 for (
const CutTerm& entry : cut_.terms) {
945 const IntegerValue coeff = entry.
coeff;
947 if (r > rhs_remainder) remainders_.push_back(r);
950 if (remainders_.size() <= 100) {
952 for (
const IntegerValue r : remainders_) {
953 best_rs_.push_back(f(r));
955 IntegerValue best_d = f(best_divisor);
960 for (
const IntegerValue t :
962 GetFactorT(rhs_remainder, best_divisor, cut_.max_magnitude)}) {
963 for (IntegerValue s(2); s <= options.
max_scaling; ++s) {
966 int num_strictly_better = 0;
968 const IntegerValue d = g(best_divisor);
969 for (
int i = 0;
i < best_rs_.size(); ++
i) {
970 const IntegerValue temp = g(remainders_[
i]);
971 if (temp * best_d < best_rs_[
i] * d)
break;
972 if (temp * best_d > best_rs_[
i] * d) num_strictly_better++;
975 if (rs_.size() == best_rs_.size() && num_strictly_better > 0) {
976 ++total_num_dominating_f_;
989 if (ib_processor !=
nullptr) {
990 const auto [num_lb, num_ub, num_merges] =
992 total_num_pos_lifts_ += num_lb;
993 total_num_neg_lifts_ += num_ub;
994 total_num_merges_ += num_merges;
995 num_ib_used_ = num_lb + num_ub;
1000 for (
int i = 0;
i < 3; ++
i) {
1001 const int64_t saved = total_num_final_complements_;
1002 for (
CutTerm& entry : cut_.terms) {
1019 if (entry.
coeff % best_divisor == 0)
continue;
1030 const double lp2 =
ToDouble(f(remainder - prod)) -
1033 if (lp2 + 1e-2 < lp1) {
1035 ++total_num_final_complements_;
1038 if (total_num_final_complements_ == saved)
break;
1041 total_num_bumps_ += ApplyWithPotentialBump(f, best_divisor, &cut_);
1046 if (!VLOG_IS_ON(1))
return;
1047 if (shared_stats_ ==
nullptr)
return;
1049 std::vector<std::pair<std::string, int64_t>> stats;
1050 const auto add_stats = [&stats](absl::string_view name,
const CutStats& s) {
1052 {absl::StrCat(name,
"num_overflows"), s.num_overflow_aborts});
1053 stats.push_back({absl::StrCat(name,
"num_lifting"), s.num_lifting});
1054 stats.push_back({absl::StrCat(name,
"num_initial_ib"), s.num_initial_ibs});
1055 stats.push_back({absl::StrCat(name,
"num_implied_lb"), s.num_lb_ibs});
1056 stats.push_back({absl::StrCat(name,
"num_implied_ub"), s.num_ub_ibs});
1057 stats.push_back({absl::StrCat(name,
"num_bumps"), s.num_bumps});
1058 stats.push_back({absl::StrCat(name,
"num_cuts"), s.num_cuts});
1059 stats.push_back({absl::StrCat(name,
"num_merges"), s.num_merges});
1061 add_stats(
"cover_cut/", cover_stats_);
1062 add_stats(
"flow_cut/", flow_stats_);
1063 add_stats(
"ls_cut/", ls_stats_);
1064 shared_stats_->AddStats(stats);
1069struct LargeCoeffFirst {
1071 if (a.
coeff ==
b.coeff) {
1074 return a.
coeff > b.coeff;
1078struct SmallContribFirst {
1079 bool operator()(
const CutTerm& a,
const CutTerm& b)
const {
1080 const double contrib_a = a.lp_value *
static_cast<double>(a.coeff.value());
1081 const double contrib_b =
b.lp_value *
static_cast<double>(
b.coeff.value());
1082 return contrib_a < contrib_b;
1086struct LargeContribFirst {
1087 bool operator()(
const CutTerm& a,
const CutTerm& b)
const {
1088 const double contrib_a = a.lp_value *
static_cast<double>(a.coeff.value());
1089 const double contrib_b =
b.lp_value *
static_cast<double>(
b.coeff.value());
1090 return contrib_a > contrib_b;
1094struct LargeLpValueFirst {
1095 bool operator()(
const CutTerm& a,
const CutTerm& b)
const {
1096 if (a.lp_value ==
b.lp_value) {
1099 return a.coeff >
b.coeff;
1101 return a.lp_value >
b.lp_value;
1110 bool operator()(
const CutTerm& a,
const CutTerm& b)
const {
1111 const double contrib_a =
1112 a.LpDistToMaxValue() /
static_cast<double>(a.coeff.value());
1113 const double contrib_b =
1114 b.LpDistToMaxValue() /
static_cast<double>(
b.coeff.value());
1115 return contrib_a < contrib_b;
1118struct KnapsackRemove {
1119 bool operator()(
const CutTerm& a,
const CutTerm& b)
const {
1120 const double contrib_a =
1121 a.LpDistToMaxValue() /
static_cast<double>(a.coeff.value());
1122 const double contrib_b =
1123 b.LpDistToMaxValue() /
static_cast<double>(
b.coeff.value());
1124 return contrib_a > contrib_b;
1134template <
class Compare>
1135int CoverCutHelper::MinimizeCover(
int cover_size, absl::int128 slack) {
1137 absl::Span<CutTerm> terms = absl::MakeSpan(cut_.terms);
1138 std::sort(terms.begin(), terms.begin() + cover_size, Compare());
1139 for (
int i = 0;
i < cover_size;) {
1140 const CutTerm& t = terms[
i];
1141 const absl::int128 contrib =
1142 absl::int128(t.bound_diff.value()) * absl::int128(t.coeff.value());
1143 if (contrib < slack) {
1145 std::swap(terms[i], terms[--cover_size]);
1150 DCHECK_GT(cover_size, 0);
1154template <
class CompareAdd,
class CompareRemove>
1155int CoverCutHelper::GetCoverSize(
int relevant_size) {
1156 if (relevant_size == 0)
return 0;
1157 absl::Span<CutTerm> terms = absl::MakeSpan(cut_.terms);
1162 for (
int i = 0;
i < relevant_size;) {
1163 CutTerm& term = terms[
i];
1164 const double dist = term.LpDistToMaxValue();
1167 std::swap(term, terms[part1]);
1170 }
else if (term.lp_value > 1e-6) {
1176 std::swap(term, terms[relevant_size]);
1179 std::sort(terms.begin() + part1, terms.begin() + relevant_size, CompareAdd());
1182 DCHECK_GE(cut_.rhs, 0);
1183 absl::int128 max_shifted_activity = -cut_.rhs;
1184 absl::int128 shifted_round_up = -cut_.rhs;
1186 for (; cover_size < relevant_size; ++cover_size) {
1187 if (max_shifted_activity > 0)
break;
1188 const CutTerm& term = terms[cover_size];
1189 max_shifted_activity += absl::int128(term.coeff.value()) *
1190 absl::int128(term.bound_diff.value());
1191 shifted_round_up += absl::int128(term.coeff.value()) *
1192 std::min(absl::int128(term.bound_diff.value()),
1193 absl::int128(std::ceil(term.lp_value - 1e-6)));
1196 DCHECK_GE(cover_size, 0);
1197 if (shifted_round_up <= 0) {
1200 return MinimizeCover<CompareRemove>(cover_size, max_shifted_activity);
1205int CoverCutHelper::GetCoverSizeForBooleans() {
1206 absl::Span<CutTerm> terms = absl::MakeSpan(cut_.terms);
1213 int relevant_size = terms.size();
1214 int best_in_part3 = -1;
1215 const double threshold = 1.0 - 1.0 /
static_cast<double>(terms.size());
1216 for (
int i = 0;
i < relevant_size;) {
1217 const double lp_value = terms[
i].lp_value;
1220 if (terms[i].bound_diff > 1) {
1222 std::swap(terms[i], terms[relevant_size]);
1226 if (lp_value >= threshold) {
1228 std::swap(terms[i], terms[part1]);
1231 }
else if (lp_value > 0.5) {
1237 std::swap(terms[i], terms[relevant_size]);
1239 if (best_in_part3 == -1 ||
1240 LargeLpValueFirst()(terms[relevant_size], terms[best_in_part3])) {
1241 best_in_part3 = relevant_size;
1246 if (best_in_part3 != -1) {
1247 std::swap(terms[relevant_size], terms[best_in_part3]);
1252 std::sort(terms.begin() + part1, terms.begin() + relevant_size,
1253 LargeLpValueFirst());
1255 double activity = 0.0;
1256 int cover_size = relevant_size;
1257 absl::int128 slack = -cut_.rhs;
1258 for (
int i = 0;
i < relevant_size; ++
i) {
1259 const CutTerm& term = terms[
i];
1260 activity += term.LpDistToMaxValue();
1269 if (activity > 0.9999) {
1276 slack += absl::int128(term.coeff.value()) *
1277 absl::int128(term.bound_diff.value());
1290 if (cover_size == 0)
return 0;
1291 return MinimizeCover<LargeCoeffFirst>(cover_size, slack);
1294void CoverCutHelper::InitializeCut(
const CutData& input_ct) {
1300 CHECK_GE(cut_.rhs, 0);
1301 DCHECK(cut_.AllCoefficientsArePositive());
1306 InitializeCut(input_ct);
1310 if (ib_processor !=
nullptr) {
1311 std::vector<CutTerm>* new_bool_terms =
1313 for (
CutTerm& term : cut_.terms) {
1321 false, &term, &cut_.rhs, new_bool_terms)) {
1322 ++cover_stats_.num_initial_ibs;
1327 absl::MakeSpan(*new_bool_terms), IntegerValue(1), &cut_);
1330 bool has_relevant_int =
false;
1331 for (
const CutTerm& term : cut_.terms) {
1333 has_relevant_int =
true;
1338 const int base_size =
static_cast<int>(cut_.terms.size());
1339 const int cover_size =
1341 ? GetCoverSize<LargeContribFirst, LargeCoeffFirst>(base_size)
1342 : GetCoverSizeForBooleans();
1343 if (!has_relevant_int && ib_processor ==
nullptr) {
1347 has_bool_base_ct_ =
true;
1348 bool_cover_size_ = cover_size;
1349 if (cover_size == 0)
return false;
1350 bool_base_ct_ = cut_;
1352 if (cover_size == 0)
return false;
1369 IntegerValue best_coeff = 0;
1370 double best_score = -1.0;
1371 IntegerValue max_coeff_in_cover(0);
1372 for (
int i = 0;
i < cover_size; ++
i) {
1374 max_coeff_in_cover = std::max(max_coeff_in_cover, term.
coeff);
1375 const double score =
1378 if (score > best_score) {
1380 best_coeff = term.
coeff;
1384 CHECK_LT(cut_.rhs, 0);
1387 best_coeff = max_coeff_in_cover;
1390 std::function<IntegerValue(IntegerValue)> f;
1392 IntegerValue max_magnitude = 0;
1393 for (
const CutTerm& term : cut_.terms) {
1396 const IntegerValue max_scaling(std::min(
1403 if (ib_processor !=
nullptr) {
1404 const auto [num_lb, num_ub, num_merges] =
1406 cover_stats_.num_lb_ibs += num_lb;
1407 cover_stats_.num_ub_ibs += num_ub;
1408 cover_stats_.num_merges += num_merges;
1411 cover_stats_.num_bumps += ApplyWithPotentialBump(f, best_coeff, &cut_);
1414 for (
int i = cover_size;
i < cut_.terms.size(); ++
i) {
1415 if (cut_.terms[
i].coeff != 0) ++num_lifting_;
1417 cover_stats_.num_lifting += num_lifting_;
1418 ++cover_stats_.num_cuts;
1424 InitializeCut(input_ct);
1429 const int base_size =
static_cast<int>(cut_.terms.size());
1430 const int cover_size = GetCoverSize<KnapsackAdd, KnapsackRemove>(base_size);
1431 if (cover_size == 0)
return false;
1435 for (
int i = 0;
i < cover_size; ++
i) {
1436 cut_.terms[
i].Complement(&cut_.rhs);
1439 if (cut_.terms[
i].expr_coeffs[1] != 0)
return false;
1454 CHECK_LT(cut_.rhs, 0);
1455 if (cut_.rhs <= absl::int128(std::numeric_limits<int64_t>::min())) {
1459 bool has_large_coeff =
false;
1460 for (
const CutTerm& term : cut_.terms) {
1462 has_large_coeff =
true;
1470 const IntegerValue positive_rhs = -
static_cast<int64_t
>(cut_.rhs);
1472 for (
int i = 0;
i < cover_size; ++
i) {
1473 const IntegerValue magnitude =
IntTypeAbs(cut_.terms[
i].coeff);
1474 min_magnitude = std::min(min_magnitude, magnitude);
1476 const bool use_scaling =
1477 has_large_coeff || min_magnitude == 1 || min_magnitude >= positive_rhs;
1483 if (ib_processor !=
nullptr) {
1484 const auto [num_lb, num_ub, num_merges] =
1486 flow_stats_.num_lb_ibs += num_lb;
1487 flow_stats_.num_ub_ibs += num_ub;
1488 flow_stats_.num_merges += num_merges;
1493 IntegerValue period = positive_rhs;
1494 for (
const CutTerm& term : cut_.terms) {
1495 if (term.
coeff > 0)
continue;
1496 period = std::max(period, -term.
coeff);
1504 if (f(-period +
FloorRatio(period, 2)) != f(-period)) {
1508 CHECK_EQ(f(-period), f(-positive_rhs));
1509 period = std::max(period,
CapProdI(2, positive_rhs) - 1);
1516 cut_.rhs = absl::int128(f(-positive_rhs).value());
1517 for (
CutTerm& term : cut_.terms) {
1518 const IntegerValue old_coeff = term.
coeff;
1520 if (old_coeff > 0 && term.
coeff != 0) ++flow_stats_.num_lifting;
1522 ++flow_stats_.num_cuts;
1529 if (has_bool_base_ct_) {
1532 CHECK(ib_processor ==
nullptr);
1533 cover_size = bool_cover_size_;
1534 if (cover_size == 0)
return false;
1535 InitializeCut(bool_base_ct_);
1537 InitializeCut(input_ct);
1543 if (ib_processor !=
nullptr) {
1544 std::vector<CutTerm>* new_bool_terms =
1546 for (
CutTerm& term : cut_.terms) {
1550 false, &term, &cut_.rhs, new_bool_terms)) {
1551 ++ls_stats_.num_initial_ibs;
1556 absl::MakeSpan(*new_bool_terms), IntegerValue(1), &cut_);
1560 cover_size = GetCoverSizeForBooleans();
1562 if (cover_size == 0)
return false;
1566 if (cut_.rhs > absl::int128(std::numeric_limits<int64_t>::max())) {
1567 ++ls_stats_.num_overflow_aborts;
1570 const IntegerValue rhs =
static_cast<int64_t
>(cut_.rhs);
1573 IntegerValue sum(0);
1574 std::vector<IntegerValue> cover_weights;
1575 for (
int i = 0;
i < cover_size; ++
i) {
1576 CHECK_EQ(cut_.terms[
i].bound_diff, 1);
1577 CHECK_GT(cut_.terms[
i].coeff, 0);
1578 cover_weights.push_back(cut_.terms[
i].coeff);
1579 sum =
CapAddI(sum, cut_.terms[
i].coeff);
1582 ++ls_stats_.num_overflow_aborts;
1591 IntegerValue previous_sum(0);
1592 std::sort(cover_weights.begin(), cover_weights.end());
1593 for (
int i = 0;
i < cover_size; ++
i) {
1594 q = IntegerValue(cover_weights.size() -
i);
1595 if (previous_sum + cover_weights[
i] * q > rhs) {
1596 p = rhs - previous_sum;
1599 previous_sum += cover_weights[
i];
1606 std::vector<IntegerValue> thresholds;
1607 for (
int i = 0;
i < q; ++
i) {
1609 if (
CapProd(p.value(),
i + 1) >= std::numeric_limits<int64_t>::max() - 1) {
1610 ++ls_stats_.num_overflow_aborts;
1613 thresholds.push_back(
CeilRatio(p * (
i + 1) + 1, q));
1617 std::reverse(cover_weights.begin(), cover_weights.end());
1618 for (
int i = q.value();
i < cover_size; ++
i) {
1619 thresholds.push_back(thresholds.back() + cover_weights[
i]);
1621 CHECK_EQ(thresholds.back(), rhs + 1);
1631 temp_cut_.rhs = cover_size - 1;
1632 temp_cut_.terms.clear();
1634 const int base_size =
static_cast<int>(cut_.terms.size());
1635 for (
int i = 0;
i < base_size; ++
i) {
1636 const CutTerm& term = cut_.terms[
i];
1637 const IntegerValue coeff = term.
coeff;
1638 IntegerValue cut_coeff(1);
1639 if (coeff < thresholds[0]) {
1640 if (
i >= cover_size)
continue;
1646 for (
int i = 1;
i < cover_size; ++
i) {
1647 if (coeff < thresholds[
i])
break;
1648 cut_coeff = IntegerValue(
i + 1);
1650 if (cut_coeff != 0 &&
i >= cover_size) ++ls_stats_.num_lifting;
1651 if (cut_coeff > 1 &&
i < cover_size) ++ls_stats_.num_lifting;
1654 temp_cut_.terms.push_back(term);
1655 temp_cut_.terms.back().coeff = cut_coeff;
1659 ++ls_stats_.num_cuts;
1664 if (!VLOG_IS_ON(1))
return;
1665 std::vector<std::pair<std::string, int64_t>> stats;
1666 stats.push_back({
"bool_rlt/num_tried", num_tried_});
1667 stats.push_back({
"bool_rlt/num_tried_factors", num_tried_factors_});
1668 shared_stats_->AddStats(stats);
1672 product_detector_->InitializeBooleanRLTCuts(lp_vars, *lp_values_);
1673 enabled_ = !product_detector_->BoolRLTCandidates().empty();
1678 if (!enabled_)
return false;
1685 absl::flat_hash_set<IntegerVariable> to_try_set;
1686 std::vector<IntegerVariable> to_try;
1703 filtered_input_.terms.clear();
1704 filtered_input_.rhs = input_ct.
rhs;
1706 const auto& candidates = product_detector_->BoolRLTCandidates();
1720 filtered_input_.rhs -= absl::int128(term.
coeff.value()) *
1731 for (
const IntegerVariable factor : candidates.at(
NegationOf(var))) {
1732 if (to_try_set.insert(factor).second) to_try.push_back(factor);
1735 filtered_input_.terms.push_back(term);
1739 double best_efficacy = 1e-3;
1741 for (
const IntegerVariable factor : to_try) {
1742 ++num_tried_factors_;
1743 if (!TryProduct(factor, filtered_input_))
continue;
1744 const double efficacy = cut_.ComputeEfficacy();
1745 if (efficacy > best_efficacy) {
1746 best_efficacy = efficacy;
1747 best_factor = factor;
1753 return TryProduct(best_factor, input_ct);
1762enum class LinearizationOption {
1771double BoolRLTCutHelper::GetLiteralLpValue(IntegerVariable var)
const {
1772 if (VariableIsPositive(var))
return (*lp_values_)[var];
1773 return 1.0 - (*lp_values_)[PositiveVariable(var)];
1776bool BoolRLTCutHelper::TryProduct(IntegerVariable factor,
1777 const CutData&
input) {
1780 absl::int128 old_rhs =
input.rhs;
1782 const double factor_lp = GetLiteralLpValue(factor);
1785 for (CutTerm term :
input.terms) {
1786 LinearizationOption best_option = LinearizationOption::DROP;
1789 const IntegerVariable var = term.GetUnderlyingLiteralOrNone();
1793 if (factor == var) {
1796 best_option = LinearizationOption::SQUARE;
1797 cut_.terms.push_back(term);
1804 best_option = LinearizationOption::DROP;
1814 double best_lp = 0.0;
1818 const double complement_lp =
1819 static_cast<double>(term.bound_diff.value()) - term.lp_value;
1820 const double mc_cormick_lp = factor_lp - complement_lp;
1821 if (mc_cormick_lp > best_lp) {
1822 best_option = LinearizationOption::MC_CORMICK;
1823 best_lp = mc_cormick_lp;
1831 const IntegerVariable ub_lit =
1832 product_detector_->LiteralProductUpperBound(factor,
NegationOf(var));
1833 if (ub_lit != kNoIntegerVariable) {
1834 const double lit_lp = GetLiteralLpValue(ub_lit);
1835 if (factor_lp - lit_lp > best_lp) {
1837 best_option = LinearizationOption::RLT;
1840 term.Complement(&old_rhs);
1843 term.lp_value = lit_lp;
1844 term.ReplaceExpressionByLiteral(ub_lit);
1845 cut_.terms.push_back(term);
1851 if (best_option == LinearizationOption::DROP)
continue;
1854 CHECK(best_option == LinearizationOption::MC_CORMICK);
1855 term.Complement(&old_rhs);
1856 cut_.terms.push_back(term);
1863 const absl::int128 limit(int64_t{1} << 50);
1864 if (old_rhs > limit || old_rhs < -limit)
return false;
1867 term.coeff = -IntegerValue(
static_cast<int64_t
>(old_rhs));
1868 term.lp_value = factor_lp;
1869 term.bound_diff = 1;
1870 term.ReplaceExpressionByLiteral(factor);
1871 cut_.terms.push_back(term);
1879 int linearization_level,
1889 result.
generate_cuts = [z, x, y, linearization_level, model, trail,
1891 if (trail->CurrentDecisionLevel() > 0 && linearization_level == 1) {
1900 if (x_lb == x_ub || y_lb == y_ub)
return true;
1905 const int64_t x_max_amp = std::max(std::abs(x_lb), std::abs(x_ub));
1906 const int64_t y_max_amp = std::max(std::abs(y_lb), std::abs(y_ub));
1907 constexpr int64_t kMaxSafeInteger = (int64_t{1} << 53) - 1;
1908 if (
CapProd(y_max_amp, x_max_amp) > kMaxSafeInteger)
return true;
1909 if (
CapProd(y_max_amp, std::abs(x.constant.value())) > kMaxSafeInteger) {
1912 if (
CapProd(x_max_amp, std::abs(y.
constant.value())) > kMaxSafeInteger) {
1916 const auto& lp_values = manager->LpValues();
1917 const double x_lp_value = x.LpValue(lp_values);
1918 const double y_lp_value = y.
LpValue(lp_values);
1919 const double z_lp_value = z.
LpValue(lp_values);
1927 auto try_add_above_cut = [&](int64_t x_coeff, int64_t y_coeff,
1929 if (-z_lp_value + x_lp_value * x_coeff + y_lp_value * y_coeff >=
1930 rhs + kMinCutViolation) {
1933 cut.
AddTerm(z, IntegerValue(-1));
1934 if (x_coeff != 0) cut.
AddTerm(x, IntegerValue(x_coeff));
1935 if (y_coeff != 0) cut.
AddTerm(y, IntegerValue(y_coeff));
1936 manager->AddCut(cut.
Build(),
"PositiveProduct");
1941 auto try_add_below_cut = [&](int64_t x_coeff, int64_t y_coeff,
1943 if (-z_lp_value + x_lp_value * x_coeff + y_lp_value * y_coeff <=
1944 rhs - kMinCutViolation) {
1947 cut.
AddTerm(z, IntegerValue(-1));
1948 if (x_coeff != 0) cut.
AddTerm(x, IntegerValue(x_coeff));
1949 if (y_coeff != 0) cut.
AddTerm(y, IntegerValue(y_coeff));
1950 manager->AddCut(cut.
Build(),
"PositiveProduct");
1961 try_add_above_cut(y_lb, x_lb, x_lb * y_lb);
1962 try_add_above_cut(y_ub, x_ub, x_ub * y_ub);
1963 try_add_below_cut(y_ub, x_lb, x_lb * y_ub);
1964 try_add_below_cut(y_lb, x_ub, x_ub * y_lb);
1974 IntegerValue x_ub,
Model* model) {
1975 const IntegerValue above_slope = x_ub + x_lb;
1978 above_hyperplan.
AddTerm(square, 1);
1979 above_hyperplan.
AddTerm(x, -above_slope);
1980 return above_hyperplan.
Build();
1985 IntegerValue x_value,
1987 const IntegerValue below_slope = 2 * x_value + 1;
1990 below_hyperplan.
AddTerm(square, 1);
1991 below_hyperplan.
AddTerm(x, -below_slope);
1992 return below_hyperplan.
Build();
1996 int linearization_level,
Model* model) {
2003 result.
generate_cuts = [y, x, linearization_level, trail, integer_trail,
2008 const IntegerValue x_ub = integer_trail->LevelZeroUpperBound(x);
2009 const IntegerValue x_lb = integer_trail->LevelZeroLowerBound(x);
2012 if (x_lb == x_ub)
return true;
2015 if (x_ub > (int64_t{1} << 31))
return true;
2020 const IntegerValue x_floor =
2021 static_cast<int64_t
>(std::floor(x.LpValue(manager->LpValues())));
2030ImpliedBoundsProcessor::BestImpliedBoundInfo
2032 auto it = cache_.find(var);
2033 if (it != cache_.end()) {
2043ImpliedBoundsProcessor::ComputeBestImpliedBound(
2044 IntegerVariable var,
2046 auto it = cache_.find(var);
2047 if (it != cache_.end())
return it->second;
2048 BestImpliedBoundInfo result;
2049 double result_slack_lp_value = std::numeric_limits<double>::infinity();
2050 const IntegerValue lb = integer_trail_->LevelZeroLowerBound(var);
2052 implied_bounds_->GetImpliedBounds(var)) {
2064 const IntegerValue diff = entry.lower_bound - lb;
2066 const double bool_lp_value =
2067 VariableIsPositive(entry.literal_view)
2068 ? lp_values[entry.literal_view]
2069 : 1.0 - lp_values[PositiveVariable(entry.literal_view)];
2070 const double slack_lp_value =
2071 lp_values[var] - ToDouble(lb) - bool_lp_value * ToDouble(diff);
2075 if (slack_lp_value < -1e-4) {
2076 LinearConstraint ib_cut;
2077 ib_cut.lb = kMinIntegerValue;
2078 std::vector<std::pair<IntegerVariable, IntegerValue>> terms;
2079 if (VariableIsPositive(entry.literal_view)) {
2081 terms.push_back({entry.literal_view, diff});
2082 terms.push_back({var, IntegerValue(-1)});
2087 terms.push_back({var, IntegerValue(-1)});
2088 ib_cut.ub = -entry.lower_bound;
2091 ib_cut_pool_.AddCut(std::move(ib_cut),
"IB", lp_values);
2097 if (slack_lp_value + 1e-4 < result_slack_lp_value ||
2098 (slack_lp_value < result_slack_lp_value + 1e-4 &&
2099 entry.lower_bound > result.implied_bound)) {
2100 result_slack_lp_value = slack_lp_value;
2101 result.var_lp_value = lp_values[var];
2102 result.bool_lp_value = bool_lp_value;
2103 result.implied_bound = entry.lower_bound;
2104 result.bool_var = entry.literal_view;
2107 cache_[var] = result;
2114 for (
const IntegerVariable var :
2115 implied_bounds_->VariablesWithImpliedBounds()) {
2117 ComputeBestImpliedBound(var, lp_values);
2126 if (!term.
IsSimple())
return false;
2151 if (bound_diff <= 0)
return false;
2194 absl::int128 unused = 0;
2208 CHECK_EQ(unused, absl::int128(0));
2213 const std::function<IntegerValue(IntegerValue)>& f, IntegerValue factor_t,
2215 int num_applied_lb = 0;
2216 int num_applied_ub = 0;
2231 bool expand =
false;
2239 base_score = AsDouble(f(bool_term.
coeff)) * bool_term.
lp_value +
2267 const double score =
2272 if (score > base_score + 1e-2) {
2274 term = ub_slack_term;
2275 tmp_terms_.push_back(ub_bool_term);
2283 tmp_terms_.push_back(bool_term);
2287 const int num_merges = cut_builder_.AddOrMergeBooleanTerms(
2288 absl::MakeSpan(tmp_terms_), factor_t, cut);
2290 return {num_applied_lb, num_applied_ub, num_merges};
2294 IntegerValue factor_t,
bool complement,
CutTerm* term, absl::int128* rhs,
2295 std::vector<CutTerm>* new_bool_terms) {
2317 new_bool_terms->push_back(bool_term);
2323 cached_data_.clear();
2325 const int size = cut->
terms.size();
2326 for (
int i = 0;
i < size; ++
i) {
2330 if (term.
expr_vars[0] >= first_slack)
continue;
2333 const IntegerVariable ib_var = term.
expr_coeffs[0] > 0
2338 cut->
terms[
i].cached_implied_lb = cached_data_.size();
2339 cached_data_.emplace_back(std::move(lb_info));
2344 cut->
terms[
i].cached_implied_ub = cached_data_.size();
2345 cached_data_.emplace_back(std::move(ub_info));
2349 return !cached_data_.empty();
2353 min_values_.clear();
2361 if (integer_trail.
IsFixed(expr)) {
2362 min_values_.insert(integer_trail.
FixedValue(expr));
2364 if (expr.
coeff > 0) {
2366 for (
const IntegerValue value :
2368 min_values_.insert(expr.
ValueAt(value));
2369 if (++count >= num_exprs)
break;
2373 for (
const IntegerValue value :
2375 min_values_.insert(-expr.
ValueAt(value));
2376 if (++count >= num_exprs)
break;
2384 IntegerValue sum = 0;
2385 for (
const IntegerValue value : min_values_) {
2387 if (++count >= expr_mins_.size())
return sum;
2393 std::sort(expr_mins_.begin(), expr_mins_.end());
2395 IntegerValue result = 0;
2396 for (
const IntegerValue value : expr_mins_) {
2398 tmp_value = std::max(tmp_value + 1, value);
2399 result += tmp_value;
2407 if (domain_bound > alldiff_bound) {
2409 return domain_bound;
2411 suffix = alldiff_bound > domain_bound ?
"a" :
"e";
2412 return alldiff_bound;
2417void TryToGenerateAllDiffCut(
2418 absl::Span<
const std::pair<double, AffineExpression>> sorted_exprs_lp,
2422 const int num_exprs = sorted_exprs_lp.size();
2424 std::vector<AffineExpression> current_set_exprs;
2430 for (
const auto& [expr_lp, expr] : sorted_exprs_lp) {
2432 diff_mins.
Add(expr, num_exprs, integer_trail);
2433 negated_diff_maxes.
Add(expr.Negated(), num_exprs, integer_trail);
2434 current_set_exprs.push_back(expr);
2435 CHECK_EQ(current_set_exprs.size(), diff_mins.
size());
2436 CHECK_EQ(current_set_exprs.size(), negated_diff_maxes.
size());
2437 std::string min_suffix;
2438 const IntegerValue required_min_sum =
2440 std::string max_suffix;
2441 const IntegerValue required_max_sum =
2443 if (required_max_sum == std::numeric_limits<IntegerValue>::max())
continue;
2444 DCHECK_LE(required_min_sum, required_max_sum);
2445 if (sum <
ToDouble(required_min_sum) - kMinCutViolation ||
2446 sum >
ToDouble(required_max_sum) + kMinCutViolation) {
2449 cut.AddTerm(expr, IntegerValue(1));
2451 top_n_cuts.
AddCut(cut.Build(),
2452 absl::StrCat(
"AllDiff_", min_suffix, max_suffix),
2458 current_set_exprs.clear();
2460 negated_diff_maxes.
Clear();
2468 absl::Span<const AffineExpression> exprs,
Model* model) {
2473 if (!integer_trail->
IsFixed(expr)) {
2474 result.
vars.push_back(expr.var);
2481 [exprs = std::vector<AffineExpression>(exprs.begin(), exprs.end()),
2487 const auto& lp_values = manager->LpValues();
2488 std::vector<std::pair<double, AffineExpression>> sorted_exprs;
2494 sorted_exprs.push_back(std::make_pair(expr.LpValue(lp_values), expr));
2498 std::sort(sorted_exprs.begin(), sorted_exprs.end(),
2499 [](std::pair<double, AffineExpression>& a,
2500 const std::pair<double, AffineExpression>&
b) {
2501 return a.first < b.first;
2503 TryToGenerateAllDiffCut(sorted_exprs, *integer_trail, lp_values,
2506 std::reverse(sorted_exprs.begin(), sorted_exprs.end());
2507 TryToGenerateAllDiffCut(sorted_exprs, *integer_trail, lp_values,
2512 VLOG(2) <<
"Created all_diff cut generator of size: " << exprs.size();
2518IntegerValue MaxCornerDifference(
const IntegerVariable var,
2519 const IntegerValue w1_i,
2520 const IntegerValue w2_i,
2521 const IntegerTrail& integer_trail) {
2522 const IntegerValue lb = integer_trail.LevelZeroLowerBound(var);
2523 const IntegerValue ub = integer_trail.LevelZeroUpperBound(var);
2524 return std::max((w2_i - w1_i) * lb, (w2_i - w1_i) * ub);
2533IntegerValue MPlusCoefficient(
2534 absl::Span<const IntegerVariable> x_vars,
2535 absl::Span<const LinearExpression> exprs,
2537 const int max_index,
const IntegerTrail& integer_trail) {
2538 IntegerValue coeff = exprs[max_index].offset;
2541 for (
const IntegerVariable var : x_vars) {
2542 const int target_index = variable_partition[var];
2543 if (max_index != target_index) {
2544 coeff += MaxCornerDifference(
2545 var, GetCoefficientOfPositiveVar(var, exprs[target_index]),
2546 GetCoefficientOfPositiveVar(var, exprs[max_index]), integer_trail);
2555double ComputeContribution(
2556 const IntegerVariable xi_var, absl::Span<const IntegerVariable> z_vars,
2557 absl::Span<const LinearExpression> exprs,
2558 const util_intops::StrongVector<IntegerVariable, double>& lp_values,
2559 const IntegerTrail& integer_trail,
const int target_index) {
2560 CHECK_GE(target_index, 0);
2561 CHECK_LT(target_index, exprs.size());
2562 const LinearExpression& target_expr = exprs[target_index];
2563 const double xi_value = lp_values[xi_var];
2565 double contrib =
ToDouble(wt_i) * xi_value;
2566 for (
int expr_index = 0; expr_index < exprs.size(); ++expr_index) {
2567 if (expr_index == target_index)
continue;
2568 const LinearExpression& max_expr = exprs[expr_index];
2569 const double z_max_value = lp_values[z_vars[expr_index]];
2570 const IntegerValue corner_value = MaxCornerDifference(
2573 contrib +=
ToDouble(corner_value) * z_max_value;
2580 absl::Span<const LinearExpression> exprs,
2581 absl::Span<const IntegerVariable> z_vars,
2584 std::vector<IntegerVariable> x_vars;
2585 result.
vars = {target};
2586 const int num_exprs = exprs.size();
2587 for (
int i = 0;
i < num_exprs; ++
i) {
2588 result.
vars.push_back(z_vars[
i]);
2589 x_vars.insert(x_vars.end(), exprs[
i].vars.begin(), exprs[
i].vars.end());
2593 DCHECK(std::all_of(x_vars.begin(), x_vars.end(), [](IntegerVariable var) {
2594 return VariableIsPositive(var);
2596 result.
vars.insert(result.
vars.end(), x_vars.begin(), x_vars.end());
2601 z_vars = std::vector<IntegerVariable>(z_vars.begin(), z_vars.end()),
2603 exprs = std::vector<LinearExpression>(exprs.begin(), exprs.end()),
2605 const auto& lp_values = manager->LpValues();
2607 lp_values.size(), -1);
2609 variable_partition_contrib(lp_values.size(),
2610 std::numeric_limits<double>::infinity());
2611 for (
int expr_index = 0; expr_index < num_exprs; ++expr_index) {
2612 for (
const IntegerVariable var : x_vars) {
2613 const double contribution = ComputeContribution(
2614 var, z_vars, exprs, lp_values, *integer_trail, expr_index);
2615 const double prev_contribution = variable_partition_contrib[var];
2616 if (contribution < prev_contribution) {
2617 variable_partition[var] = expr_index;
2618 variable_partition_contrib[var] = contribution;
2625 double violation = lp_values[target];
2626 cut.
AddTerm(target, IntegerValue(-1));
2628 for (
const IntegerVariable xi_var : x_vars) {
2629 const int input_index = variable_partition[xi_var];
2632 if (coeff != IntegerValue(0)) {
2635 violation -=
ToDouble(coeff) * lp_values[xi_var];
2637 for (
int expr_index = 0; expr_index < num_exprs; ++expr_index) {
2638 const IntegerVariable z_var = z_vars[expr_index];
2639 const IntegerValue z_coeff = MPlusCoefficient(
2640 x_vars, exprs, variable_partition, expr_index, *integer_trail);
2641 if (z_coeff != IntegerValue(0)) {
2644 violation -=
ToDouble(z_coeff) * lp_values[z_var];
2646 if (violation > 1e-2) {
2647 manager->AddCut(cut.
Build(),
"LinMax");
2656IntegerValue EvaluateMaxAffine(
2657 absl::Span<
const std::pair<IntegerValue, IntegerValue>> affines,
2659 IntegerValue y = kMinIntegerValue;
2660 for (
const auto& p : affines) {
2661 y = std::max(y, x * p.first + p.second);
2670 absl::Span<
const std::pair<IntegerValue, IntegerValue>> affines,
2674 const IntegerValue x_max = integer_trail->LevelZeroUpperBound(var);
2676 const IntegerValue y_at_min = EvaluateMaxAffine(affines, x_min);
2677 const IntegerValue y_at_max = EvaluateMaxAffine(affines, x_max);
2679 const IntegerValue delta_x = x_max - x_min;
2680 const IntegerValue delta_y = y_at_max - y_at_min;
2693 int64_t abs_magnitude = std::abs(target.
offset.value());
2694 for (
int i = 0;
i < target.
vars.size(); ++
i) {
2695 const IntegerVariable var = target.
vars[
i];
2696 const IntegerValue var_min = integer_trail->LevelZeroLowerBound(var);
2697 const IntegerValue var_max = integer_trail->LevelZeroUpperBound(var);
2699 CapProd(std::max(std::abs(var_min.value()), std::abs(var_max.value())),
2700 std::abs(target.
coeffs[
i].value())),
2710 builder->
AddTerm(var, -delta_y);
2714 VLOG(2) <<
"Linear constraint can cause overflow: " << builder->
Build();
2724 std::vector<std::pair<IntegerValue, IntegerValue>> affines,
2725 const std::string cut_name,
Model* model) {
2728 result.
vars.push_back(var);
2732 result.
generate_cuts = [target, var, affines, cut_name, integer_trail,
2734 if (integer_trail->
IsFixed(var))
return true;
2737 manager->AddCut(builder.
Build(), cut_name);
2745 absl::Span<const IntegerVariable> base_variables,
Model* model) {
2748 std::vector<IntegerVariable> variables;
2749 std::vector<Literal> literals;
2750 absl::flat_hash_map<LiteralIndex, IntegerVariable> positive_map;
2751 absl::flat_hash_map<LiteralIndex, IntegerVariable> negative_map;
2754 for (
const IntegerVariable var : base_variables) {
2755 if (integer_trail->LowerBound(var) != IntegerValue(0))
continue;
2756 if (integer_trail->UpperBound(var) != IntegerValue(1))
continue;
2757 const LiteralIndex literal_index = encoder->GetAssociatedLiteral(
2760 variables.push_back(var);
2761 literals.push_back(
Literal(literal_index));
2762 positive_map[literal_index] = var;
2767 result.
vars = variables;
2770 result.
generate_cuts = [variables, literals, implication_graph, positive_map,
2773 std::vector<double> packed_values;
2774 std::vector<double> packed_reduced_costs;
2775 const auto& lp_values = manager->LpValues();
2776 const auto& reduced_costs = manager->ReducedCosts();
2777 for (
int i = 0;
i < literals.size(); ++
i) {
2778 packed_values.push_back(lp_values[variables[
i]]);
2779 packed_reduced_costs.push_back(reduced_costs[variables[
i]]);
2781 const std::vector<std::vector<Literal>> at_most_ones =
2782 implication_graph->GenerateAtMostOnesWithLargeWeight(
2783 literals, packed_values, packed_reduced_costs);
2785 for (
const std::vector<Literal>& at_most_one : at_most_ones) {
2790 model, IntegerValue(std::numeric_limits<int64_t>::min()),
2792 for (
const Literal l : at_most_one) {
2793 if (positive_map.contains(l.Index())) {
2794 builder.
AddTerm(positive_map.at(l.Index()), IntegerValue(1));
2797 builder.
AddTerm(negative_map.at(l.Index()), IntegerValue(-1));
2802 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