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;
467 if (!VLOG_IS_ON(1))
return;
468 if (shared_stats_ ==
nullptr)
return;
469 std::vector<std::pair<std::string, int64_t>> stats;
470 stats.push_back({
"gub/num_improvements", num_improvements_});
472 {
"gub/num_todo_both_complements", num_todo_both_complements_});
473 shared_stats_->AddStats(stats);
477 const std::function<IntegerValue(IntegerValue)>& f,
478 const IntegerValue divisor,
CutData* cut) {
485 const absl::int128 original_rhs = cut->
rhs;
486 const int num_terms = cut->
terms.size();
487 initial_coeffs_.resize(num_terms);
488 for (
int i = 0;
i < num_terms; ++
i) {
490 initial_coeffs_[
i] = entry.
coeff;
494 const int bump_index = ApplyWithPotentialBump(f, divisor, cut);
495 last_num_bumps_ += (bump_index >= 0);
499 for (
int i = 0;
i < num_terms; ++
i) {
500 if (initial_coeffs_[
i] > 0 && cut->
terms[
i].coeff > 0) ++last_num_lifts_;
509 already_seen_.clear();
513 const int limit = integer_encoder_->NumVariables();
514 for (
int i = 0;
i < num_terms; ++
i) {
519 if (entry.
expr_vars[0] >= limit)
continue;
520 if (integer_trail_->LevelZeroLowerBound(entry.
expr_vars[0]) != 0)
continue;
521 if (integer_trail_->LevelZeroUpperBound(entry.
expr_vars[0]) != 1)
continue;
525 complemented =
false;
534 const LiteralIndex literal_index = integer_encoder_->GetAssociatedLiteral(
536 if (literal_index != -1) {
537 absl::Span<const int> indices =
538 implications_->AtMostOneIndices(
Literal(literal_index));
539 if (!indices.empty()) {
544 for (
const int index : indices) amo_count_[index]++;
547 {
i,
Literal(literal_index), initial_coeffs_[
i], complemented});
554 std::sort(literals_.begin(), literals_.end(),
555 [](
const LiteralInCut a, LiteralInCut
b) {
556 if (a.complemented != b.complemented) return a.complemented;
557 return a.original_coeff > b.original_coeff;
571 bool rhs_changed_once =
false;
572 const int num_literals = literals_.size();
573 const IntegerValue f_divisor = f(divisor);
574 for (
int i = 0;
i < num_literals; ++
i) {
576 int64_t best_sum = 0;
577 bool was_lifted =
false;
578 const LiteralInCut& curr = literals_[
i];
583 if (already_seen_.contains(curr.literal))
continue;
584 already_seen_.insert(curr.literal);
586 for (
const int amo_index : implications_->AtMostOneIndices(curr.literal)) {
587 const int64_t count_or_index = amo_count_[amo_index];
591 if (count_or_index < 0) {
593 const int rep_index_in_literals = -count_or_index - 1;
594 CHECK_LT(rep_index_in_literals,
i);
595 const LiteralInCut& rep = literals_[rep_index_in_literals];
596 if (rep.complemented && !curr.complemented) {
597 const IntegerValue f_rep = cut->
terms[rep.index].coeff;
610 const IntegerValue x_d = curr.original_coeff / divisor;
611 const IntegerValue x_r = curr.original_coeff % divisor;
612 IntegerValue y_d = rep.original_coeff / divisor;
613 IntegerValue y_r = rep.original_coeff % divisor;
615 if (x_r > 0 && y_r > 0) {
618 }
else if (x_r < 0 && y_r < 0) {
623 const IntegerValue new_coeff =
624 -f_rep + (x_d + y_d) * f_divisor + f(x_r + y_r);
628 if (new_coeff > cut->
terms[curr.index].coeff) {
632 cut->
terms[curr.index].coeff = new_coeff;
637 if (rep.original_coeff < 0 && rep.complemented &&
638 curr.original_coeff < 0 && curr.complemented) {
639 ++num_todo_both_complements_;
646 if ( (
true))
continue;
662 if (!rhs_changed_once && rep.original_coeff == curr.original_coeff) {
663 const int64_t a = -rep.original_coeff.value();
664 const absl::int128 new_rhs =
665 ApplyToInt128(f, divisor, original_rhs + a) + f(-a).value();
666 CHECK_LE(new_rhs, cut->
rhs);
667 if (new_rhs < cut->rhs) {
670 rhs_changed_once =
true;
675 if (count_or_index > best_sum) {
676 best_sum = count_or_index;
681 if (was_lifted)
continue;
685 if (curr.complemented && best != -1) {
686 amo_count_[best] = -
i - 1;
695IntegerValue
GetFactorT(IntegerValue rhs_remainder, IntegerValue divisor,
696 IntegerValue max_magnitude) {
700 IntegerValue max_t(std::numeric_limits<int64_t>::max());
701 if (max_magnitude != 0) {
702 max_t = max_t / max_magnitude;
704 return rhs_remainder == 0
706 : std::min(max_t,
CeilRatio(divisor / 2, rhs_remainder));
710 IntegerValue rhs_remainder, IntegerValue divisor, IntegerValue t,
711 IntegerValue max_scaling) {
712 DCHECK_GE(max_scaling, 1);
717 DCHECK_LT(rhs_remainder, divisor);
723 std::min(max_scaling, std::numeric_limits<int64_t>::max() / divisor);
725 const IntegerValue size = divisor - rhs_remainder;
726 if (max_scaling == 1 || size == 1) {
730 return [t, divisor](IntegerValue coeff) {
733 }
else if (size <= max_scaling) {
734 return [size, rhs_remainder, t, divisor](IntegerValue coeff) {
735 const IntegerValue t_coeff = t * coeff;
736 const IntegerValue ratio =
FloorRatio(t_coeff, divisor);
738 const IntegerValue diff = remainder - rhs_remainder;
739 return size * ratio + std::max(IntegerValue(0), diff);
741 }
else if (max_scaling.value() * rhs_remainder.value() < divisor) {
751 return [t, divisor, max_scaling](IntegerValue coeff) {
752 const IntegerValue t_coeff = t * coeff;
753 const IntegerValue ratio =
FloorRatio(t_coeff, divisor);
755 const IntegerValue bucket =
FloorRatio(remainder * max_scaling, divisor);
756 return max_scaling * ratio + bucket;
781 return [size, rhs_remainder, t, divisor, max_scaling](IntegerValue coeff) {
782 const IntegerValue t_coeff = t * coeff;
783 const IntegerValue ratio =
FloorRatio(t_coeff, divisor);
785 const IntegerValue diff = remainder - rhs_remainder;
786 const IntegerValue bucket =
787 diff > 0 ?
CeilRatio(diff * (max_scaling - 1), size)
789 return max_scaling * ratio + bucket;
795 IntegerValue positive_rhs, IntegerValue min_magnitude) {
796 CHECK_GT(positive_rhs, 0);
797 CHECK_GT(min_magnitude, 0);
799 if (min_magnitude >=
CeilRatio(positive_rhs, 2)) {
800 return [positive_rhs](IntegerValue v) {
801 if (v >= 0)
return IntegerValue(0);
802 if (v > -positive_rhs)
return IntegerValue(-1);
803 return IntegerValue(-2);
810 min_magnitude = std::min(min_magnitude,
FloorRatio(positive_rhs, 2));
811 const IntegerValue second_threshold = positive_rhs - min_magnitude;
812 return [positive_rhs, min_magnitude, second_threshold](IntegerValue v) {
813 if (v >= 0)
return IntegerValue(0);
814 if (v <= -positive_rhs)
return -positive_rhs;
815 if (v <= -second_threshold)
return -second_threshold;
820 if (v >= -min_magnitude)
return -min_magnitude;
828std::function<IntegerValue(IntegerValue)>
830 IntegerValue scaling) {
831 if (scaling >= positive_rhs) {
833 return [positive_rhs](IntegerValue v) {
834 if (v >= 0)
return IntegerValue(0);
835 if (v <= -positive_rhs)
return -positive_rhs;
842 std::min(scaling, IntegerValue(std::numeric_limits<int64_t>::max()) /
845 return [](IntegerValue v) {
846 if (v >= 0)
return IntegerValue(0);
847 return IntegerValue(-1);
852 return [positive_rhs, scaling](IntegerValue v) {
853 if (v >= 0)
return IntegerValue(0);
854 if (v <= -positive_rhs)
return -scaling;
855 return FloorRatio(v * (scaling - 1), (positive_rhs - 1));
860 if (!VLOG_IS_ON(1))
return;
861 if (shared_stats_ ==
nullptr)
return;
862 std::vector<std::pair<std::string, int64_t>> stats;
863 stats.push_back({
"rounding_cut/num_initial_ibs_", total_num_initial_ibs_});
865 {
"rounding_cut/num_initial_merges_", total_num_initial_merges_});
866 stats.push_back({
"rounding_cut/num_pos_lifts", total_num_pos_lifts_});
867 stats.push_back({
"rounding_cut/num_neg_lifts", total_num_neg_lifts_});
869 {
"rounding_cut/num_post_complements", total_num_post_complements_});
870 stats.push_back({
"rounding_cut/num_overflows", total_num_overflow_abort_});
871 stats.push_back({
"rounding_cut/num_adjusts", total_num_coeff_adjust_});
872 stats.push_back({
"rounding_cut/num_merges", total_num_merges_});
873 stats.push_back({
"rounding_cut/num_bumps", total_num_bumps_});
875 {
"rounding_cut/num_final_complements", total_num_final_complements_});
876 stats.push_back({
"rounding_cut/num_dominating_f", total_num_dominating_f_});
877 shared_stats_->AddStats(stats);
880double IntegerRoundingCutHelper::GetScaledViolation(
881 IntegerValue divisor, IntegerValue max_scaling,
882 IntegerValue remainder_threshold,
const CutData& cut) {
883 absl::int128 rhs = cut.
rhs;
886 if (initial_rhs_remainder < remainder_threshold)
return 0.0;
904 adjusted_coeffs_.clear();
905 const IntegerValue adjust_threshold =
906 (divisor - initial_rhs_remainder - 1) /
908 if (adjust_threshold > 0) {
912 double max_violation =
static_cast<double>(initial_rhs_remainder.value());
916 if (remainder == 0)
continue;
917 if (remainder <= initial_rhs_remainder) {
921 static_cast<double>(remainder.value()) * entry.
lp_value;
922 if (max_violation <= 1e-3)
return 0.0;
927 const IntegerValue adjust = divisor - remainder;
928 const IntegerValue prod = CapProdI(adjust, entry.
bound_diff);
929 if (prod <= adjust_threshold) {
932 const IntegerValue new_coeff = CapAddI(entry.
coeff, adjust);
933 if (!AtMinOrMaxInt64I(new_coeff)) {
934 rhs += absl::int128(prod.value());
935 adjusted_coeffs_.push_back({i, new_coeff});
936 max_magnitude = std::max(max_magnitude, IntTypeAbs(new_coeff));
943 const IntegerValue t =
GetFactorT(rhs_remainder, divisor, max_magnitude);
955 double max_violation = scaling *
ToDouble(rhs_remainder);
961 double violation = -
static_cast<double>(ApplyToInt128(f, divisor, rhs));
962 double l2_norm = 0.0;
963 int adjusted_coeffs_index = 0;
965 const CutTerm& entry = cut.
terms[
i];
968 IntegerValue coeff = entry.coeff;
969 if (adjusted_coeffs_index < adjusted_coeffs_.size() &&
970 adjusted_coeffs_[adjusted_coeffs_index].first == i) {
971 coeff = adjusted_coeffs_[adjusted_coeffs_index].second;
972 adjusted_coeffs_index++;
975 if (coeff == 0)
continue;
976 const IntegerValue new_coeff = f(coeff);
977 const double new_coeff_double =
ToDouble(new_coeff);
978 const double lp_value = entry.lp_value;
983 l2_norm += new_coeff_double * new_coeff_double;
984 violation += new_coeff_double * lp_value;
985 max_violation -= (scaling *
ToDouble(coeff) - new_coeff_double) * lp_value;
986 if (max_violation <= 1e-3)
return 0.0;
988 if (l2_norm == 0.0)
return 0.0;
998 return violation / sqrt(l2_norm);
1009 std::vector<CutTerm>* new_bool_terms =
1011 for (
CutTerm& term : cut_.terms) {
1020 true, &term, &cut_.rhs, new_bool_terms)) {
1021 ++total_num_initial_ibs_;
1029 true, &term, &cut_.rhs, new_bool_terms)) {
1030 ++total_num_initial_ibs_;
1037 if (new_bool_terms->empty())
return false;
1038 total_num_initial_merges_ +=
1040 absl::MakeSpan(*new_bool_terms), IntegerValue(1), &cut_);
1061 const IntegerValue remainder_threshold(
1062 std::max(IntegerValue(1), cut_.max_magnitude / 1000));
1063 if (cut_.rhs >= 0 && cut_.rhs < remainder_threshold.value()) {
1073 for (
const CutTerm& entry : cut_.terms) {
1076 if (magnitude <= remainder_threshold)
continue;
1077 divisors_.push_back(magnitude);
1081 if (divisors_.size() > 50)
break;
1083 if (divisors_.empty())
return false;
1093 IntegerValue best_divisor(0);
1094 double best_scaled_violation = 1e-3;
1095 for (
const IntegerValue divisor : divisors_) {
1098 const double violation = GetScaledViolation(divisor, options.
max_scaling,
1099 remainder_threshold, cut_);
1100 if (violation > best_scaled_violation) {
1101 best_scaled_violation = violation;
1102 best_adjusted_coeffs_ = adjusted_coeffs_;
1103 best_divisor = divisor;
1106 if (best_divisor == 0)
return false;
1109 for (
int div = 2; div < 9; ++div) {
1110 const IntegerValue divisor = best_divisor / IntegerValue(div);
1111 if (divisor <= 1)
continue;
1112 const double violation = GetScaledViolation(divisor, options.
max_scaling,
1113 remainder_threshold, cut_);
1114 if (violation > best_scaled_violation) {
1115 best_scaled_violation = violation;
1116 best_adjusted_coeffs_ = adjusted_coeffs_;
1117 best_divisor = divisor;
1125 for (
CutTerm& entry : cut_.terms) {
1127 if (entry.
coeff % best_divisor == 0)
continue;
1132 const double violation = GetScaledViolation(
1133 best_divisor, options.
max_scaling, remainder_threshold, cut_);
1134 if (violation > best_scaled_violation) {
1136 ++total_num_post_complements_;
1137 best_scaled_violation = violation;
1138 best_adjusted_coeffs_ = adjusted_coeffs_;
1146 for (
const auto [index, new_coeff] : best_adjusted_coeffs_) {
1147 ++total_num_coeff_adjust_;
1148 CutTerm& entry = cut_.terms[index];
1149 const IntegerValue remainder = new_coeff - entry.
coeff;
1150 CHECK_GT(remainder, 0);
1151 entry.
coeff = new_coeff;
1152 cut_.rhs += absl::int128(remainder.value()) *
1154 cut_.max_magnitude = std::max(cut_.max_magnitude,
IntTypeAbs(new_coeff));
1159 IntegerValue factor_t =
1160 GetFactorT(rhs_remainder, best_divisor, cut_.max_magnitude);
1170 remainders_.clear();
1171 for (
const CutTerm& entry : cut_.terms) {
1173 const IntegerValue coeff = entry.
coeff;
1175 if (r > rhs_remainder) remainders_.push_back(r);
1178 if (remainders_.size() <= 100) {
1180 for (
const IntegerValue r : remainders_) {
1181 best_rs_.push_back(f(r));
1183 IntegerValue best_d = f(best_divisor);
1188 for (
const IntegerValue t :
1190 GetFactorT(rhs_remainder, best_divisor, cut_.max_magnitude)}) {
1191 for (IntegerValue s(2); s <= options.
max_scaling; ++s) {
1194 int num_strictly_better = 0;
1196 const IntegerValue d = g(best_divisor);
1197 for (
int i = 0;
i < best_rs_.size(); ++
i) {
1198 const IntegerValue temp = g(remainders_[
i]);
1199 if (temp * best_d < best_rs_[
i] * d)
break;
1200 if (temp * best_d > best_rs_[
i] * d) num_strictly_better++;
1201 rs_.push_back(temp);
1203 if (rs_.size() == best_rs_.size() && num_strictly_better > 0) {
1204 ++total_num_dominating_f_;
1217 if (ib_processor !=
nullptr) {
1218 const auto [num_lb, num_ub, num_merges] =
1220 total_num_pos_lifts_ += num_lb;
1221 total_num_neg_lifts_ += num_ub;
1222 total_num_merges_ += num_merges;
1223 num_ib_used_ = num_lb + num_ub;
1228 for (
int i = 0;
i < 3; ++
i) {
1229 const int64_t saved = total_num_final_complements_;
1230 for (
CutTerm& entry : cut_.terms) {
1247 if (entry.
coeff % best_divisor == 0)
continue;
1258 const double lp2 =
ToDouble(f(remainder - prod)) -
1261 if (lp2 + 1e-2 < lp1) {
1263 ++total_num_final_complements_;
1266 if (total_num_final_complements_ == saved)
break;
1269 gub_helper_.ApplyWithPotentialBumpAndGUB(f, best_divisor, &cut_);
1270 total_num_bumps_ += gub_helper_.last_num_bumps();
1275 if (!VLOG_IS_ON(1))
return;
1276 if (shared_stats_ ==
nullptr)
return;
1278 std::vector<std::pair<std::string, int64_t>> stats;
1279 const auto add_stats = [&stats](absl::string_view name,
const CutStats& s) {
1281 {absl::StrCat(name,
"num_overflows"), s.num_overflow_aborts});
1282 stats.push_back({absl::StrCat(name,
"num_lifting"), s.num_lifting});
1283 stats.push_back({absl::StrCat(name,
"num_initial_ib"), s.num_initial_ibs});
1284 stats.push_back({absl::StrCat(name,
"num_implied_lb"), s.num_lb_ibs});
1285 stats.push_back({absl::StrCat(name,
"num_implied_ub"), s.num_ub_ibs});
1286 stats.push_back({absl::StrCat(name,
"num_bumps"), s.num_bumps});
1287 stats.push_back({absl::StrCat(name,
"num_gubs"), s.num_gubs});
1288 stats.push_back({absl::StrCat(name,
"num_cuts"), s.num_cuts});
1289 stats.push_back({absl::StrCat(name,
"num_merges"), s.num_merges});
1291 add_stats(
"cover_cut/", cover_stats_);
1292 add_stats(
"flow_cut/", flow_stats_);
1293 add_stats(
"ls_cut/", ls_stats_);
1294 shared_stats_->AddStats(stats);
1299struct LargeCoeffFirst {
1301 if (a.
coeff ==
b.coeff) {
1304 return a.
coeff > b.coeff;
1308struct SmallContribFirst {
1309 bool operator()(
const CutTerm& a,
const CutTerm& b)
const {
1310 const double contrib_a = a.lp_value *
static_cast<double>(a.coeff.value());
1311 const double contrib_b =
b.lp_value *
static_cast<double>(
b.coeff.value());
1312 return contrib_a < contrib_b;
1316struct LargeContribFirst {
1317 bool operator()(
const CutTerm& a,
const CutTerm& b)
const {
1318 const double contrib_a = a.lp_value *
static_cast<double>(a.coeff.value());
1319 const double contrib_b =
b.lp_value *
static_cast<double>(
b.coeff.value());
1320 return contrib_a > contrib_b;
1324struct LargeLpValueFirst {
1325 bool operator()(
const CutTerm& a,
const CutTerm& b)
const {
1326 if (a.lp_value ==
b.lp_value) {
1329 return a.coeff >
b.coeff;
1331 return a.lp_value >
b.lp_value;
1340 bool operator()(
const CutTerm& a,
const CutTerm& b)
const {
1341 const double contrib_a =
1342 a.LpDistToMaxValue() /
static_cast<double>(a.coeff.value());
1343 const double contrib_b =
1344 b.LpDistToMaxValue() /
static_cast<double>(
b.coeff.value());
1345 return contrib_a < contrib_b;
1348struct KnapsackRemove {
1349 bool operator()(
const CutTerm& a,
const CutTerm& b)
const {
1350 const double contrib_a =
1351 a.LpDistToMaxValue() /
static_cast<double>(a.coeff.value());
1352 const double contrib_b =
1353 b.LpDistToMaxValue() /
static_cast<double>(
b.coeff.value());
1354 return contrib_a > contrib_b;
1364template <
class Compare>
1365int CoverCutHelper::MinimizeCover(
int cover_size, absl::int128 slack) {
1367 absl::Span<CutTerm> terms = absl::MakeSpan(cut_.terms);
1368 std::sort(terms.begin(), terms.begin() + cover_size, Compare());
1369 for (
int i = 0;
i < cover_size;) {
1370 const CutTerm& t = terms[
i];
1371 const absl::int128 contrib =
1372 absl::int128(t.bound_diff.value()) * absl::int128(t.coeff.value());
1373 if (contrib < slack) {
1375 std::swap(terms[i], terms[--cover_size]);
1380 DCHECK_GT(cover_size, 0);
1384template <
class CompareAdd,
class CompareRemove>
1385int CoverCutHelper::GetCoverSize(
int relevant_size) {
1386 if (relevant_size == 0)
return 0;
1387 absl::Span<CutTerm> terms = absl::MakeSpan(cut_.terms);
1392 for (
int i = 0;
i < relevant_size;) {
1393 CutTerm& term = terms[
i];
1394 const double dist = term.LpDistToMaxValue();
1397 std::swap(term, terms[part1]);
1400 }
else if (term.lp_value > 1e-6) {
1406 std::swap(term, terms[relevant_size]);
1409 std::sort(terms.begin() + part1, terms.begin() + relevant_size, CompareAdd());
1412 DCHECK_GE(cut_.rhs, 0);
1413 absl::int128 max_shifted_activity = -cut_.rhs;
1414 absl::int128 shifted_round_up = -cut_.rhs;
1416 for (; cover_size < relevant_size; ++cover_size) {
1417 if (max_shifted_activity > 0)
break;
1418 const CutTerm& term = terms[cover_size];
1419 max_shifted_activity += absl::int128(term.coeff.value()) *
1420 absl::int128(term.bound_diff.value());
1421 shifted_round_up += absl::int128(term.coeff.value()) *
1422 std::min(absl::int128(term.bound_diff.value()),
1423 absl::int128(std::ceil(term.lp_value - 1e-6)));
1426 DCHECK_GE(cover_size, 0);
1427 if (shifted_round_up <= 0) {
1430 return MinimizeCover<CompareRemove>(cover_size, max_shifted_activity);
1435int CoverCutHelper::GetCoverSizeForBooleans() {
1436 absl::Span<CutTerm> terms = absl::MakeSpan(cut_.terms);
1443 int relevant_size = terms.size();
1444 int best_in_part3 = -1;
1445 const double threshold = 1.0 - 1.0 /
static_cast<double>(terms.size());
1446 for (
int i = 0;
i < relevant_size;) {
1447 const double lp_value = terms[
i].lp_value;
1450 if (terms[i].bound_diff > 1) {
1452 std::swap(terms[i], terms[relevant_size]);
1456 if (lp_value >= threshold) {
1458 std::swap(terms[i], terms[part1]);
1461 }
else if (lp_value > 0.5) {
1467 std::swap(terms[i], terms[relevant_size]);
1469 if (best_in_part3 == -1 ||
1470 LargeLpValueFirst()(terms[relevant_size], terms[best_in_part3])) {
1471 best_in_part3 = relevant_size;
1476 if (best_in_part3 != -1) {
1477 std::swap(terms[relevant_size], terms[best_in_part3]);
1482 std::sort(terms.begin() + part1, terms.begin() + relevant_size,
1483 LargeLpValueFirst());
1485 double activity = 0.0;
1486 int cover_size = relevant_size;
1487 absl::int128 slack = -cut_.rhs;
1488 for (
int i = 0;
i < relevant_size; ++
i) {
1489 const CutTerm& term = terms[
i];
1490 activity += term.LpDistToMaxValue();
1499 if (activity > 0.9999) {
1506 slack += absl::int128(term.coeff.value()) *
1507 absl::int128(term.bound_diff.value());
1520 if (cover_size == 0)
return 0;
1521 return MinimizeCover<LargeCoeffFirst>(cover_size, slack);
1524void CoverCutHelper::InitializeCut(
const CutData& input_ct) {
1530 CHECK_GE(cut_.rhs, 0);
1531 DCHECK(cut_.AllCoefficientsArePositive());
1536 InitializeCut(input_ct);
1540 if (ib_processor !=
nullptr) {
1541 std::vector<CutTerm>* new_bool_terms =
1543 for (
CutTerm& term : cut_.terms) {
1551 false, &term, &cut_.rhs, new_bool_terms)) {
1552 ++cover_stats_.num_initial_ibs;
1557 absl::MakeSpan(*new_bool_terms), IntegerValue(1), &cut_);
1560 bool has_relevant_int =
false;
1561 for (
const CutTerm& term : cut_.terms) {
1563 has_relevant_int =
true;
1568 const int base_size =
static_cast<int>(cut_.terms.size());
1569 const int cover_size =
1571 ? GetCoverSize<LargeContribFirst, LargeCoeffFirst>(base_size)
1572 : GetCoverSizeForBooleans();
1573 if (!has_relevant_int && ib_processor ==
nullptr) {
1577 has_bool_base_ct_ =
true;
1578 bool_cover_size_ = cover_size;
1579 if (cover_size == 0)
return false;
1580 bool_base_ct_ = cut_;
1582 if (cover_size == 0)
return false;
1599 IntegerValue best_coeff = 0;
1600 double best_score = -1.0;
1601 IntegerValue max_coeff_in_cover(0);
1602 for (
int i = 0;
i < cover_size; ++
i) {
1604 max_coeff_in_cover = std::max(max_coeff_in_cover, term.
coeff);
1605 const double score =
1608 if (score > best_score) {
1610 best_coeff = term.
coeff;
1614 CHECK_LT(cut_.rhs, 0);
1617 best_coeff = max_coeff_in_cover;
1620 std::function<IntegerValue(IntegerValue)> f;
1622 IntegerValue max_magnitude = 0;
1623 for (
const CutTerm& term : cut_.terms) {
1626 const IntegerValue max_scaling(std::min(
1633 if (ib_processor !=
nullptr) {
1634 const auto [num_lb, num_ub, num_merges] =
1636 cover_stats_.num_lb_ibs += num_lb;
1637 cover_stats_.num_ub_ibs += num_ub;
1638 cover_stats_.num_merges += num_merges;
1643 gub_helper_.ApplyWithPotentialBumpAndGUB(f, best_coeff, &cut_);
1644 cover_stats_.num_bumps += gub_helper_.last_num_bumps();
1645 cover_stats_.num_gubs += gub_helper_.last_num_gubs();
1646 cover_stats_.num_lifting += num_lifting_ = gub_helper_.last_num_lifts();
1647 ++cover_stats_.num_cuts;
1653 InitializeCut(input_ct);
1658 const int base_size =
static_cast<int>(cut_.terms.size());
1659 const int cover_size = GetCoverSize<KnapsackAdd, KnapsackRemove>(base_size);
1660 if (cover_size == 0)
return false;
1664 for (
int i = 0;
i < cover_size; ++
i) {
1665 cut_.terms[
i].Complement(&cut_.rhs);
1668 if (cut_.terms[
i].expr_coeffs[1] != 0)
return false;
1683 CHECK_LT(cut_.rhs, 0);
1684 if (cut_.rhs <= absl::int128(std::numeric_limits<int64_t>::min())) {
1688 bool has_large_coeff =
false;
1689 for (
const CutTerm& term : cut_.terms) {
1691 has_large_coeff =
true;
1699 const IntegerValue positive_rhs = -
static_cast<int64_t
>(cut_.rhs);
1701 for (
int i = 0;
i < cover_size; ++
i) {
1702 const IntegerValue magnitude =
IntTypeAbs(cut_.terms[
i].coeff);
1703 min_magnitude = std::min(min_magnitude, magnitude);
1705 const bool use_scaling =
1706 has_large_coeff || min_magnitude == 1 || min_magnitude >= positive_rhs;
1712 if (ib_processor !=
nullptr) {
1713 const auto [num_lb, num_ub, num_merges] =
1715 flow_stats_.num_lb_ibs += num_lb;
1716 flow_stats_.num_ub_ibs += num_ub;
1717 flow_stats_.num_merges += num_merges;
1721 IntegerValue period = positive_rhs;
1723 for (
const CutTerm& term : cut_.terms) {
1724 if (term.
coeff > 0)
continue;
1725 period = std::max(period, -term.
coeff);
1733 if (f(-period +
FloorRatio(period, 2)) != f(-period)) {
1737 CHECK_EQ(f(-period), f(-positive_rhs));
1738 period = std::max(period,
CapProdI(2, positive_rhs) - 1);
1745 gub_helper_.ApplyWithPotentialBumpAndGUB(f, period, &cut_);
1746 flow_stats_.num_bumps += gub_helper_.last_num_bumps();
1747 flow_stats_.num_gubs += gub_helper_.last_num_gubs();
1748 flow_stats_.num_lifting += gub_helper_.last_num_lifts();
1749 ++flow_stats_.num_cuts;
1756 if (has_bool_base_ct_) {
1759 CHECK(ib_processor ==
nullptr);
1760 cover_size = bool_cover_size_;
1761 if (cover_size == 0)
return false;
1762 InitializeCut(bool_base_ct_);
1764 InitializeCut(input_ct);
1770 if (ib_processor !=
nullptr) {
1771 std::vector<CutTerm>* new_bool_terms =
1773 for (
CutTerm& term : cut_.terms) {
1777 false, &term, &cut_.rhs, new_bool_terms)) {
1778 ++ls_stats_.num_initial_ibs;
1783 absl::MakeSpan(*new_bool_terms), IntegerValue(1), &cut_);
1787 cover_size = GetCoverSizeForBooleans();
1789 if (cover_size == 0)
return false;
1793 if (cut_.rhs > absl::int128(std::numeric_limits<int64_t>::max())) {
1794 ++ls_stats_.num_overflow_aborts;
1797 const IntegerValue rhs =
static_cast<int64_t
>(cut_.rhs);
1800 IntegerValue sum(0);
1801 std::vector<IntegerValue> cover_weights;
1802 for (
int i = 0;
i < cover_size; ++
i) {
1803 CHECK_EQ(cut_.terms[
i].bound_diff, 1);
1804 CHECK_GT(cut_.terms[
i].coeff, 0);
1805 cover_weights.push_back(cut_.terms[
i].coeff);
1806 sum =
CapAddI(sum, cut_.terms[
i].coeff);
1809 ++ls_stats_.num_overflow_aborts;
1818 IntegerValue previous_sum(0);
1819 std::sort(cover_weights.begin(), cover_weights.end());
1820 for (
int i = 0;
i < cover_size; ++
i) {
1821 q = IntegerValue(cover_weights.size() -
i);
1822 if (previous_sum + cover_weights[
i] * q > rhs) {
1823 p = rhs - previous_sum;
1826 previous_sum += cover_weights[
i];
1833 std::vector<IntegerValue> thresholds;
1834 for (
int i = 0;
i < q; ++
i) {
1836 if (
CapProd(p.value(),
i + 1) >= std::numeric_limits<int64_t>::max() - 1) {
1837 ++ls_stats_.num_overflow_aborts;
1840 thresholds.push_back(
CeilRatio(p * (
i + 1) + 1, q));
1844 std::reverse(cover_weights.begin(), cover_weights.end());
1845 for (
int i = q.value();
i < cover_size; ++
i) {
1846 thresholds.push_back(thresholds.back() + cover_weights[
i]);
1848 CHECK_EQ(thresholds.back(), rhs + 1);
1858 temp_cut_.rhs = cover_size - 1;
1859 temp_cut_.terms.clear();
1861 const int base_size =
static_cast<int>(cut_.terms.size());
1862 for (
int i = 0;
i < base_size; ++
i) {
1863 const CutTerm& term = cut_.terms[
i];
1864 const IntegerValue coeff = term.
coeff;
1865 IntegerValue cut_coeff(1);
1866 if (coeff < thresholds[0]) {
1867 if (
i >= cover_size)
continue;
1873 for (
int i = 1;
i < cover_size; ++
i) {
1874 if (coeff < thresholds[
i])
break;
1875 cut_coeff = IntegerValue(
i + 1);
1877 if (cut_coeff != 0 &&
i >= cover_size) ++ls_stats_.num_lifting;
1878 if (cut_coeff > 1 &&
i < cover_size) ++ls_stats_.num_lifting;
1881 temp_cut_.terms.push_back(term);
1882 temp_cut_.terms.back().coeff = cut_coeff;
1886 ++ls_stats_.num_cuts;
1891 if (!VLOG_IS_ON(1))
return;
1892 std::vector<std::pair<std::string, int64_t>> stats;
1893 stats.push_back({
"bool_rlt/num_tried", num_tried_});
1894 stats.push_back({
"bool_rlt/num_tried_factors", num_tried_factors_});
1895 shared_stats_->AddStats(stats);
1899 product_detector_->InitializeBooleanRLTCuts(lp_vars, *lp_values_);
1900 enabled_ = !product_detector_->BoolRLTCandidates().empty();
1905 if (!enabled_)
return false;
1912 absl::flat_hash_set<IntegerVariable> to_try_set;
1913 std::vector<IntegerVariable> to_try;
1930 filtered_input_.terms.clear();
1931 filtered_input_.rhs = input_ct.
rhs;
1933 const auto& candidates = product_detector_->BoolRLTCandidates();
1947 filtered_input_.rhs -= absl::int128(term.
coeff.value()) *
1958 for (
const IntegerVariable factor : candidates.at(
NegationOf(var))) {
1959 if (to_try_set.insert(factor).second) to_try.push_back(factor);
1962 filtered_input_.terms.push_back(term);
1966 double best_efficacy = 1e-3;
1968 for (
const IntegerVariable factor : to_try) {
1969 ++num_tried_factors_;
1970 if (!TryProduct(factor, filtered_input_))
continue;
1971 const double efficacy = cut_.ComputeEfficacy();
1972 if (efficacy > best_efficacy) {
1973 best_efficacy = efficacy;
1974 best_factor = factor;
1980 return TryProduct(best_factor, input_ct);
1989enum class LinearizationOption {
1998double BoolRLTCutHelper::GetLiteralLpValue(IntegerVariable var)
const {
1999 if (VariableIsPositive(var))
return (*lp_values_)[var];
2000 return 1.0 - (*lp_values_)[PositiveVariable(var)];
2003bool BoolRLTCutHelper::TryProduct(IntegerVariable factor,
2004 const CutData&
input) {
2007 absl::int128 old_rhs =
input.rhs;
2009 const double factor_lp = GetLiteralLpValue(factor);
2012 for (CutTerm term :
input.terms) {
2013 LinearizationOption best_option = LinearizationOption::DROP;
2016 const IntegerVariable var = term.GetUnderlyingLiteralOrNone();
2020 if (factor == var) {
2023 best_option = LinearizationOption::SQUARE;
2024 cut_.terms.push_back(term);
2031 best_option = LinearizationOption::DROP;
2041 double best_lp = 0.0;
2045 const double complement_lp =
2046 static_cast<double>(term.bound_diff.value()) - term.lp_value;
2047 const double mc_cormick_lp = factor_lp - complement_lp;
2048 if (mc_cormick_lp > best_lp) {
2049 best_option = LinearizationOption::MC_CORMICK;
2050 best_lp = mc_cormick_lp;
2058 const IntegerVariable ub_lit =
2059 product_detector_->LiteralProductUpperBound(factor,
NegationOf(var));
2060 if (ub_lit != kNoIntegerVariable) {
2061 const double lit_lp = GetLiteralLpValue(ub_lit);
2062 if (factor_lp - lit_lp > best_lp) {
2064 best_option = LinearizationOption::RLT;
2067 term.Complement(&old_rhs);
2070 term.lp_value = lit_lp;
2071 term.ReplaceExpressionByLiteral(ub_lit);
2072 cut_.terms.push_back(term);
2078 if (best_option == LinearizationOption::DROP)
continue;
2081 CHECK(best_option == LinearizationOption::MC_CORMICK);
2082 term.Complement(&old_rhs);
2083 cut_.terms.push_back(term);
2090 const absl::int128 limit(int64_t{1} << 50);
2091 if (old_rhs > limit || old_rhs < -limit)
return false;
2094 term.coeff = -IntegerValue(
static_cast<int64_t
>(old_rhs));
2095 term.lp_value = factor_lp;
2096 term.bound_diff = 1;
2097 term.ReplaceExpressionByLiteral(factor);
2098 cut_.terms.push_back(term);
2106 int linearization_level,
2116 result.
generate_cuts = [z, x, y, linearization_level, model, trail,
2118 if (trail->CurrentDecisionLevel() > 0 && linearization_level == 1) {
2127 if (x_lb == x_ub || y_lb == y_ub)
return true;
2132 const int64_t x_max_amp = std::max(std::abs(x_lb), std::abs(x_ub));
2133 const int64_t y_max_amp = std::max(std::abs(y_lb), std::abs(y_ub));
2134 constexpr int64_t kMaxSafeInteger = (int64_t{1} << 53) - 1;
2135 if (
CapProd(y_max_amp, x_max_amp) > kMaxSafeInteger)
return true;
2136 if (
CapProd(y_max_amp, std::abs(x.constant.value())) > kMaxSafeInteger) {
2139 if (
CapProd(x_max_amp, std::abs(y.
constant.value())) > kMaxSafeInteger) {
2143 const auto& lp_values = manager->LpValues();
2144 const double x_lp_value = x.LpValue(lp_values);
2145 const double y_lp_value = y.
LpValue(lp_values);
2146 const double z_lp_value = z.
LpValue(lp_values);
2154 auto try_add_above_cut = [&](int64_t x_coeff, int64_t y_coeff,
2156 if (-z_lp_value + x_lp_value * x_coeff + y_lp_value * y_coeff >=
2157 rhs + kMinCutViolation) {
2160 cut.
AddTerm(z, IntegerValue(-1));
2161 if (x_coeff != 0) cut.
AddTerm(x, IntegerValue(x_coeff));
2162 if (y_coeff != 0) cut.
AddTerm(y, IntegerValue(y_coeff));
2163 manager->AddCut(cut.
Build(),
"PositiveProduct");
2168 auto try_add_below_cut = [&](int64_t x_coeff, int64_t y_coeff,
2170 if (-z_lp_value + x_lp_value * x_coeff + y_lp_value * y_coeff <=
2171 rhs - kMinCutViolation) {
2174 cut.
AddTerm(z, IntegerValue(-1));
2175 if (x_coeff != 0) cut.
AddTerm(x, IntegerValue(x_coeff));
2176 if (y_coeff != 0) cut.
AddTerm(y, IntegerValue(y_coeff));
2177 manager->AddCut(cut.
Build(),
"PositiveProduct");
2188 try_add_above_cut(y_lb, x_lb, x_lb * y_lb);
2189 try_add_above_cut(y_ub, x_ub, x_ub * y_ub);
2190 try_add_below_cut(y_ub, x_lb, x_lb * y_ub);
2191 try_add_below_cut(y_lb, x_ub, x_ub * y_lb);
2201 IntegerValue x_ub,
Model* model) {
2202 const IntegerValue above_slope = x_ub + x_lb;
2205 above_hyperplan.
AddTerm(square, 1);
2206 above_hyperplan.
AddTerm(x, -above_slope);
2207 return above_hyperplan.
Build();
2212 IntegerValue x_value,
2214 const IntegerValue below_slope = 2 * x_value + 1;
2217 below_hyperplan.
AddTerm(square, 1);
2218 below_hyperplan.
AddTerm(x, -below_slope);
2219 return below_hyperplan.
Build();
2223 int linearization_level,
Model* model) {
2230 result.
generate_cuts = [y, x, linearization_level, trail, integer_trail,
2235 const IntegerValue x_ub = integer_trail->LevelZeroUpperBound(x);
2236 const IntegerValue x_lb = integer_trail->LevelZeroLowerBound(x);
2239 if (x_lb == x_ub)
return true;
2242 if (x_ub > (int64_t{1} << 31))
return true;
2247 const IntegerValue x_floor =
2248 static_cast<int64_t
>(std::floor(x.LpValue(manager->LpValues())));
2257ImpliedBoundsProcessor::BestImpliedBoundInfo
2259 auto it = cache_.find(var);
2260 if (it != cache_.end()) {
2270ImpliedBoundsProcessor::ComputeBestImpliedBound(
2271 IntegerVariable var,
2273 auto it = cache_.find(var);
2274 if (it != cache_.end())
return it->second;
2275 BestImpliedBoundInfo result;
2276 double result_slack_lp_value = std::numeric_limits<double>::infinity();
2277 const IntegerValue lb = integer_trail_->LevelZeroLowerBound(var);
2279 implied_bounds_->GetImpliedBounds(var)) {
2291 const IntegerValue diff = entry.lower_bound - lb;
2293 const double bool_lp_value =
2294 VariableIsPositive(entry.literal_view)
2295 ? lp_values[entry.literal_view]
2296 : 1.0 - lp_values[PositiveVariable(entry.literal_view)];
2297 const double slack_lp_value =
2298 lp_values[var] - ToDouble(lb) - bool_lp_value * ToDouble(diff);
2302 if (slack_lp_value < -1e-4) {
2303 LinearConstraint ib_cut;
2304 ib_cut.lb = kMinIntegerValue;
2305 std::vector<std::pair<IntegerVariable, IntegerValue>> terms;
2306 if (VariableIsPositive(entry.literal_view)) {
2308 terms.push_back({entry.literal_view, diff});
2309 terms.push_back({var, IntegerValue(-1)});
2314 terms.push_back({var, IntegerValue(-1)});
2315 ib_cut.ub = -entry.lower_bound;
2318 ib_cut_pool_.AddCut(std::move(ib_cut),
"IB", lp_values);
2324 if (slack_lp_value + 1e-4 < result_slack_lp_value ||
2325 (slack_lp_value < result_slack_lp_value + 1e-4 &&
2326 entry.lower_bound > result.implied_bound)) {
2327 result_slack_lp_value = slack_lp_value;
2328 result.var_lp_value = lp_values[var];
2329 result.bool_lp_value = bool_lp_value;
2330 result.implied_bound = entry.lower_bound;
2331 result.bool_var = entry.literal_view;
2334 cache_[var] = result;
2341 for (
const IntegerVariable var :
2342 implied_bounds_->VariablesWithImpliedBounds()) {
2344 ComputeBestImpliedBound(var, lp_values);
2353 if (!term.
IsSimple())
return false;
2378 if (bound_diff <= 0)
return false;
2421 absl::int128 unused = 0;
2435 CHECK_EQ(unused, absl::int128(0));
2440 const std::function<IntegerValue(IntegerValue)>& f, IntegerValue factor_t,
2442 int num_applied_lb = 0;
2443 int num_applied_ub = 0;
2458 bool expand =
false;
2466 base_score = AsDouble(f(bool_term.
coeff)) * bool_term.
lp_value +
2494 const double score =
2499 if (score > base_score + 1e-2) {
2501 term = ub_slack_term;
2502 tmp_terms_.push_back(ub_bool_term);
2510 tmp_terms_.push_back(bool_term);
2514 const int num_merges = cut_builder_.AddOrMergeBooleanTerms(
2515 absl::MakeSpan(tmp_terms_), factor_t, cut);
2517 return {num_applied_lb, num_applied_ub, num_merges};
2521 IntegerValue factor_t,
bool complement,
CutTerm* term, absl::int128* rhs,
2522 std::vector<CutTerm>* new_bool_terms) {
2544 new_bool_terms->push_back(bool_term);
2550 cached_data_.clear();
2552 const int size = cut->
terms.size();
2553 for (
int i = 0;
i < size; ++
i) {
2557 if (term.
expr_vars[0] >= first_slack)
continue;
2560 const IntegerVariable ib_var = term.
expr_coeffs[0] > 0
2565 cut->
terms[
i].cached_implied_lb = cached_data_.size();
2566 cached_data_.emplace_back(std::move(lb_info));
2571 cut->
terms[
i].cached_implied_ub = cached_data_.size();
2572 cached_data_.emplace_back(std::move(ub_info));
2576 return !cached_data_.empty();
2580 min_values_.clear();
2588 if (integer_trail.
IsFixed(expr)) {
2589 min_values_.insert(integer_trail.
FixedValue(expr));
2591 if (expr.
coeff > 0) {
2593 for (
const IntegerValue value :
2595 min_values_.insert(expr.
ValueAt(value));
2596 if (++count >= num_exprs)
break;
2600 for (
const IntegerValue value :
2602 min_values_.insert(-expr.
ValueAt(value));
2603 if (++count >= num_exprs)
break;
2611 IntegerValue sum = 0;
2612 for (
const IntegerValue value : min_values_) {
2614 if (++count >= expr_mins_.size())
return sum;
2620 std::sort(expr_mins_.begin(), expr_mins_.end());
2622 IntegerValue result = 0;
2623 for (
const IntegerValue value : expr_mins_) {
2625 tmp_value = std::max(tmp_value + 1, value);
2626 result += tmp_value;
2634 if (domain_bound > alldiff_bound) {
2636 return domain_bound;
2638 suffix = alldiff_bound > domain_bound ?
"a" :
"e";
2639 return alldiff_bound;
2644void TryToGenerateAllDiffCut(
2645 absl::Span<
const std::pair<double, AffineExpression>> sorted_exprs_lp,
2649 const int num_exprs = sorted_exprs_lp.size();
2651 std::vector<AffineExpression> current_set_exprs;
2657 for (
const auto& [expr_lp, expr] : sorted_exprs_lp) {
2659 diff_mins.
Add(expr, num_exprs, integer_trail);
2660 negated_diff_maxes.
Add(expr.Negated(), num_exprs, integer_trail);
2661 current_set_exprs.push_back(expr);
2662 CHECK_EQ(current_set_exprs.size(), diff_mins.
size());
2663 CHECK_EQ(current_set_exprs.size(), negated_diff_maxes.
size());
2664 std::string min_suffix;
2665 const IntegerValue required_min_sum =
2667 std::string max_suffix;
2668 const IntegerValue required_max_sum =
2670 if (required_max_sum == std::numeric_limits<IntegerValue>::max())
continue;
2671 DCHECK_LE(required_min_sum, required_max_sum);
2672 if (sum <
ToDouble(required_min_sum) - kMinCutViolation ||
2673 sum >
ToDouble(required_max_sum) + kMinCutViolation) {
2676 cut.AddTerm(expr, IntegerValue(1));
2678 top_n_cuts.
AddCut(cut.Build(),
2679 absl::StrCat(
"AllDiff_", min_suffix, max_suffix),
2685 current_set_exprs.clear();
2687 negated_diff_maxes.
Clear();
2695 absl::Span<const AffineExpression> exprs,
Model* model) {
2700 if (!integer_trail->
IsFixed(expr)) {
2701 result.
vars.push_back(expr.var);
2708 [exprs = std::vector<AffineExpression>(exprs.begin(), exprs.end()),
2714 const auto& lp_values = manager->LpValues();
2715 std::vector<std::pair<double, AffineExpression>> sorted_exprs;
2721 sorted_exprs.push_back(std::make_pair(expr.LpValue(lp_values), expr));
2725 std::sort(sorted_exprs.begin(), sorted_exprs.end(),
2726 [](std::pair<double, AffineExpression>& a,
2727 const std::pair<double, AffineExpression>&
b) {
2728 return a.first < b.first;
2730 TryToGenerateAllDiffCut(sorted_exprs, *integer_trail, lp_values,
2733 std::reverse(sorted_exprs.begin(), sorted_exprs.end());
2734 TryToGenerateAllDiffCut(sorted_exprs, *integer_trail, lp_values,
2739 VLOG(2) <<
"Created all_diff cut generator of size: " << exprs.size();
2745IntegerValue MaxCornerDifference(
const IntegerVariable var,
2746 const IntegerValue w1_i,
2747 const IntegerValue w2_i,
2748 const IntegerTrail& integer_trail) {
2749 const IntegerValue lb = integer_trail.LevelZeroLowerBound(var);
2750 const IntegerValue ub = integer_trail.LevelZeroUpperBound(var);
2751 return std::max((w2_i - w1_i) * lb, (w2_i - w1_i) * ub);
2760IntegerValue MPlusCoefficient(
2761 absl::Span<const IntegerVariable> x_vars,
2762 absl::Span<const LinearExpression> exprs,
2764 const int max_index,
const IntegerTrail& integer_trail) {
2765 IntegerValue coeff = exprs[max_index].offset;
2768 for (
const IntegerVariable var : x_vars) {
2769 const int target_index = variable_partition[var];
2770 if (max_index != target_index) {
2771 coeff += MaxCornerDifference(
2772 var, GetCoefficientOfPositiveVar(var, exprs[target_index]),
2773 GetCoefficientOfPositiveVar(var, exprs[max_index]), integer_trail);
2782double ComputeContribution(
2783 const IntegerVariable xi_var, absl::Span<const IntegerVariable> z_vars,
2784 absl::Span<const LinearExpression> exprs,
2785 const util_intops::StrongVector<IntegerVariable, double>& lp_values,
2786 const IntegerTrail& integer_trail,
const int target_index) {
2787 CHECK_GE(target_index, 0);
2788 CHECK_LT(target_index, exprs.size());
2789 const LinearExpression& target_expr = exprs[target_index];
2790 const double xi_value = lp_values[xi_var];
2792 double contrib =
ToDouble(wt_i) * xi_value;
2793 for (
int expr_index = 0; expr_index < exprs.size(); ++expr_index) {
2794 if (expr_index == target_index)
continue;
2795 const LinearExpression& max_expr = exprs[expr_index];
2796 const double z_max_value = lp_values[z_vars[expr_index]];
2797 const IntegerValue corner_value = MaxCornerDifference(
2800 contrib +=
ToDouble(corner_value) * z_max_value;
2807 absl::Span<const LinearExpression> exprs,
2808 absl::Span<const IntegerVariable> z_vars,
2811 std::vector<IntegerVariable> x_vars;
2812 result.
vars = {target};
2813 const int num_exprs = exprs.size();
2814 for (
int i = 0;
i < num_exprs; ++
i) {
2815 result.
vars.push_back(z_vars[
i]);
2816 x_vars.insert(x_vars.end(), exprs[
i].vars.begin(), exprs[
i].vars.end());
2820 DCHECK(std::all_of(x_vars.begin(), x_vars.end(), [](IntegerVariable var) {
2821 return VariableIsPositive(var);
2823 result.
vars.insert(result.
vars.end(), x_vars.begin(), x_vars.end());
2828 z_vars = std::vector<IntegerVariable>(z_vars.begin(), z_vars.end()),
2830 exprs = std::vector<LinearExpression>(exprs.begin(), exprs.end()),
2832 const auto& lp_values = manager->LpValues();
2834 lp_values.size(), -1);
2836 variable_partition_contrib(lp_values.size(),
2837 std::numeric_limits<double>::infinity());
2838 for (
int expr_index = 0; expr_index < num_exprs; ++expr_index) {
2839 for (
const IntegerVariable var : x_vars) {
2840 const double contribution = ComputeContribution(
2841 var, z_vars, exprs, lp_values, *integer_trail, expr_index);
2842 const double prev_contribution = variable_partition_contrib[var];
2843 if (contribution < prev_contribution) {
2844 variable_partition[var] = expr_index;
2845 variable_partition_contrib[var] = contribution;
2852 double violation = lp_values[target];
2853 cut.
AddTerm(target, IntegerValue(-1));
2855 for (
const IntegerVariable xi_var : x_vars) {
2856 const int input_index = variable_partition[xi_var];
2859 if (coeff != IntegerValue(0)) {
2862 violation -=
ToDouble(coeff) * lp_values[xi_var];
2864 for (
int expr_index = 0; expr_index < num_exprs; ++expr_index) {
2865 const IntegerVariable z_var = z_vars[expr_index];
2866 const IntegerValue z_coeff = MPlusCoefficient(
2867 x_vars, exprs, variable_partition, expr_index, *integer_trail);
2868 if (z_coeff != IntegerValue(0)) {
2871 violation -=
ToDouble(z_coeff) * lp_values[z_var];
2873 if (violation > 1e-2) {
2874 manager->AddCut(cut.
Build(),
"LinMax");
2883IntegerValue EvaluateMaxAffine(
2884 absl::Span<
const std::pair<IntegerValue, IntegerValue>> affines,
2886 IntegerValue y = kMinIntegerValue;
2887 for (
const auto& p : affines) {
2888 y = std::max(y, x * p.first + p.second);
2897 absl::Span<
const std::pair<IntegerValue, IntegerValue>> affines,
2901 const IntegerValue x_max = integer_trail->LevelZeroUpperBound(var);
2903 const IntegerValue y_at_min = EvaluateMaxAffine(affines, x_min);
2904 const IntegerValue y_at_max = EvaluateMaxAffine(affines, x_max);
2906 const IntegerValue delta_x = x_max - x_min;
2907 const IntegerValue delta_y = y_at_max - y_at_min;
2920 int64_t abs_magnitude = std::abs(target.
offset.value());
2921 for (
int i = 0;
i < target.
vars.size(); ++
i) {
2922 const IntegerVariable var = target.
vars[
i];
2923 const IntegerValue var_min = integer_trail->LevelZeroLowerBound(var);
2924 const IntegerValue var_max = integer_trail->LevelZeroUpperBound(var);
2926 CapProd(std::max(std::abs(var_min.value()), std::abs(var_max.value())),
2927 std::abs(target.
coeffs[
i].value())),
2937 builder->
AddTerm(var, -delta_y);
2941 VLOG(2) <<
"Linear constraint can cause overflow: " << builder->
Build();
2951 std::vector<std::pair<IntegerValue, IntegerValue>> affines,
2952 const std::string cut_name,
Model* model) {
2955 result.
vars.push_back(var);
2959 result.
generate_cuts = [target, var, affines, cut_name, integer_trail,
2961 if (integer_trail->
IsFixed(var))
return true;
2964 manager->AddCut(builder.
Build(), cut_name);
2972 absl::Span<const IntegerVariable> base_variables,
Model* model) {
2975 std::vector<IntegerVariable> variables;
2976 std::vector<Literal> literals;
2977 absl::flat_hash_map<LiteralIndex, IntegerVariable> positive_map;
2978 absl::flat_hash_map<LiteralIndex, IntegerVariable> negative_map;
2981 for (
const IntegerVariable var : base_variables) {
2982 if (integer_trail->LowerBound(var) != IntegerValue(0))
continue;
2983 if (integer_trail->UpperBound(var) != IntegerValue(1))
continue;
2984 const LiteralIndex literal_index = encoder->GetAssociatedLiteral(
2987 variables.push_back(var);
2988 literals.push_back(
Literal(literal_index));
2989 positive_map[literal_index] = var;
2994 result.
vars = variables;
2997 result.
generate_cuts = [variables, literals, implication_graph, positive_map,
3000 std::vector<double> packed_values;
3001 std::vector<double> packed_reduced_costs;
3002 const auto& lp_values = manager->LpValues();
3003 const auto& reduced_costs = manager->ReducedCosts();
3004 for (
int i = 0;
i < literals.size(); ++
i) {
3005 packed_values.push_back(lp_values[variables[
i]]);
3006 packed_reduced_costs.push_back(reduced_costs[variables[
i]]);
3008 const std::vector<std::vector<Literal>> at_most_ones =
3009 implication_graph->GenerateAtMostOnesWithLargeWeight(
3010 literals, packed_values, packed_reduced_costs);
3012 for (
const std::vector<Literal>& at_most_one : at_most_ones) {
3017 model, IntegerValue(std::numeric_limits<int64_t>::min()),
3019 for (
const Literal l : at_most_one) {
3020 if (positive_map.contains(l.Index())) {
3021 builder.
AddTerm(positive_map.at(l.Index()), IntegerValue(1));
3024 builder.
AddTerm(negative_map.at(l.Index()), IntegerValue(-1));
3029 manager->AddCut(builder.
Build(),
"Clique");
DomainIteratorBeginEnd Values() const &
bool TrySimpleSeparation(const CutData &input_ct)
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)
bool ConvertToLinearConstraint(const CutData &cut, LinearConstraint *output)
int AddOrMergeBooleanTerms(absl::Span< CutTerm > terms, IntegerValue t, CutData *cut)
void ApplyWithPotentialBumpAndGUB(const std::function< IntegerValue(IntegerValue)> &f, IntegerValue divisor, 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()
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)
IntegerValue FixedValue(IntegerVariable i) const
bool IsFixed(IntegerVariable i) const
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
const Domain & InitialVariableDomain(IntegerVariable var) const
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
void ResetBounds(IntegerValue lb, IntegerValue ub)
void AddTerm(IntegerVariable var, IntegerValue coeff)
void AddLinearExpression(const LinearExpression &expr)
void AddConstant(IntegerValue value)
LiteralIndex NegatedIndex() const
IntegerValue GetBestLowerBound(std::string &suffix)
void Add(const AffineExpression &expr, int num_expr, const IntegerTrail &integer_trail)
IntegerValue SumOfDifferentMins()
IntegerValue SumOfMinDomainValues()
void TransferToManager(LinearConstraintManager *manager)
void AddCut(LinearConstraint ct, absl::string_view name, const util_intops::StrongVector< IntegerVariable, double > &lp_solution)
int CurrentDecisionLevel() const
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
bool AddProductTo(IntegerValue a, IntegerValue b, IntegerValue *result)
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)
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)
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)
double ToDouble(IntegerValue value)
std::function< IntegerValue(IntegerValue)> GetSuperAdditiveRoundingFunction(IntegerValue rhs_remainder, IntegerValue divisor, IntegerValue t, IntegerValue max_scaling)
bool AtMinOrMaxInt64(int64_t x)
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
double LpValue(const util_intops::StrongVector< IntegerVariable, double > &lp_values) const
bool FillFromLinearConstraint(const LinearConstraint &base_ct, const util_intops::StrongVector< IntegerVariable, double > &lp_values, IntegerTrail *integer_trail)
bool AllCoefficientsArePositive() const
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
IntegerValue max_magnitude
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
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