26#include "absl/base/attributes.h"
27#include "absl/container/btree_map.h"
28#include "absl/container/btree_set.h"
29#include "absl/container/flat_hash_map.h"
30#include "absl/container/flat_hash_set.h"
31#include "absl/log/check.h"
32#include "absl/meta/type_traits.h"
33#include "absl/numeric/int128.h"
34#include "absl/strings/str_cat.h"
35#include "absl/strings/string_view.h"
36#include "absl/types/span.h"
60double AsDouble(IntegerValue v) {
return static_cast<double>(v.value()); }
65 return absl::StrCat(
"coeff=",
coeff.value(),
" lp=",
lp_value,
73 std::string result = absl::StrCat(
"CutData rhs=",
rhs,
"\n");
75 absl::StrAppend(&result, term.DebugString(),
"\n");
86 for (
int i = 0;
i < 2; ++
i) {
134 double lp_value, IntegerValue lb, IntegerValue ub) {
135 if (coeff == 0)
return true;
136 const IntegerValue bound_diff = ub - lb;
139 const bool complement = coeff < 0;
142 rhs -= absl::int128(coeff.value()) *
143 absl::int128(complement ? ub.value() : lb.value());
147 if (bound_diff == 0)
return true;
157 entry.
coeff = -coeff;
158 entry.
lp_value =
static_cast<double>(ub.value()) - lp_value;
164 entry.
lp_value = lp_value -
static_cast<double>(lb.value());
166 terms.push_back(entry);
174 rhs = absl::int128(base_ct.
ub.value());
177 for (
int i = 0;
i < num_terms; ++
i) {
178 const IntegerVariable
var = base_ct.
vars[
i];
189 IntegerValue ub, absl::Span<const IntegerVariable> vars,
190 absl::Span<const IntegerValue> coeffs, absl::Span<const double> lp_values,
193 rhs = absl::int128(ub.value());
196 const int size = lp_values.size();
197 if (
size == 0)
return true;
199 CHECK_EQ(vars.size(),
size);
200 CHECK_EQ(coeffs.size(),
size);
204 for (
int i = 0;
i <
size; ++
i) {
216 if (term.coeff >= 0)
continue;
217 term.Complement(&
rhs);
223 if (term.lp_value <= term.LpDistToMaxValue())
continue;
224 term.Complement(&
rhs);
230 if (term.coeff < 0)
return false;
238 for (
int i = 0;
i <
terms.size(); ++
i) {
250 return a.lp_value > b.lp_value;
255 double violation = -
static_cast<double>(
rhs);
257 violation += term.lp_value *
static_cast<double>(term.coeff.value());
263 double violation = -
static_cast<double>(
rhs);
266 const double coeff =
static_cast<double>(term.coeff.value());
267 violation += term.lp_value * coeff;
268 norm += coeff * coeff;
270 return violation / std::sqrt(norm);
275 constraint_is_indexed_ =
false;
277 secondary_bool_index_.clear();
280void CutDataBuilder::RegisterAllBooleanTerms(
const CutData& cut) {
281 constraint_is_indexed_ =
true;
283 for (
int i = 0;
i <
size; ++
i) {
296 if (!constraint_is_indexed_) {
297 RegisterAllBooleanTerms(*cut);
302 const bool is_positive = (term.
expr_coeffs[0] > 0);
303 const int new_index = cut->
terms.size();
304 const auto [it, inserted] = bool_index_.insert({
var, new_index});
306 cut->
terms.push_back(term);
311 int entry_index = it->second;
312 if (entry_index >= new_index || cut->
terms[entry_index].expr_vars[0] !=
var) {
313 it->second = new_index;
314 cut->
terms.push_back(term);
319 if ((cut->
terms[entry_index].expr_coeffs[0] > 0) != is_positive) {
320 const auto [it, inserted] = secondary_bool_index_.insert({
var, new_index});
322 cut->
terms.push_back(term);
327 entry_index = it->second;
328 if (entry_index >= new_index ||
329 cut->
terms[entry_index].expr_vars[0] !=
var) {
330 it->second = new_index;
331 cut->
terms.push_back(term);
336 if ((cut->
terms[entry_index].expr_coeffs[0] > 0) != is_positive) {
337 it->second = new_index;
338 cut->
terms.push_back(term);
342 DCHECK_EQ(cut->
terms[entry_index].expr_vars[0],
var);
343 DCHECK_EQ((cut->
terms[entry_index].expr_coeffs[0] > 0), is_positive);
350 const IntegerValue new_coeff =
354 cut->
terms.push_back(term);
357 cut->
terms[entry_index].coeff = new_coeff;
363ABSL_DEPRECATED(
"Only used in tests, this will be removed.")
367 if (cut.rhs > absl::int128(std::numeric_limits<int64_t>::max()) ||
368 cut.rhs < absl::int128(std::numeric_limits<int64_t>::min())) {
371 IntegerValue new_rhs =
static_cast<int64_t
>(cut.rhs);
372 for (
const CutTerm& term : cut.terms) {
373 for (int i = 0; i < 2; ++i) {
374 if (term.expr_coeffs[i] == 0) continue;
375 if (!AddProductTo(term.coeff, term.expr_coeffs[i],
376 &tmp_map_[term.expr_vars[i]])) {
386 output->ub = new_rhs;
387 output->resize(tmp_map_.size());
389 for (
const auto [
var, coeff] : tmp_map_) {
390 if (coeff == 0)
continue;
391 output->vars[new_size] =
var;
392 output->coeffs[new_size] = coeff;
395 output->resize(new_size);
404const double kMinCutViolation = 1e-4;
406IntegerValue PositiveRemainder(absl::int128 dividend,
407 IntegerValue positive_divisor) {
408 DCHECK_GT(positive_divisor, 0);
409 const IntegerValue m =
410 static_cast<int64_t
>(dividend % absl::int128(positive_divisor.value()));
411 return m < 0 ? m + positive_divisor : m;
415absl::int128 ApplyToInt128(
const std::function<IntegerValue(IntegerValue)>& f,
416 IntegerValue divisor, absl::int128
value) {
418 const absl::int128 k =
419 (
value - absl::int128(rest.value())) / absl::int128(divisor.value());
420 const absl::int128 result =
421 k * absl::int128(f(divisor).
value()) + absl::int128(f(rest).
value());
435int ApplyWithPotentialBump(
const std::function<IntegerValue(IntegerValue)>& f,
436 const IntegerValue divisor, CutData* cut) {
438 double bump_score = -1.0;
439 IntegerValue bump_coeff;
441 const IntegerValue f_remainder = f(remainder);
442 cut->rhs = ApplyToInt128(f, divisor, cut->rhs);
443 for (
int i = 0;
i < cut->terms.size(); ++
i) {
444 CutTerm& entry = cut->terms[
i];
445 const IntegerValue f_coeff = f(entry.coeff);
446 if (entry.bound_diff == 1) {
450 const IntegerValue alternative =
452 ? f_remainder - f(remainder - entry.coeff)
453 : f_remainder - IntegerValue(static_cast<int64_t>(ApplyToInt128(
457 DCHECK_GE(alternative, f_coeff);
458 if (alternative > f_coeff) {
459 const double score =
ToDouble(alternative - f_coeff) * entry.lp_value;
460 if (score > bump_score) {
463 bump_coeff = alternative;
467 entry.coeff = f_coeff;
469 if (bump_index >= 0) {
470 cut->terms[bump_index].coeff = bump_coeff;
482IntegerValue
GetFactorT(IntegerValue rhs_remainder, IntegerValue divisor,
483 IntegerValue max_magnitude) {
487 IntegerValue max_t(std::numeric_limits<int64_t>::max());
488 if (max_magnitude != 0) {
489 max_t = max_t / max_magnitude;
491 return rhs_remainder == 0
493 : std::min(max_t,
CeilRatio(divisor / 2, rhs_remainder));
497 IntegerValue rhs_remainder, IntegerValue divisor, IntegerValue t,
498 IntegerValue max_scaling) {
499 DCHECK_GE(max_scaling, 1);
504 DCHECK_LT(rhs_remainder, divisor);
510 std::min(max_scaling, std::numeric_limits<int64_t>::max() / divisor);
512 const IntegerValue
size = divisor - rhs_remainder;
513 if (max_scaling == 1 ||
size == 1) {
517 return [t, divisor](IntegerValue coeff) {
520 }
else if (
size <= max_scaling) {
521 return [
size, rhs_remainder, t, divisor](IntegerValue coeff) {
522 const IntegerValue t_coeff = t * coeff;
525 const IntegerValue diff = remainder - rhs_remainder;
526 return size *
ratio + std::max(IntegerValue(0), diff);
528 }
else if (max_scaling.value() * rhs_remainder.value() < divisor) {
538 return [t, divisor, max_scaling](IntegerValue coeff) {
539 const IntegerValue t_coeff = t * coeff;
542 const IntegerValue bucket =
FloorRatio(remainder * max_scaling, divisor);
543 return max_scaling *
ratio + bucket;
568 return [
size, rhs_remainder, t, divisor, max_scaling](IntegerValue coeff) {
569 const IntegerValue t_coeff = t * coeff;
572 const IntegerValue diff = remainder - rhs_remainder;
573 const IntegerValue bucket =
576 return max_scaling *
ratio + bucket;
582 IntegerValue positive_rhs, IntegerValue min_magnitude) {
583 CHECK_GT(positive_rhs, 0);
584 CHECK_GT(min_magnitude, 0);
586 if (min_magnitude >=
CeilRatio(positive_rhs, 2)) {
587 return [positive_rhs](IntegerValue v) {
588 if (v >= 0)
return IntegerValue(0);
589 if (v > -positive_rhs)
return IntegerValue(-1);
590 return IntegerValue(-2);
597 min_magnitude = std::min(min_magnitude,
FloorRatio(positive_rhs, 2));
598 const IntegerValue second_threshold = positive_rhs - min_magnitude;
599 return [positive_rhs, min_magnitude, second_threshold](IntegerValue v) {
600 if (v >= 0)
return IntegerValue(0);
601 if (v <= -positive_rhs)
return -positive_rhs;
602 if (v <= -second_threshold)
return -second_threshold;
607 if (v >= -min_magnitude)
return -min_magnitude;
615std::function<IntegerValue(IntegerValue)>
617 IntegerValue scaling) {
618 if (scaling >= positive_rhs) {
620 return [positive_rhs](IntegerValue v) {
621 if (v >= 0)
return IntegerValue(0);
622 if (v <= -positive_rhs)
return -positive_rhs;
629 std::min(scaling, IntegerValue(std::numeric_limits<int64_t>::max()) /
632 return [](IntegerValue v) {
633 if (v >= 0)
return IntegerValue(0);
634 return IntegerValue(-1);
639 return [positive_rhs, scaling](IntegerValue v) {
640 if (v >= 0)
return IntegerValue(0);
641 if (v <= -positive_rhs)
return -scaling;
642 return FloorRatio(v * (scaling - 1), (positive_rhs - 1));
646IntegerRoundingCutHelper::~IntegerRoundingCutHelper() {
647 if (!VLOG_IS_ON(1))
return;
648 if (shared_stats_ ==
nullptr)
return;
649 std::vector<std::pair<std::string, int64_t>> stats;
650 stats.push_back({
"rounding_cut/num_initial_ibs_", total_num_initial_ibs_});
652 {
"rounding_cut/num_initial_merges_", total_num_initial_merges_});
653 stats.push_back({
"rounding_cut/num_pos_lifts", total_num_pos_lifts_});
654 stats.push_back({
"rounding_cut/num_neg_lifts", total_num_neg_lifts_});
656 {
"rounding_cut/num_post_complements", total_num_post_complements_});
657 stats.push_back({
"rounding_cut/num_overflows", total_num_overflow_abort_});
658 stats.push_back({
"rounding_cut/num_adjusts", total_num_coeff_adjust_});
659 stats.push_back({
"rounding_cut/num_merges", total_num_merges_});
660 stats.push_back({
"rounding_cut/num_bumps", total_num_bumps_});
662 {
"rounding_cut/num_final_complements", total_num_final_complements_});
663 stats.push_back({
"rounding_cut/num_dominating_f", total_num_dominating_f_});
664 shared_stats_->AddStats(stats);
667double IntegerRoundingCutHelper::GetScaledViolation(
668 IntegerValue divisor, IntegerValue max_scaling,
669 IntegerValue remainder_threshold,
const CutData& cut) {
670 absl::int128 rhs = cut.
rhs;
673 if (initial_rhs_remainder < remainder_threshold)
return 0.0;
691 adjusted_coeffs_.clear();
692 const IntegerValue adjust_threshold =
693 (divisor - initial_rhs_remainder - 1) /
695 if (adjust_threshold > 0) {
699 double max_violation =
static_cast<double>(initial_rhs_remainder.value());
703 if (remainder == 0)
continue;
704 if (remainder <= initial_rhs_remainder) {
708 static_cast<double>(remainder.value()) * entry.
lp_value;
709 if (max_violation <= 1e-3)
return 0.0;
714 const IntegerValue adjust = divisor - remainder;
715 const IntegerValue prod = CapProdI(adjust, entry.
bound_diff);
716 if (prod <= adjust_threshold) {
717 rhs += absl::int128(prod.value());
718 const IntegerValue new_coeff = entry.
coeff + adjust;
719 adjusted_coeffs_.push_back({i, new_coeff});
720 max_magnitude = std::max(max_magnitude, IntTypeAbs(new_coeff));
726 const IntegerValue t =
GetFactorT(rhs_remainder, divisor, max_magnitude);
738 double max_violation = scaling *
ToDouble(rhs_remainder);
744 double violation = -
static_cast<double>(ApplyToInt128(f, divisor, rhs));
746 int adjusted_coeffs_index = 0;
748 const CutTerm& entry = cut.
terms[
i];
751 IntegerValue coeff = entry.coeff;
752 if (adjusted_coeffs_index < adjusted_coeffs_.size() &&
753 adjusted_coeffs_[adjusted_coeffs_index].first == i) {
754 coeff = adjusted_coeffs_[adjusted_coeffs_index].second;
755 adjusted_coeffs_index++;
758 if (coeff == 0)
continue;
759 const IntegerValue new_coeff = f(coeff);
760 const double new_coeff_double =
ToDouble(new_coeff);
761 const double lp_value = entry.lp_value;
766 l2_norm += new_coeff_double * new_coeff_double;
767 violation += new_coeff_double * lp_value;
768 max_violation -= (scaling *
ToDouble(coeff) - new_coeff_double) * lp_value;
769 if (max_violation <= 1e-3)
return 0.0;
771 if (
l2_norm == 0.0)
return 0.0;
781 return violation / sqrt(
l2_norm);
785bool IntegerRoundingCutHelper::ComputeCut(
793 const int old_size =
static_cast<int>(cut_.terms.size());
795 for (
int i = 0;
i < old_size; ++
i) {
796 if (cut_.terms[
i].bound_diff <= 1)
continue;
797 if (!cut_.terms[
i].HasRelevantLpValue())
continue;
801 cut_.terms[
i].Complement(&cut_.rhs);
805 ++total_num_initial_ibs_;
809 cut_.terms[
i].Complement(&cut_.rhs);
816 ++total_num_initial_ibs_;
819 total_num_initial_merges_ +=
825 if (abort)
return false;
846 const IntegerValue remainder_threshold(
847 std::max(IntegerValue(1), cut_.max_magnitude / 1000));
848 if (cut_.rhs >= 0 && cut_.rhs < remainder_threshold.value()) {
858 for (
const CutTerm& entry : cut_.terms) {
861 if (magnitude <= remainder_threshold)
continue;
862 divisors_.push_back(magnitude);
866 if (divisors_.size() > 50)
break;
868 if (divisors_.empty())
return false;
878 IntegerValue best_divisor(0);
879 double best_scaled_violation = 1e-3;
880 for (
const IntegerValue divisor : divisors_) {
883 const double violation = GetScaledViolation(divisor, options.
max_scaling,
884 remainder_threshold, cut_);
885 if (violation > best_scaled_violation) {
886 best_scaled_violation = violation;
887 best_adjusted_coeffs_ = adjusted_coeffs_;
888 best_divisor = divisor;
891 if (best_divisor == 0)
return false;
894 for (
int div = 2; div < 9; ++div) {
895 const IntegerValue divisor = best_divisor / IntegerValue(div);
896 if (divisor <= 1)
continue;
897 const double violation = GetScaledViolation(divisor, options.
max_scaling,
898 remainder_threshold, cut_);
899 if (violation > best_scaled_violation) {
900 best_scaled_violation = violation;
901 best_adjusted_coeffs_ = adjusted_coeffs_;
902 best_divisor = divisor;
907 for (
CutTerm& entry : cut_.terms) {
909 if (entry.
coeff % best_divisor == 0)
continue;
914 const double violation = GetScaledViolation(
915 best_divisor, options.
max_scaling, remainder_threshold, cut_);
916 if (violation > best_scaled_violation) {
918 ++total_num_post_complements_;
919 best_scaled_violation = violation;
920 best_adjusted_coeffs_ = adjusted_coeffs_;
928 for (
const auto [
index, new_coeff] : best_adjusted_coeffs_) {
929 ++total_num_coeff_adjust_;
931 const IntegerValue remainder = new_coeff - entry.
coeff;
932 CHECK_GT(remainder, 0);
933 entry.
coeff = new_coeff;
934 cut_.rhs += absl::int128(remainder.value()) *
936 cut_.max_magnitude = std::max(cut_.max_magnitude,
IntTypeAbs(new_coeff));
941 IntegerValue factor_t =
942 GetFactorT(rhs_remainder, best_divisor, cut_.max_magnitude);
953 for (
const CutTerm& entry : cut_.terms) {
955 const IntegerValue coeff = entry.
coeff;
957 if (r > rhs_remainder) remainders_.push_back(r);
960 if (remainders_.size() <= 100) {
962 for (
const IntegerValue r : remainders_) {
963 best_rs_.push_back(f(r));
965 IntegerValue best_d = f(best_divisor);
970 for (
const IntegerValue t :
972 GetFactorT(rhs_remainder, best_divisor, cut_.max_magnitude)}) {
973 for (IntegerValue s(2); s <= options.
max_scaling; ++s) {
976 int num_strictly_better = 0;
978 const IntegerValue d = g(best_divisor);
979 for (
int i = 0;
i < best_rs_.size(); ++
i) {
980 const IntegerValue temp = g(remainders_[
i]);
981 if (temp * best_d < best_rs_[
i] * d)
break;
982 if (temp * best_d > best_rs_[
i] * d) num_strictly_better++;
985 if (rs_.size() == best_rs_.size() && num_strictly_better > 0) {
986 ++total_num_dominating_f_;
999 if (ib_processor !=
nullptr) {
1001 f, factor_t, &cut_, &cut_builder_);
1002 total_num_pos_lifts_ += num_lb;
1003 total_num_neg_lifts_ += num_ub;
1004 total_num_merges_ += cut_builder_.NumMergesSinceLastClear();
1005 num_ib_used_ = num_lb + num_ub;
1010 for (
int i = 0;
i < 3; ++
i) {
1011 const int64_t saved = total_num_final_complements_;
1012 for (
CutTerm& entry : cut_.terms) {
1029 if (entry.
coeff % best_divisor == 0)
continue;
1040 const double lp2 =
ToDouble(f(remainder - prod)) -
1043 if (lp2 + 1e-2 < lp1) {
1045 ++total_num_final_complements_;
1048 if (total_num_final_complements_ == saved)
break;
1051 total_num_bumps_ += ApplyWithPotentialBump(f, best_divisor, &cut_);
1055CoverCutHelper::~CoverCutHelper() {
1056 if (!VLOG_IS_ON(1))
return;
1057 if (shared_stats_ ==
nullptr)
return;
1059 std::vector<std::pair<std::string, int64_t>> stats;
1060 const auto add_stats = [&stats](absl::string_view
name,
const CutStats& s) {
1062 {absl::StrCat(
name,
"num_overflows"), s.num_overflow_aborts});
1063 stats.push_back({absl::StrCat(
name,
"num_lifting"), s.num_lifting});
1064 stats.push_back({absl::StrCat(
name,
"num_initial_ib"), s.num_initial_ibs});
1065 stats.push_back({absl::StrCat(
name,
"num_implied_lb"), s.num_lb_ibs});
1066 stats.push_back({absl::StrCat(
name,
"num_implied_ub"), s.num_ub_ibs});
1067 stats.push_back({absl::StrCat(
name,
"num_bumps"), s.num_bumps});
1068 stats.push_back({absl::StrCat(
name,
"num_cuts"), s.num_cuts});
1069 stats.push_back({absl::StrCat(
name,
"num_merges"), s.num_merges});
1071 add_stats(
"cover_cut/", cover_stats_);
1072 add_stats(
"flow_cut/", flow_stats_);
1073 add_stats(
"ls_cut/", ls_stats_);
1074 shared_stats_->AddStats(stats);
1079struct LargeCoeffFirst {
1081 if (
a.coeff ==
b.coeff) {
1082 return a.LpDistToMaxValue() >
b.LpDistToMaxValue();
1084 return a.coeff >
b.coeff;
1088struct SmallContribFirst {
1089 bool operator()(
const CutTerm&
a,
const CutTerm&
b)
const {
1090 const double contrib_a =
a.lp_value *
static_cast<double>(
a.coeff.value());
1091 const double contrib_b =
b.lp_value *
static_cast<double>(
b.coeff.value());
1092 return contrib_a < contrib_b;
1096struct LargeContribFirst {
1097 bool operator()(
const CutTerm&
a,
const CutTerm&
b)
const {
1098 const double contrib_a =
a.lp_value *
static_cast<double>(
a.coeff.value());
1099 const double contrib_b =
b.lp_value *
static_cast<double>(
b.coeff.value());
1100 return contrib_a > contrib_b;
1109 bool operator()(
const CutTerm&
a,
const CutTerm&
b)
const {
1110 const double contrib_a =
1111 a.LpDistToMaxValue() /
static_cast<double>(
a.coeff.value());
1112 const double contrib_b =
1113 b.LpDistToMaxValue() /
static_cast<double>(
b.coeff.value());
1114 return contrib_a < contrib_b;
1117struct KnapsackRemove {
1118 bool operator()(
const CutTerm&
a,
const CutTerm&
b)
const {
1119 const double contrib_a =
1120 a.LpDistToMaxValue() /
static_cast<double>(
a.coeff.value());
1121 const double contrib_b =
1122 b.LpDistToMaxValue() /
static_cast<double>(
b.coeff.value());
1123 return contrib_a > contrib_b;
1133template <
class Compare>
1134int CoverCutHelper::MinimizeCover(
int cover_size, absl::int128 slack) {
1136 std::sort(cut_.terms.begin(), cut_.terms.begin() + cover_size, Compare());
1137 for (
int i = 0;
i < cover_size;) {
1138 const CutTerm& t = cut_.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(cut_.terms[i], cut_.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;
1159 for (
int i = 0;
i < relevant_size;) {
1160 CutTerm& term = cut_.terms[
i];
1164 std::swap(term, cut_.terms[part1]);
1167 }
else if (term.lp_value > 1e-6) {
1173 std::swap(term, cut_.terms[relevant_size]);
1176 std::sort(cut_.terms.begin() + part1, cut_.terms.begin() + relevant_size,
1180 CHECK_GE(cut_.rhs, 0);
1181 absl::int128 max_shifted_activity = -cut_.rhs;
1182 absl::int128 shifted_round_up = -cut_.rhs;
1185 for (; cover_size < relevant_size; ++cover_size) {
1186 if (max_shifted_activity > 0)
break;
1187 const CutTerm& term = cut_.terms[cover_size];
1188 max_shifted_activity += absl::int128(term.coeff.value()) *
1189 absl::int128(term.bound_diff.value());
1190 shifted_round_up += absl::int128(term.coeff.value()) *
1191 std::min(absl::int128(term.bound_diff.value()),
1192 absl::int128(std::ceil(term.lp_value - 1e-6)));
1193 dist += term.LpDistToMaxValue();
1196 CHECK_GE(cover_size, 0);
1197 if (shifted_round_up <= 0) {
1200 return MinimizeCover<CompareRemove>(cover_size, max_shifted_activity);
1205int CoverCutHelper::GetCoverSizeForBooleans() {
1209 int relevant_size = cut_.terms.size();
1210 const double threshold = 1.0 - 1.0 /
static_cast<double>(relevant_size);
1211 for (
int i = 0;
i < relevant_size;) {
1212 const double lp_value = cut_.terms[
i].lp_value;
1215 if (cut_.terms[i].bound_diff > 1) {
1217 std::swap(cut_.terms[i], cut_.terms[relevant_size]);
1221 if (lp_value >= threshold) {
1223 std::swap(cut_.terms[i], cut_.terms[part1]);
1226 }
else if (lp_value >= 0.001) {
1232 std::swap(cut_.terms[i], cut_.terms[relevant_size]);
1237 std::sort(cut_.terms.begin() + part1, cut_.terms.begin() + relevant_size,
1238 [](
const CutTerm&
a,
const CutTerm&
b) {
1239 if (a.lp_value == b.lp_value) {
1241 return a.coeff < b.coeff;
1243 return a.lp_value >
b.lp_value;
1246 double activity = 0.0;
1247 int cover_size = relevant_size;
1248 absl::int128 slack = -cut_.rhs;
1249 for (
int i = 0;
i < relevant_size; ++
i) {
1250 const CutTerm& term = cut_.terms[
i];
1251 activity += term.LpDistToMaxValue();
1260 if (activity > 0.9999) {
1267 slack += absl::int128(term.coeff.value()) *
1268 absl::int128(term.bound_diff.value());
1278 if (slack <= 0)
return 0;
1279 if (cover_size == 0)
return 0;
1280 return MinimizeCover<LargeCoeffFirst>(cover_size, slack);
1283void CoverCutHelper::InitializeCut(
const CutData& input_ct) {
1289 CHECK_GE(cut_.rhs, 0);
1290 DCHECK(cut_.AllCoefficientsArePositive());
1293bool CoverCutHelper::TrySimpleKnapsack(
const CutData& input_ct,
1295 InitializeCut(input_ct);
1299 if (ib_processor !=
nullptr) {
1301 const int old_size =
static_cast<int>(cut_.terms.size());
1302 for (
int i = 0;
i < old_size; ++
i) {
1305 const CutTerm& term = cut_.terms[
i];
1312 ++cover_stats_.num_initial_ibs;
1317 bool has_relevant_int =
false;
1318 for (
const CutTerm& term : cut_.terms) {
1320 has_relevant_int =
true;
1325 const int base_size =
static_cast<int>(cut_.terms.size());
1326 const int cover_size =
1328 ? GetCoverSize<LargeContribFirst, LargeCoeffFirst>(base_size)
1329 : GetCoverSizeForBooleans();
1330 if (!has_relevant_int && ib_processor ==
nullptr) {
1334 has_bool_base_ct_ =
true;
1335 bool_base_ct_ = cut_;
1336 bool_cover_size_ = cover_size;
1338 if (cover_size == 0)
return false;
1355 IntegerValue best_coeff = 0;
1356 double best_score = -1.0;
1357 IntegerValue max_coeff_in_cover(0);
1358 for (
int i = 0;
i < cover_size; ++
i) {
1360 max_coeff_in_cover = std::max(max_coeff_in_cover, term.
coeff);
1361 const double score =
1364 if (score > best_score) {
1366 best_coeff = term.
coeff;
1370 CHECK_LT(cut_.rhs, 0);
1373 best_coeff = max_coeff_in_cover;
1376 std::function<IntegerValue(IntegerValue)> f;
1378 IntegerValue max_magnitude = 0;
1379 for (
const CutTerm& term : cut_.terms) {
1382 const IntegerValue max_scaling(std::min(
1389 if (ib_processor !=
nullptr) {
1391 f, 1, &cut_, &cut_builder_);
1392 cover_stats_.num_lb_ibs += num_lb;
1393 cover_stats_.num_ub_ibs += num_ub;
1394 cover_stats_.num_merges += cut_builder_.NumMergesSinceLastClear();
1397 cover_stats_.num_bumps += ApplyWithPotentialBump(f, best_coeff, &cut_);
1400 for (
int i = cover_size;
i < cut_.terms.size(); ++
i) {
1401 if (cut_.terms[
i].coeff != 0) ++num_lifting_;
1403 cover_stats_.num_lifting += num_lifting_;
1404 ++cover_stats_.num_cuts;
1408bool CoverCutHelper::TrySingleNodeFlow(
const CutData& input_ct,
1410 InitializeCut(input_ct);
1415 const int base_size =
static_cast<int>(cut_.terms.size());
1416 const int cover_size = GetCoverSize<KnapsackAdd, KnapsackRemove>(base_size);
1417 if (cover_size == 0)
return false;
1421 for (
int i = 0;
i < cover_size; ++
i) {
1422 cut_.terms[
i].Complement(&cut_.rhs);
1425 if (cut_.terms[
i].expr_coeffs[1] != 0)
return false;
1440 CHECK_LT(cut_.rhs, 0);
1441 if (cut_.rhs <= absl::int128(std::numeric_limits<int64_t>::min())) {
1445 bool has_large_coeff =
false;
1446 for (
const CutTerm& term : cut_.terms) {
1448 has_large_coeff =
true;
1456 const IntegerValue positive_rhs = -
static_cast<int64_t
>(cut_.rhs);
1458 for (
int i = 0;
i < cover_size; ++
i) {
1459 const IntegerValue magnitude =
IntTypeAbs(cut_.terms[
i].coeff);
1460 min_magnitude = std::min(min_magnitude, magnitude);
1462 const bool use_scaling =
1463 has_large_coeff || min_magnitude == 1 || min_magnitude >= positive_rhs;
1469 if (ib_processor !=
nullptr) {
1471 f, 1, &cut_, &cut_builder_);
1472 flow_stats_.num_lb_ibs += num_lb;
1473 flow_stats_.num_ub_ibs += num_ub;
1474 flow_stats_.num_merges += cut_builder_.NumMergesSinceLastClear();
1479 IntegerValue period = positive_rhs;
1480 for (
const CutTerm& term : cut_.terms) {
1481 if (term.
coeff > 0)
continue;
1482 period = std::max(period, -term.
coeff);
1490 if (f(-period +
FloorRatio(period, 2)) != f(-period)) {
1494 CHECK_EQ(f(-period), f(-positive_rhs));
1495 period = std::max(period,
CapProdI(2, positive_rhs) - 1);
1502 cut_.rhs = absl::int128(f(-positive_rhs).
value());
1503 for (
CutTerm& term : cut_.terms) {
1504 const IntegerValue old_coeff = term.
coeff;
1506 if (old_coeff > 0 && term.
coeff != 0) ++flow_stats_.num_lifting;
1508 ++flow_stats_.num_cuts;
1512bool CoverCutHelper::TryWithLetchfordSouliLifting(
1515 if (has_bool_base_ct_) {
1518 CHECK(ib_processor ==
nullptr);
1519 InitializeCut(bool_base_ct_);
1520 cover_size = bool_cover_size_;
1522 InitializeCut(input_ct);
1528 if (ib_processor !=
nullptr) {
1530 const int old_size =
static_cast<int>(cut_.terms.size());
1531 for (
int i = 0;
i < old_size; ++
i) {
1532 if (cut_.terms[
i].bound_diff <= 1)
continue;
1536 ++ls_stats_.num_initial_ibs;
1542 cover_size = GetCoverSizeForBooleans();
1544 if (cover_size == 0)
return false;
1548 if (cut_.rhs > absl::int128(std::numeric_limits<int64_t>::max())) {
1549 ++ls_stats_.num_overflow_aborts;
1552 const IntegerValue rhs =
static_cast<int64_t
>(cut_.rhs);
1555 IntegerValue sum(0);
1556 std::vector<IntegerValue> cover_weights;
1557 for (
int i = 0;
i < cover_size; ++
i) {
1558 CHECK_EQ(cut_.terms[
i].bound_diff, 1);
1559 CHECK_GT(cut_.terms[
i].coeff, 0);
1560 cover_weights.push_back(cut_.terms[
i].coeff);
1561 sum =
CapAddI(sum, cut_.terms[
i].coeff);
1564 ++ls_stats_.num_overflow_aborts;
1573 IntegerValue previous_sum(0);
1574 std::sort(cover_weights.begin(), cover_weights.end());
1575 for (
int i = 0;
i < cover_size; ++
i) {
1576 q = IntegerValue(cover_weights.size() -
i);
1577 if (previous_sum + cover_weights[
i] * q > rhs) {
1578 p = rhs - previous_sum;
1581 previous_sum += cover_weights[
i];
1588 std::vector<IntegerValue> thresholds;
1589 for (
int i = 0;
i < q; ++
i) {
1591 if (
CapProd(p.value(),
i + 1) >= std::numeric_limits<int64_t>::max() - 1) {
1592 ++ls_stats_.num_overflow_aborts;
1595 thresholds.push_back(
CeilRatio(p * (
i + 1) + 1, q));
1599 std::reverse(cover_weights.begin(), cover_weights.end());
1600 for (
int i = q.value();
i < cover_size; ++
i) {
1601 thresholds.push_back(thresholds.back() + cover_weights[
i]);
1603 CHECK_EQ(thresholds.back(), rhs + 1);
1613 temp_cut_.rhs = cover_size - 1;
1614 temp_cut_.terms.clear();
1616 const int base_size =
static_cast<int>(cut_.terms.size());
1617 for (
int i = 0;
i < base_size; ++
i) {
1618 const CutTerm& term = cut_.terms[
i];
1619 const IntegerValue coeff = term.
coeff;
1620 IntegerValue cut_coeff(1);
1621 if (coeff < thresholds[0]) {
1622 if (
i >= cover_size)
continue;
1628 for (
int i = 1;
i < cover_size; ++
i) {
1629 if (coeff < thresholds[
i])
break;
1630 cut_coeff = IntegerValue(
i + 1);
1632 if (cut_coeff != 0 &&
i >= cover_size) ++ls_stats_.num_lifting;
1633 if (cut_coeff > 1 &&
i < cover_size) ++ls_stats_.num_lifting;
1636 temp_cut_.terms.push_back(term);
1637 temp_cut_.terms.back().coeff = cut_coeff;
1641 ++ls_stats_.num_cuts;
1645BoolRLTCutHelper::~BoolRLTCutHelper() {
1646 if (!VLOG_IS_ON(1))
return;
1647 std::vector<std::pair<std::string, int64_t>> stats;
1648 stats.push_back({
"bool_rlt/num_tried", num_tried_});
1649 stats.push_back({
"bool_rlt/num_tried_factors", num_tried_factors_});
1650 shared_stats_->AddStats(stats);
1653void BoolRLTCutHelper::Initialize(
1654 const absl::flat_hash_map<IntegerVariable, glop::ColIndex>& lp_vars) {
1655 product_detector_->InitializeBooleanRLTCuts(lp_vars, *lp_values_);
1656 enabled_ = !product_detector_->BoolRLTCandidates().empty();
1660bool BoolRLTCutHelper::TrySimpleSeparation(
const CutData& input_ct) {
1661 if (!enabled_)
return false;
1668 absl::flat_hash_set<IntegerVariable> to_try_set;
1669 std::vector<IntegerVariable> to_try;
1686 filtered_input_.terms.clear();
1687 filtered_input_.rhs = input_ct.
rhs;
1689 const auto& candidates = product_detector_->BoolRLTCandidates();
1703 filtered_input_.rhs -= absl::int128(term.
coeff.value()) *
1714 for (
const IntegerVariable factor : candidates.at(
NegationOf(
var))) {
1715 if (to_try_set.insert(factor).second) to_try.push_back(factor);
1718 filtered_input_.terms.push_back(term);
1722 double best_efficacy = 1e-3;
1724 for (
const IntegerVariable factor : to_try) {
1725 ++num_tried_factors_;
1726 if (!TryProduct(factor, filtered_input_))
continue;
1727 const double efficacy = cut_.ComputeEfficacy();
1728 if (efficacy > best_efficacy) {
1729 best_efficacy = efficacy;
1730 best_factor = factor;
1736 return TryProduct(best_factor, input_ct);
1745enum class LinearizationOption {
1754double BoolRLTCutHelper::GetLiteralLpValue(IntegerVariable
var)
const {
1755 if (VariableIsPositive(
var))
return (*lp_values_)[
var];
1756 return 1.0 - (*lp_values_)[PositiveVariable(
var)];
1759bool BoolRLTCutHelper::TryProduct(IntegerVariable factor,
1760 const CutData&
input) {
1763 absl::int128 old_rhs =
input.rhs;
1765 const double factor_lp = GetLiteralLpValue(factor);
1768 for (CutTerm term :
input.terms) {
1769 LinearizationOption best_option = LinearizationOption::DROP;
1772 const IntegerVariable
var = term.GetUnderlyingLiteralOrNone();
1776 if (factor ==
var) {
1779 best_option = LinearizationOption::SQUARE;
1780 cut_.terms.push_back(term);
1787 best_option = LinearizationOption::DROP;
1797 double best_lp = 0.0;
1801 const double complement_lp =
1802 static_cast<double>(term.bound_diff.value()) - term.lp_value;
1803 const double mc_cormick_lp = factor_lp - complement_lp;
1804 if (mc_cormick_lp > best_lp) {
1805 best_option = LinearizationOption::MC_CORMICK;
1806 best_lp = mc_cormick_lp;
1814 const IntegerVariable ub_lit =
1815 product_detector_->LiteralProductUpperBound(factor,
NegationOf(
var));
1816 if (ub_lit != kNoIntegerVariable) {
1817 const double lit_lp = GetLiteralLpValue(ub_lit);
1818 if (factor_lp - lit_lp > best_lp) {
1820 best_option = LinearizationOption::RLT;
1823 term.Complement(&old_rhs);
1826 term.lp_value = lit_lp;
1827 term.ReplaceExpressionByLiteral(ub_lit);
1828 cut_.terms.push_back(term);
1834 if (best_option == LinearizationOption::DROP)
continue;
1837 CHECK(best_option == LinearizationOption::MC_CORMICK);
1838 term.Complement(&old_rhs);
1839 cut_.terms.push_back(term);
1846 const absl::int128 limit(int64_t{1} << 50);
1847 if (old_rhs > limit || old_rhs < -limit)
return false;
1850 term.coeff = -IntegerValue(
static_cast<int64_t
>(old_rhs));
1851 term.lp_value = factor_lp;
1852 term.bound_diff = 1;
1853 term.ReplaceExpressionByLiteral(factor);
1854 cut_.terms.push_back(term);
1862 int linearization_level,
1874 if (trail->CurrentDecisionLevel() > 0 && linearization_level == 1) {
1883 if (x_lb == x_ub || y_lb == y_ub)
return true;
1888 const int64_t x_max_amp = std::max(std::abs(x_lb), std::abs(x_ub));
1889 const int64_t y_max_amp = std::max(std::abs(y_lb), std::abs(y_ub));
1890 constexpr int64_t kMaxSafeInteger = (int64_t{1} << 53) - 1;
1891 if (
CapProd(y_max_amp, x_max_amp) > kMaxSafeInteger)
return true;
1892 if (
CapProd(y_max_amp, std::abs(
x.constant.value())) > kMaxSafeInteger) {
1895 if (
CapProd(x_max_amp, std::abs(
y.constant.value())) > kMaxSafeInteger) {
1899 const auto& lp_values = manager->LpValues();
1900 const double x_lp_value =
x.LpValue(lp_values);
1901 const double y_lp_value =
y.LpValue(lp_values);
1902 const double z_lp_value = z.
LpValue(lp_values);
1910 auto try_add_above_cut = [&](int64_t x_coeff, int64_t y_coeff,
1912 if (-z_lp_value + x_lp_value * x_coeff + y_lp_value * y_coeff >=
1913 rhs + kMinCutViolation) {
1916 cut.
AddTerm(z, IntegerValue(-1));
1917 if (x_coeff != 0) cut.
AddTerm(
x, IntegerValue(x_coeff));
1918 if (y_coeff != 0) cut.
AddTerm(
y, IntegerValue(y_coeff));
1919 manager->AddCut(cut.
Build(),
"PositiveProduct");
1924 auto try_add_below_cut = [&](int64_t x_coeff, int64_t y_coeff,
1926 if (-z_lp_value + x_lp_value * x_coeff + y_lp_value * y_coeff <=
1927 rhs - kMinCutViolation) {
1930 cut.
AddTerm(z, IntegerValue(-1));
1931 if (x_coeff != 0) cut.
AddTerm(
x, IntegerValue(x_coeff));
1932 if (y_coeff != 0) cut.
AddTerm(
y, IntegerValue(y_coeff));
1933 manager->AddCut(cut.
Build(),
"PositiveProduct");
1944 try_add_above_cut(y_lb, x_lb, x_lb * y_lb);
1945 try_add_above_cut(y_ub, x_ub, x_ub * y_ub);
1946 try_add_below_cut(y_ub, x_lb, x_lb * y_ub);
1947 try_add_below_cut(y_lb, x_ub, x_ub * y_lb);
1958 const IntegerValue above_slope = x_ub + x_lb;
1961 above_hyperplan.
AddTerm(square, 1);
1962 above_hyperplan.
AddTerm(
x, -above_slope);
1963 return above_hyperplan.
Build();
1968 IntegerValue x_value,
1970 const IntegerValue below_slope = 2 * x_value + 1;
1973 below_hyperplan.
AddTerm(square, 1);
1974 below_hyperplan.
AddTerm(
x, -below_slope);
1975 return below_hyperplan.
Build();
1986 result.
generate_cuts = [
y,
x, linearization_level, trail, integer_trail,
1991 const IntegerValue x_ub = integer_trail->LevelZeroUpperBound(
x);
1992 const IntegerValue x_lb = integer_trail->LevelZeroLowerBound(
x);
1995 if (x_lb == x_ub)
return true;
1998 if (x_ub > (int64_t{1} << 31))
return true;
2003 const IntegerValue x_floor =
2004 static_cast<int64_t
>(std::floor(
x.LpValue(manager->LpValues())));
2013ImpliedBoundsProcessor::BestImpliedBoundInfo
2014ImpliedBoundsProcessor::GetCachedImpliedBoundInfo(IntegerVariable
var)
const {
2015 auto it = cache_.find(
var);
2016 if (it != cache_.end()) {
2026ImpliedBoundsProcessor::ComputeBestImpliedBound(
2027 IntegerVariable
var,
2029 auto it = cache_.find(
var);
2030 if (it != cache_.end())
return it->second;
2031 BestImpliedBoundInfo result;
2032 double result_slack_lp_value = std::numeric_limits<double>::infinity();
2033 const IntegerValue lb = integer_trail_->LevelZeroLowerBound(
var);
2035 implied_bounds_->GetImpliedBounds(
var)) {
2047 const IntegerValue diff = entry.lower_bound - lb;
2049 const double bool_lp_value = entry.is_positive
2050 ? lp_values[entry.literal_view]
2051 : 1.0 - lp_values[entry.literal_view];
2052 const double slack_lp_value =
2053 lp_values[
var] - ToDouble(lb) - bool_lp_value * ToDouble(diff);
2057 if (slack_lp_value < -1e-4) {
2058 LinearConstraint ib_cut;
2059 ib_cut.lb = kMinIntegerValue;
2060 std::vector<std::pair<IntegerVariable, IntegerValue>> terms;
2061 if (entry.is_positive) {
2063 terms.push_back({entry.literal_view, diff});
2064 terms.push_back({
var, IntegerValue(-1)});
2068 terms.push_back({entry.literal_view, -diff});
2069 terms.push_back({
var, IntegerValue(-1)});
2070 ib_cut.ub = -entry.lower_bound;
2073 ib_cut_pool_.AddCut(std::move(ib_cut),
"IB", lp_values);
2079 if (slack_lp_value + 1e-4 < result_slack_lp_value ||
2080 (slack_lp_value < result_slack_lp_value + 1e-4 &&
2081 entry.lower_bound > result.implied_bound)) {
2082 result_slack_lp_value = slack_lp_value;
2083 result.var_lp_value = lp_values[
var];
2084 result.bool_lp_value = bool_lp_value;
2085 result.implied_bound = entry.lower_bound;
2086 result.is_positive = entry.is_positive;
2087 result.bool_var = entry.literal_view;
2090 cache_[
var] = result;
2094void ImpliedBoundsProcessor::RecomputeCacheAndSeparateSomeImpliedBoundCuts(
2097 for (
const IntegerVariable
var :
2098 implied_bounds_->VariablesWithImpliedBounds()) {
2100 ComputeBestImpliedBound(
var, lp_values);
2104bool ImpliedBoundsProcessor::DecomposeWithImpliedLowerBound(
2109 if (!term.
IsSimple())
return false;
2134 if (bound_diff <= 0)
return false;
2174bool ImpliedBoundsProcessor::DecomposeWithImpliedUpperBound(
2177 absl::int128 unused = 0;
2180 if (!DecomposeWithImpliedLowerBound(complement, factor_t, bool_term,
2191 CHECK_EQ(unused, absl::int128(0));
2195std::pair<int, int> ImpliedBoundsProcessor::PostprocessWithImpliedBound(
2196 const std::function<IntegerValue(IntegerValue)>& f, IntegerValue factor_t,
2198 int num_applied_lb = 0;
2199 int num_applied_ub = 0;
2206 const int initial_size = cut->
terms.size();
2207 for (
int i = 0;
i < initial_size; ++
i) {
2215 bool expand =
false;
2216 if (DecomposeWithImpliedLowerBound(term, factor_t, bool_term, slack_term)) {
2223 base_score = AsDouble(f(bool_term.
coeff)) * bool_term.
lp_value +
2249 if (DecomposeWithImpliedUpperBound(term, factor_t, ub_bool_term,
2251 const double score =
2256 if (score > base_score + 1e-2) {
2258 term = ub_slack_term;
2270 return {num_applied_lb, num_applied_ub};
2274bool ImpliedBoundsProcessor::TryToExpandWithLowerImpliedbound(
2275 IntegerValue factor_t,
int i,
bool complement,
CutData* cut,
2281 if (!DecomposeWithImpliedLowerBound(term, factor_t, bool_term, slack_term)) {
2307bool ImpliedBoundsProcessor::CacheDataForCut(IntegerVariable first_slack,
2309 base_cut_builder_.ClearIndices();
2310 cached_data_.clear();
2313 for (
int i = 0;
i <
size; ++
i) {
2317 if (term.
expr_vars[0] >= first_slack)
continue;
2320 const IntegerVariable ib_var = term.
expr_coeffs[0] > 0
2325 cut->
terms[
i].cached_implied_lb = cached_data_.size();
2326 cached_data_.emplace_back(std::move(lb_info));
2329 GetCachedImpliedBoundInfo(
NegationOf(ib_var));
2331 cut->
terms[
i].cached_implied_ub = cached_data_.size();
2332 cached_data_.emplace_back(std::move(ub_info));
2336 return !cached_data_.empty();
2339void SumOfAllDiffLowerBounder::Clear() {
2340 min_values_.clear();
2348 if (integer_trail.
IsFixed(expr)) {
2349 min_values_.insert(integer_trail.
FixedValue(expr));
2351 if (expr.
coeff > 0) {
2353 for (
const IntegerValue
value :
2356 if (++count >= num_exprs)
break;
2360 for (
const IntegerValue
value :
2363 if (++count >= num_exprs)
break;
2369IntegerValue SumOfAllDiffLowerBounder::SumOfMinDomainValues() {
2371 IntegerValue sum = 0;
2372 for (
const IntegerValue
value : min_values_) {
2374 if (++count >= expr_mins_.size())
return sum;
2379IntegerValue SumOfAllDiffLowerBounder::SumOfDifferentMins() {
2380 std::sort(expr_mins_.begin(), expr_mins_.end());
2382 IntegerValue result = 0;
2383 for (
const IntegerValue
value : expr_mins_) {
2385 tmp_value = std::max(tmp_value + 1,
value);
2386 result += tmp_value;
2391IntegerValue SumOfAllDiffLowerBounder::GetBestLowerBound(std::string& suffix) {
2392 const IntegerValue domain_bound = SumOfMinDomainValues();
2393 const IntegerValue alldiff_bound = SumOfDifferentMins();
2394 if (domain_bound > alldiff_bound) {
2396 return domain_bound;
2398 suffix = alldiff_bound > domain_bound ?
"a" :
"e";
2399 return alldiff_bound;
2404void TryToGenerateAllDiffCut(
2405 const std::vector<std::pair<double, AffineExpression>>& sorted_exprs_lp,
2409 const int num_exprs = sorted_exprs_lp.size();
2411 std::vector<AffineExpression> current_set_exprs;
2417 for (
const auto& [expr_lp, expr] : sorted_exprs_lp) {
2419 diff_mins.
Add(expr, num_exprs, integer_trail);
2420 negated_diff_maxes.
Add(expr.Negated(), num_exprs, integer_trail);
2421 current_set_exprs.push_back(expr);
2422 CHECK_EQ(current_set_exprs.size(), diff_mins.
size());
2423 CHECK_EQ(current_set_exprs.size(), negated_diff_maxes.
size());
2424 std::string min_suffix;
2425 const IntegerValue required_min_sum =
2427 std::string max_suffix;
2428 const IntegerValue required_max_sum =
2430 if (sum <
ToDouble(required_min_sum) - kMinCutViolation ||
2431 sum >
ToDouble(required_max_sum) + kMinCutViolation) {
2434 cut.AddTerm(expr, IntegerValue(1));
2436 top_n_cuts.
AddCut(cut.Build(),
2437 absl::StrCat(
"AllDiff_", min_suffix, max_suffix),
2443 current_set_exprs.clear();
2445 negated_diff_maxes.
Clear();
2453 const std::vector<AffineExpression>& exprs,
Model*
model) {
2458 if (!integer_trail->
IsFixed(expr)) {
2459 result.
vars.push_back(expr.var);
2471 const auto& lp_values = manager->LpValues();
2472 std::vector<std::pair<double, AffineExpression>> sorted_exprs;
2478 sorted_exprs.push_back(std::make_pair(expr.LpValue(lp_values), expr));
2482 std::sort(sorted_exprs.begin(), sorted_exprs.end(),
2483 [](std::pair<double, AffineExpression>&
a,
2484 const std::pair<double, AffineExpression>&
b) {
2485 return a.first < b.first;
2487 TryToGenerateAllDiffCut(sorted_exprs, *integer_trail, lp_values, top_n_cuts,
2490 std::reverse(sorted_exprs.begin(), sorted_exprs.end());
2491 TryToGenerateAllDiffCut(sorted_exprs, *integer_trail, lp_values, top_n_cuts,
2496 VLOG(2) <<
"Created all_diff cut generator of size: " << exprs.size();
2502IntegerValue MaxCornerDifference(
const IntegerVariable
var,
2503 const IntegerValue w1_i,
2504 const IntegerValue w2_i,
2505 const IntegerTrail& integer_trail) {
2506 const IntegerValue lb = integer_trail.LevelZeroLowerBound(
var);
2507 const IntegerValue ub = integer_trail.LevelZeroUpperBound(
var);
2508 return std::max((w2_i - w1_i) * lb, (w2_i - w1_i) * ub);
2517IntegerValue MPlusCoefficient(
2518 const std::vector<IntegerVariable>& x_vars,
2519 const std::vector<LinearExpression>& exprs,
2521 const int max_index,
const IntegerTrail& integer_trail) {
2522 IntegerValue coeff = exprs[max_index].offset;
2525 for (
const IntegerVariable
var : x_vars) {
2526 const int target_index = variable_partition[
var];
2527 if (max_index != target_index) {
2528 coeff += MaxCornerDifference(
2529 var, GetCoefficientOfPositiveVar(
var, exprs[target_index]),
2530 GetCoefficientOfPositiveVar(
var, exprs[max_index]), integer_trail);
2539double ComputeContribution(
2540 const IntegerVariable xi_var,
const std::vector<IntegerVariable>& z_vars,
2541 const std::vector<LinearExpression>& exprs,
2543 const IntegerTrail& integer_trail,
const int target_index) {
2544 CHECK_GE(target_index, 0);
2545 CHECK_LT(target_index, exprs.size());
2546 const LinearExpression& target_expr = exprs[target_index];
2547 const double xi_value = lp_values[xi_var];
2549 double contrib =
ToDouble(wt_i) * xi_value;
2550 for (
int expr_index = 0; expr_index < exprs.size(); ++expr_index) {
2551 if (expr_index == target_index)
continue;
2552 const LinearExpression& max_expr = exprs[expr_index];
2553 const double z_max_value = lp_values[z_vars[expr_index]];
2554 const IntegerValue corner_value = MaxCornerDifference(
2557 contrib +=
ToDouble(corner_value) * z_max_value;
2564 const IntegerVariable target,
const std::vector<LinearExpression>& exprs,
2565 const std::vector<IntegerVariable>& z_vars,
Model*
model) {
2567 std::vector<IntegerVariable> x_vars;
2568 result.
vars = {target};
2569 const int num_exprs = exprs.size();
2570 for (
int i = 0;
i < num_exprs; ++
i) {
2571 result.
vars.push_back(z_vars[
i]);
2572 x_vars.insert(x_vars.end(), exprs[
i].vars.begin(), exprs[
i].vars.end());
2576 DCHECK(std::all_of(x_vars.begin(), x_vars.end(), [](IntegerVariable
var) {
2577 return VariableIsPositive(var);
2579 result.
vars.insert(result.
vars.end(), x_vars.begin(), x_vars.end());
2582 result.
generate_cuts = [x_vars, z_vars, target, num_exprs, exprs,
2585 const auto& lp_values = manager->LpValues();
2587 lp_values.size(), -1);
2589 variable_partition_contrib(lp_values.size(),
2590 std::numeric_limits<double>::infinity());
2591 for (
int expr_index = 0; expr_index < num_exprs; ++expr_index) {
2592 for (
const IntegerVariable
var : x_vars) {
2593 const double contribution = ComputeContribution(
2594 var, z_vars, exprs, lp_values, *integer_trail, expr_index);
2595 const double prev_contribution = variable_partition_contrib[
var];
2596 if (contribution < prev_contribution) {
2597 variable_partition[
var] = expr_index;
2598 variable_partition_contrib[
var] = contribution;
2605 double violation = lp_values[target];
2606 cut.
AddTerm(target, IntegerValue(-1));
2608 for (
const IntegerVariable xi_var : x_vars) {
2609 const int input_index = variable_partition[xi_var];
2612 if (coeff != IntegerValue(0)) {
2615 violation -=
ToDouble(coeff) * lp_values[xi_var];
2617 for (
int expr_index = 0; expr_index < num_exprs; ++expr_index) {
2618 const IntegerVariable z_var = z_vars[expr_index];
2619 const IntegerValue z_coeff = MPlusCoefficient(
2620 x_vars, exprs, variable_partition, expr_index, *integer_trail);
2621 if (z_coeff != IntegerValue(0)) {
2624 violation -=
ToDouble(z_coeff) * lp_values[z_var];
2626 if (violation > 1e-2) {
2627 manager->AddCut(cut.
Build(),
"LinMax");
2636IntegerValue EvaluateMaxAffine(
2637 const std::vector<std::pair<IntegerValue, IntegerValue>>& affines,
2639 IntegerValue
y = kMinIntegerValue;
2640 for (
const auto& p : affines) {
2641 y = std::max(
y,
x * p.first + p.second);
2650 const std::vector<std::pair<IntegerValue, IntegerValue>>& affines,
2654 const IntegerValue x_max = integer_trail->LevelZeroUpperBound(
var);
2656 const IntegerValue y_at_min = EvaluateMaxAffine(affines, x_min);
2657 const IntegerValue y_at_max = EvaluateMaxAffine(affines, x_max);
2659 const IntegerValue delta_x = x_max - x_min;
2660 const IntegerValue delta_y = y_at_max - y_at_min;
2673 int64_t abs_magnitude = std::abs(target.
offset.value());
2674 for (
int i = 0;
i < target.
vars.size(); ++
i) {
2675 const IntegerVariable
var = target.
vars[
i];
2676 const IntegerValue var_min = integer_trail->LevelZeroLowerBound(
var);
2677 const IntegerValue var_max = integer_trail->LevelZeroUpperBound(
var);
2679 CapProd(std::max(std::abs(var_min.value()), std::abs(var_max.value())),
2680 std::abs(target.
coeffs[
i].value())),
2694 VLOG(2) <<
"Linear constraint can cause overflow: " << builder->
Build();
2704 std::vector<std::pair<IntegerValue, IntegerValue>> affines,
2714 if (integer_trail->
IsFixed(
var))
return true;
2717 manager->AddCut(builder.
Build(), cut_name);
2725 const std::vector<IntegerVariable>& base_variables,
Model*
model) {
2728 std::vector<IntegerVariable> variables;
2729 std::vector<Literal> literals;
2730 absl::flat_hash_map<LiteralIndex, IntegerVariable> positive_map;
2731 absl::flat_hash_map<LiteralIndex, IntegerVariable> negative_map;
2734 for (
const IntegerVariable
var : base_variables) {
2735 if (integer_trail->LowerBound(
var) != IntegerValue(0))
continue;
2736 if (integer_trail->UpperBound(
var) != IntegerValue(1))
continue;
2737 const LiteralIndex literal_index = encoder->GetAssociatedLiteral(
2738 IntegerLiteral::GreaterOrEqual(
var, IntegerValue(1)));
2740 variables.push_back(
var);
2741 literals.push_back(
Literal(literal_index));
2742 positive_map[literal_index] =
var;
2747 result.
vars = variables;
2749 result.
generate_cuts = [variables, literals, implication_graph, positive_map,
2752 std::vector<double> packed_values;
2753 const auto& lp_values = manager->LpValues();
2754 for (
int i = 0;
i < literals.size(); ++
i) {
2755 packed_values.push_back(lp_values[variables[
i]]);
2757 const std::vector<std::vector<Literal>> at_most_ones =
2758 implication_graph->GenerateAtMostOnesWithLargeWeight(literals,
2761 for (
const std::vector<Literal>& at_most_one : at_most_ones) {
2766 model, IntegerValue(std::numeric_limits<int64_t>::min()),
2768 for (
const Literal l : at_most_one) {
2769 if (positive_map.contains(l.Index())) {
2770 builder.
AddTerm(positive_map.at(l.Index()), IntegerValue(1));
2773 builder.
AddTerm(negative_map.at(l.Index()), IntegerValue(-1));
2778 manager->AddCut(builder.
Build(),
"Clique");
DomainIteratorBeginEnd Values() const &
Stores temporaries used to build or manipulate a CutData.
int NumMergesSinceLastClear() const
void AddOrMergeTerm(const CutTerm &term, IntegerValue t, CutData *cut)
CutDataBuilder * BaseCutBuilder()
bool TryToExpandWithLowerImpliedbound(IntegerValue factor_t, int i, bool complement, CutData *cut, CutDataBuilder *builder)
Important: The cut_builder_ must have been reset.
std::pair< int, int > PostprocessWithImpliedBound(const std::function< IntegerValue(IntegerValue)> &f, IntegerValue factor_t, CutData *cut, CutDataBuilder *builder)
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)
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
const std::string name
A name for logging purposes.
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
IntegerValue FloorRatio(IntegerValue dividend, IntegerValue positive_divisor)
CutGenerator CreateCliqueCutGenerator(const std::vector< IntegerVariable > &base_variables, Model *model)
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)
IntegerValue GetFactorT(IntegerValue rhs_remainder, IntegerValue divisor, IntegerValue max_magnitude)
IntType IntTypeAbs(IntType t)
bool BuildMaxAffineUpConstraint(const LinearExpression &target, IntegerVariable var, const std::vector< std::pair< IntegerValue, IntegerValue > > &affines, Model *model, LinearConstraintBuilder *builder)
CutGenerator CreatePositiveMultiplicationCutGenerator(AffineExpression z, AffineExpression x, AffineExpression y, int linearization_level, Model *model)
A cut generator for z = x * y (x and y >= 0).
IntegerValue CeilRatio(IntegerValue dividend, IntegerValue positive_divisor)
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(const std::vector< IntegerVariable > &vars)
Returns the vector of the negated variables.
CutGenerator CreateLinMaxCutGenerator(const IntegerVariable target, const std::vector< LinearExpression > &exprs, const std::vector< IntegerVariable > &z_vars, Model *model)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
const IntegerVariable kNoIntegerVariable(-1)
void CleanTermsAndFillConstraint(std::vector< std::pair< IntegerVariable, IntegerValue > > *terms, LinearExpression *output)
std::function< IntegerValue(IntegerValue)> GetSuperAdditiveStrengtheningFunction(IntegerValue positive_rhs, IntegerValue min_magnitude)
CutGenerator CreateSquareCutGenerator(AffineExpression y, AffineExpression x, int linearization_level, 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)
CutGenerator CreateAllDifferentCutGenerator(const std::vector< AffineExpression > &exprs, 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)
std::vector< double > lower_bounds
std::vector< double > upper_bounds
double l2_norm
The L2 norm of the finite values.
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.
void Canonicalize()
This sorts terms and fill both num_relevant_entries and max_magnitude.
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
bool AppendOneTerm(IntegerVariable var, IntegerValue coeff, double lp_value, IntegerValue lb, IntegerValue ub)
void ComplementForSmallerLpValues()
std::vector< IntegerVariable > vars
std::function< bool(LinearConstraintManager *manager)> generate_cuts
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
std::unique_ptr< IntegerValue[]> coeffs
std::unique_ptr< IntegerVariable[]> vars
std::vector< IntegerVariable > vars
std::vector< IntegerValue > coeffs
bool use_ib_before_heuristic