[CP-SAT] rewrite cut massaging procedure (cover, flow, knapsack)
This commit is contained in:
@@ -47,7 +47,7 @@ namespace sat {
|
||||
|
||||
std::string CutTerm::DebugString() const {
|
||||
return absl::StrCat("coeff=", coeff.value(), " lp=", lp_value,
|
||||
" range=", bound_diff.value(), " expr_size=", expr_size);
|
||||
" range=", bound_diff.value());
|
||||
}
|
||||
|
||||
bool CutTerm::Complement(IntegerValue* rhs) {
|
||||
@@ -56,7 +56,7 @@ bool CutTerm::Complement(IntegerValue* rhs) {
|
||||
if (!AddProductTo(-coeff, bound_diff, rhs)) return false;
|
||||
|
||||
// We keep the same expression variable.
|
||||
for (int i = 0; i < expr_size; ++i) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
expr_coeffs[i] = -expr_coeffs[i];
|
||||
}
|
||||
expr_offset = bound_diff - expr_offset;
|
||||
@@ -93,8 +93,8 @@ bool CutData::AppendOneTerm(IntegerVariable var, IntegerValue coeff,
|
||||
if (bound_diff == 0) return true;
|
||||
|
||||
CutTerm entry;
|
||||
entry.expr_size = 1;
|
||||
entry.expr_vars[0] = var;
|
||||
entry.expr_coeffs[1] = 0;
|
||||
entry.bound_diff = bound_diff;
|
||||
if (complement) {
|
||||
// X = -(UB - X) + UB
|
||||
@@ -188,7 +188,7 @@ void CutDataBuilder::RegisterAllBooleansTerms(const CutData& cut) {
|
||||
for (int i = 0; i < size; ++i) {
|
||||
const CutTerm& term = cut.terms[i];
|
||||
if (term.bound_diff != 1) continue;
|
||||
if (term.expr_size != 1) continue;
|
||||
if (!term.IsSimple()) continue;
|
||||
if (term.expr_coeffs[0] > 0) {
|
||||
direct_index_[term.expr_vars[0]] = i;
|
||||
} else {
|
||||
@@ -199,7 +199,7 @@ void CutDataBuilder::RegisterAllBooleansTerms(const CutData& cut) {
|
||||
|
||||
void CutDataBuilder::AddOrMergeTerm(const CutTerm& term, IntegerValue t,
|
||||
CutData* cut) {
|
||||
DCHECK_EQ(term.expr_size, 1);
|
||||
DCHECK(term.IsSimple());
|
||||
const IntegerVariable var = term.expr_vars[0];
|
||||
const int new_index = cut->terms.size();
|
||||
const auto [it, inserted] =
|
||||
@@ -232,7 +232,8 @@ bool CutDataBuilder::ConvertToLinearConstraint(const CutData& cut,
|
||||
tmp_map_.clear();
|
||||
IntegerValue new_rhs = cut.rhs;
|
||||
for (const CutTerm& term : cut.terms) {
|
||||
for (int i = 0; i < term.expr_size; ++i) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
if (term.expr_coeffs[i] == 0) continue;
|
||||
if (!AddProductTo(term.coeff, term.expr_coeffs[i],
|
||||
&tmp_map_[term.expr_vars[i]])) {
|
||||
return false;
|
||||
@@ -389,8 +390,6 @@ IntegerRoundingCutHelper::~IntegerRoundingCutHelper() {
|
||||
stats.push_back({"rounding_cut/num_initial_ibs_", total_num_initial_ibs_});
|
||||
stats.push_back(
|
||||
{"rounding_cut/num_initial_merges_", total_num_initial_merges_});
|
||||
stats.push_back({"rounding_cut/num_initial_complements_",
|
||||
total_num_initial_complements_});
|
||||
stats.push_back({"rounding_cut/num_pos_lifts", total_num_pos_lifts_});
|
||||
stats.push_back({"rounding_cut/num_neg_lifts", total_num_neg_lifts_});
|
||||
stats.push_back(
|
||||
@@ -525,7 +524,7 @@ double IntegerRoundingCutHelper::GetScaledViolation(
|
||||
bool IntegerRoundingCutHelper::HasComplementedImpliedBound(
|
||||
const CutTerm& entry, ImpliedBoundsProcessor* ib_processor) {
|
||||
if (ib_processor == nullptr) return false;
|
||||
if (entry.expr_size != 1) return false;
|
||||
if (!entry.IsSimple()) return false;
|
||||
if (entry.bound_diff == 1) return false;
|
||||
const ImpliedBoundsProcessor::BestImpliedBoundInfo info =
|
||||
ib_processor->GetCachedImpliedBoundInfo(
|
||||
@@ -534,132 +533,48 @@ bool IntegerRoundingCutHelper::HasComplementedImpliedBound(
|
||||
return info.bool_var != kNoIntegerVariable;
|
||||
}
|
||||
|
||||
// Important: The cut_builder_ must have been reset.
|
||||
bool IntegerRoundingCutHelper::TryToExpandWithLowerImpliedbound(
|
||||
IntegerValue factor_t, int i, bool complement, CutData* cut,
|
||||
ImpliedBoundsProcessor* ib_processor) {
|
||||
CutTerm& term = cut->terms[i];
|
||||
|
||||
// We only want to expand non-Boolean and non-slack term!
|
||||
if (term.bound_diff <= 1) return false;
|
||||
if (term.expr_size != 1) return false;
|
||||
|
||||
// Try lower bounded direction for implied bound.
|
||||
// This kind should always be beneficial if it exists:
|
||||
//
|
||||
// Because X = bound_diff * B + S
|
||||
// We can replace coeff * X by the expression before applying f:
|
||||
// = f(coeff * bound_diff) * B + f(coeff) * [X - bound_diff * B]
|
||||
// = f(coeff) * X + (f(coeff * bound_diff) - f(coeff) * bound_diff] * B
|
||||
// So we can "lift" B into the cut with a non-negative coefficient.
|
||||
//
|
||||
// Note that this lifting is really the same as if we used that implied
|
||||
// bound before since in f(coeff * bound_diff) * B + f(coeff) * S, if we
|
||||
// replace S by its value [X - bound_diff * B] we get the same result.
|
||||
//
|
||||
// TODO(user): Ignore if bound_diff == 1 ? But we can still merge B with
|
||||
// another entry if it exists, so it can still be good in this case.
|
||||
//
|
||||
// TODO(user): Only do it if coeff_b > 0 ? But again we could still merge
|
||||
// B with an existing Boolean for a better cut even if coeff_b == 0.
|
||||
const IntegerVariable ib_var = term.expr_coeffs[0] > 0
|
||||
? term.expr_vars[0]
|
||||
: NegationOf(term.expr_vars[0]);
|
||||
const ImpliedBoundsProcessor::BestImpliedBoundInfo info =
|
||||
ib_processor->GetCachedImpliedBoundInfo(ib_var);
|
||||
const IntegerValue lb = -term.expr_offset;
|
||||
const IntegerValue bound_diff = info.implied_bound - lb;
|
||||
if (bound_diff <= 0) return false;
|
||||
if (info.bool_var == kNoIntegerVariable) return false;
|
||||
if (ProdOverflow(factor_t, CapProdI(term.coeff, bound_diff))) return false;
|
||||
|
||||
// We have X = info.diff * Boolean + slack.
|
||||
CutTerm bool_term;
|
||||
bool_term.coeff = term.coeff * bound_diff;
|
||||
bool_term.expr_size = 1;
|
||||
bool_term.expr_vars[0] = info.bool_var;
|
||||
bool_term.bound_diff = IntegerValue(1);
|
||||
bool_term.lp_value = info.bool_lp_value;
|
||||
if (info.is_positive) {
|
||||
bool_term.expr_coeffs[0] = IntegerValue(1);
|
||||
bool_term.expr_offset = IntegerValue(0);
|
||||
} else {
|
||||
bool_term.expr_coeffs[0] = IntegerValue(-1);
|
||||
bool_term.expr_offset = IntegerValue(1);
|
||||
}
|
||||
|
||||
// Create slack.
|
||||
// The expression is term.exp - bound_diff * bool_term
|
||||
// The variable shouldn't be the same.
|
||||
DCHECK_EQ(term.expr_size, 1);
|
||||
DCHECK_EQ(bool_term.expr_size, 1);
|
||||
DCHECK_NE(term.expr_vars[0], bool_term.expr_vars[0]);
|
||||
CutTerm slack_term;
|
||||
slack_term.expr_size = 2;
|
||||
slack_term.expr_vars[0] = term.expr_vars[0];
|
||||
slack_term.expr_coeffs[0] = term.expr_coeffs[0];
|
||||
slack_term.expr_vars[1] = bool_term.expr_vars[0];
|
||||
slack_term.expr_coeffs[1] = -bound_diff * bool_term.expr_coeffs[0];
|
||||
slack_term.expr_offset =
|
||||
term.expr_offset - bound_diff * bool_term.expr_offset;
|
||||
|
||||
slack_term.lp_value = info.SlackLpValue(lb);
|
||||
slack_term.coeff = term.coeff;
|
||||
slack_term.bound_diff = term.bound_diff;
|
||||
|
||||
// It should be good to use IB, but sometime we have things like
|
||||
// 7.3 = 2 * bool@1 + 5.3 and the expanded Boolean is at its upper bound.
|
||||
// It is always good to complement such variable.
|
||||
//
|
||||
// Note that here we do more and just complement anything closer to UB.
|
||||
//
|
||||
// TODO(user): Because of merges, we might have entry with a coefficient of
|
||||
// zero than are not useful. Remove them.
|
||||
if (complement && bool_term.lp_value > 0.5) {
|
||||
if (bool_term.Complement(&cut->rhs)) ++total_num_initial_complements_;
|
||||
}
|
||||
|
||||
term = slack_term;
|
||||
cut_builder_.AddOrMergeTerm(bool_term, factor_t, cut);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO(user): This is slow, 50% of run time on a2c1s1.pb.gz. Optimize!
|
||||
bool IntegerRoundingCutHelper::ComputeCut(
|
||||
RoundingOptions options, const CutData& base_ct,
|
||||
ImpliedBoundsProcessor* ib_processor) {
|
||||
bool all_booleans = true;
|
||||
bool some_relevant_positions = false;
|
||||
for (const CutTerm& term : base_ct.terms) {
|
||||
if (term.bound_diff != 1) all_booleans = false;
|
||||
if (term.HasRelevantLpValue()) some_relevant_positions = true;
|
||||
}
|
||||
|
||||
// TODO(user): Maybe this shouldn't be called on such constraint.
|
||||
if (!some_relevant_positions) {
|
||||
VLOG(2) << "Issue, nothing to cut.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// No need to process any implied bounds if only Booleans are involved.
|
||||
if (all_booleans) ib_processor = nullptr;
|
||||
|
||||
// Try IB before heuristic?
|
||||
// This should be better except it can mess up the norm and the divisors.
|
||||
best_cut_ = base_ct;
|
||||
if (options.use_ib_before_heuristic && ib_processor != nullptr) {
|
||||
cut_builder_.RegisterAllBooleansTerms(best_cut_);
|
||||
const int old_size = static_cast<int>(best_cut_.terms.size());
|
||||
bool abort = true;
|
||||
for (int i = 0; i < old_size; ++i) {
|
||||
if (best_cut_.terms[i].bound_diff <= 1) continue;
|
||||
if (!best_cut_.terms[i].HasRelevantLpValue()) continue;
|
||||
if (TryToExpandWithLowerImpliedbound(IntegerValue(1), i,
|
||||
/*complement=*/true, &best_cut_,
|
||||
ib_processor)) {
|
||||
|
||||
if (options.prefer_positive_ib && best_cut_.terms[i].coeff < 0) {
|
||||
// We complement the term before trying the implied bound.
|
||||
if (best_cut_.terms[i].Complement(&best_cut_.rhs)) {
|
||||
if (ib_processor->TryToExpandWithLowerImpliedbound(
|
||||
IntegerValue(1), i,
|
||||
/*complement=*/true, &best_cut_, &cut_builder_)) {
|
||||
++total_num_initial_ibs_;
|
||||
abort = false;
|
||||
continue;
|
||||
}
|
||||
best_cut_.terms[i].Complement(&best_cut_.rhs);
|
||||
}
|
||||
}
|
||||
|
||||
if (ib_processor->TryToExpandWithLowerImpliedbound(
|
||||
IntegerValue(1), i,
|
||||
/*complement=*/true, &best_cut_, &cut_builder_)) {
|
||||
abort = false;
|
||||
++total_num_initial_ibs_;
|
||||
}
|
||||
}
|
||||
total_num_initial_merges_ += cut_builder_.NumMergesSinceLastClear();
|
||||
|
||||
// TODO(user): We assume that this is called with and without the option
|
||||
// use_ib_before_heuristic, so that we can abort if no IB has been applied
|
||||
// since then we will redo the computation. This is not really clean.
|
||||
if (abort) return false;
|
||||
}
|
||||
|
||||
// Our heuristic will try to generate a few different cuts, and we will keep
|
||||
@@ -840,10 +755,10 @@ bool IntegerRoundingCutHelper::ComputeCut(
|
||||
|
||||
// We only want to expand non-Boolean and non-slack term!
|
||||
if (term.bound_diff <= 1) continue;
|
||||
if (term.expr_size != 1) continue;
|
||||
if (!term.IsSimple()) continue;
|
||||
|
||||
if (TryToExpandWithLowerImpliedbound(factor_t, i, /*complement=*/false,
|
||||
&best_cut_, ib_processor)) {
|
||||
if (ib_processor->TryToExpandWithLowerImpliedbound(
|
||||
factor_t, i, /*complement=*/false, &best_cut_, &cut_builder_)) {
|
||||
++num_ib_used_;
|
||||
++total_num_pos_lifts_;
|
||||
continue;
|
||||
@@ -894,8 +809,8 @@ bool IntegerRoundingCutHelper::ComputeCut(
|
||||
// We reverse the is_positive meaning here since we have (1 - B).
|
||||
CutTerm bool_term;
|
||||
bool_term.coeff = bound_diff * term.coeff;
|
||||
bool_term.expr_size = 1;
|
||||
bool_term.expr_vars[0] = info.bool_var;
|
||||
bool_term.expr_coeffs[1] = 0;
|
||||
bool_term.bound_diff = IntegerValue(1);
|
||||
bool_term.lp_value = 1.0 - info.bool_lp_value;
|
||||
if (!info.is_positive) {
|
||||
@@ -909,7 +824,6 @@ bool IntegerRoundingCutHelper::ComputeCut(
|
||||
// Create the slack term in X = diff * (1 - B) - S
|
||||
CutTerm slack_term;
|
||||
slack_term.coeff = -term.coeff;
|
||||
slack_term.expr_size = 2;
|
||||
slack_term.expr_vars[0] = term.expr_vars[0];
|
||||
slack_term.expr_coeffs[0] = -term.expr_coeffs[0];
|
||||
slack_term.expr_vars[1] = bool_term.expr_vars[0];
|
||||
@@ -1018,45 +932,33 @@ CoverCutHelper::~CoverCutHelper() {
|
||||
std::vector<std::pair<std::string, int64_t>> stats;
|
||||
stats.push_back({"cover_cut/num_overflows", total_num_overflow_abort_});
|
||||
stats.push_back({"cover_cut/num_lifting", total_num_lifting_});
|
||||
stats.push_back({"cover_cut/num_cover_not_bool", total_num_cover_not_bool_});
|
||||
stats.push_back({"cover_cut/num_implied_bounds", total_num_ibs_});
|
||||
shared_stats_->AddStats(stats);
|
||||
}
|
||||
|
||||
bool CoverCutHelper::TrySimpleKnapsack(const CutData& input) {
|
||||
cut_.Clear();
|
||||
alt_cut_.Clear();
|
||||
|
||||
base_ct_ = input;
|
||||
if (base_ct_.rhs < 0) return false;
|
||||
|
||||
// Now make each coeff positive.
|
||||
//
|
||||
// TODO(user): maybe we should do it all at once to avoid some overflow
|
||||
// condition.
|
||||
for (CutTerm& term : base_ct_.terms) {
|
||||
if (term.coeff >= 0) continue;
|
||||
if (!term.Complement(&base_ct_.rhs)) {
|
||||
++total_num_overflow_abort_;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Try a simple cover heuristic.
|
||||
// Look for violated CUT of the form: sum (UB - X) or (X - LB) >= 1.
|
||||
std::sort(base_ct_.terms.begin(), base_ct_.terms.end(),
|
||||
// Try a simple cover heuristic.
|
||||
// Look for violated CUT of the form: sum (UB - X) or (X - LB) >= 1.
|
||||
int CoverCutHelper::GetCoverSize(int relevant_size, IntegerValue* rhs) {
|
||||
relevant_size =
|
||||
std::partition(
|
||||
base_ct_.terms.begin(), base_ct_.terms.begin() + relevant_size,
|
||||
[](const CutTerm& t) { return t.LpDistToMaxValue() < 0.9999; }) -
|
||||
base_ct_.terms.begin();
|
||||
std::sort(base_ct_.terms.begin(), base_ct_.terms.begin() + relevant_size,
|
||||
[](const CutTerm& a, const CutTerm& b) {
|
||||
if (a.LpDistToMaxValue() == b.LpDistToMaxValue()) {
|
||||
const double dist_a = a.LpDistToMaxValue();
|
||||
const double dist_b = b.LpDistToMaxValue();
|
||||
if (dist_a == dist_b) {
|
||||
// Prefer low coefficients if the distance is the same.
|
||||
return a.coeff < b.coeff;
|
||||
}
|
||||
return a.LpDistToMaxValue() < b.LpDistToMaxValue();
|
||||
return dist_a < dist_b;
|
||||
});
|
||||
|
||||
double activity = 0.0;
|
||||
int cover_size = 0;
|
||||
IntegerValue rhs = base_ct_.rhs;
|
||||
const int base_size = base_ct_.terms.size();
|
||||
for (int i = 0; i < base_size; ++i) {
|
||||
int cover_size = relevant_size;
|
||||
*rhs = base_ct_.rhs;
|
||||
for (int i = 0; i < relevant_size; ++i) {
|
||||
const CutTerm& term = base_ct_.terms[i];
|
||||
activity += term.LpDistToMaxValue();
|
||||
|
||||
@@ -1072,11 +974,11 @@ bool CoverCutHelper::TrySimpleKnapsack(const CutData& input) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!AddProductTo(-term.coeff, term.bound_diff, &rhs)) {
|
||||
if (!AddProductTo(-term.coeff, term.bound_diff, rhs)) {
|
||||
// Abort early if we run into overflow.
|
||||
// In that case, rhs must be negative, and we can try this cover still.
|
||||
cover_size = i;
|
||||
CHECK_LT(rhs, 0);
|
||||
CHECK_LT(*rhs, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1088,15 +990,13 @@ bool CoverCutHelper::TrySimpleKnapsack(const CutData& input) {
|
||||
// possible violation. Note also that we lift as much as possible, so we don't
|
||||
// necessarily optimize for the cut efficacity though. But we do get a
|
||||
// stronger cut.
|
||||
if (rhs >= 0) return false;
|
||||
if (cover_size == 0) return false;
|
||||
if (*rhs >= 0) return 0;
|
||||
if (cover_size == 0) return 0;
|
||||
|
||||
// Transform to a minimal cover. We want to greedily remove the largest coeff
|
||||
// first, so we have more chance for the "lifting" below which can increase
|
||||
// the cut violation. If the coeff are the same, we prefer to remove high
|
||||
// distance from upper bound first.
|
||||
//
|
||||
// We compute the cut at the same time.
|
||||
std::sort(base_ct_.terms.begin(), base_ct_.terms.begin() + cover_size,
|
||||
[](const CutTerm& a, const CutTerm& b) {
|
||||
if (a.coeff == b.coeff) {
|
||||
@@ -1106,13 +1006,63 @@ bool CoverCutHelper::TrySimpleKnapsack(const CutData& input) {
|
||||
});
|
||||
for (int i = 0; i < cover_size; ++i) {
|
||||
const CutTerm& t = base_ct_.terms[i];
|
||||
if (t.bound_diff * t.coeff + rhs >= 0) continue;
|
||||
rhs += t.bound_diff * t.coeff;
|
||||
if (t.bound_diff * t.coeff + *rhs >= 0) continue;
|
||||
*rhs += t.bound_diff * t.coeff;
|
||||
std::swap(base_ct_.terms[i], base_ct_.terms[--cover_size]);
|
||||
}
|
||||
CHECK_GT(cover_size, 0);
|
||||
|
||||
// Generate alternative cut.
|
||||
GenerateLetchfordSouliLifting(base_ct_, cover_size);
|
||||
return cover_size;
|
||||
}
|
||||
|
||||
bool CoverCutHelper::MakeAllTermsPositive() {
|
||||
// Make sure each coeff is positive.
|
||||
//
|
||||
// TODO(user): maybe we should do it all at once to avoid some overflow
|
||||
// condition.
|
||||
for (CutTerm& term : base_ct_.terms) {
|
||||
if (term.coeff >= 0) continue;
|
||||
if (!term.Complement(&base_ct_.rhs)) {
|
||||
++total_num_overflow_abort_;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// We should have aborted early if the base constraint was already infeasible.
|
||||
CHECK_GE(base_ct_.rhs, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CoverCutHelper::TrySimpleKnapsack(const CutData& input,
|
||||
ImpliedBoundsProcessor* ib_processor) {
|
||||
cut_.Clear();
|
||||
base_ct_ = input;
|
||||
if (!MakeAllTermsPositive()) return false;
|
||||
|
||||
if (ib_processor != nullptr) {
|
||||
cut_builder_.RegisterAllBooleansTerms(base_ct_);
|
||||
const int old_size = static_cast<int>(base_ct_.terms.size());
|
||||
for (int i = 0; i < old_size; ++i) {
|
||||
// We only look at non-Boolean with an lp value not close to the upper
|
||||
// bound.
|
||||
const CutTerm& term = base_ct_.terms[i];
|
||||
if (term.bound_diff <= 1) continue;
|
||||
if (term.lp_value + 1e-4 > static_cast<double>(term.bound_diff.value())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ib_processor->TryToExpandWithLowerImpliedbound(
|
||||
IntegerValue(1), i,
|
||||
/*complement=*/false, &base_ct_, &cut_builder_)) {
|
||||
++total_num_ibs_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IntegerValue rhs;
|
||||
const int base_size = static_cast<int>(base_ct_.terms.size());
|
||||
const int cover_size = GetCoverSize(base_size, &rhs);
|
||||
if (cover_size == 0) return false;
|
||||
|
||||
// The cut is just that the sum of variable cannot be at their max value.
|
||||
base_ct_.rhs = IntegerValue(-1);
|
||||
@@ -1193,29 +1143,54 @@ bool CoverCutHelper::TrySimpleKnapsack(const CutData& input) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CoverCutHelper::GenerateLetchfordSouliLifting(const CutData& base_ct,
|
||||
int cover_size) {
|
||||
alt_cut_.Clear();
|
||||
bool CoverCutHelper::TryWithLetchfordSouliLifting(
|
||||
const CutData& input, ImpliedBoundsProcessor* ib_processor) {
|
||||
cut_.Clear();
|
||||
base_ct_ = input;
|
||||
if (!MakeAllTermsPositive()) return false;
|
||||
|
||||
// Perform IB expansion with no restriction, all coeff should still be
|
||||
// positive.
|
||||
//
|
||||
// TODO(user): Merge Boolean terms that are complement of each other.
|
||||
if (ib_processor != nullptr) {
|
||||
cut_builder_.RegisterAllBooleansTerms(base_ct_);
|
||||
const int old_size = static_cast<int>(base_ct_.terms.size());
|
||||
for (int i = 0; i < old_size; ++i) {
|
||||
if (base_ct_.terms[i].bound_diff <= 1) continue;
|
||||
if (ib_processor->TryToExpandWithLowerImpliedbound(
|
||||
IntegerValue(1), i,
|
||||
/*complement=*/false, &base_ct_, &cut_builder_)) {
|
||||
++total_num_ibs_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(user): we currently only deal with Boolean in the cover. Fix.
|
||||
const int num_bools =
|
||||
std::partition(base_ct_.terms.begin(), base_ct_.terms.end(),
|
||||
[](const CutTerm& t) { return t.bound_diff == 1; }) -
|
||||
base_ct_.terms.begin();
|
||||
if (num_bools == 0) return false;
|
||||
|
||||
IntegerValue rhs;
|
||||
const int cover_size = GetCoverSize(num_bools, &rhs);
|
||||
if (cover_size == 0) return false;
|
||||
|
||||
// Collect the weight in the cover.
|
||||
IntegerValue sum(0);
|
||||
std::vector<IntegerValue> cover_weights;
|
||||
const int base_size = base_ct.terms.size();
|
||||
for (int i = 0; i < cover_size; ++i) {
|
||||
// TODO(user): we currently only deal with Boolean in the cover. Fix.
|
||||
if (base_ct.terms[i].bound_diff != 1) {
|
||||
++total_num_cover_not_bool_;
|
||||
return false;
|
||||
}
|
||||
|
||||
cover_weights.push_back(base_ct.terms[i].coeff);
|
||||
sum = CapAddI(sum, base_ct.terms[i].coeff);
|
||||
CHECK_EQ(base_ct_.terms[i].bound_diff, 1);
|
||||
CHECK_GT(base_ct_.terms[i].coeff, 0);
|
||||
cover_weights.push_back(base_ct_.terms[i].coeff);
|
||||
sum = CapAddI(sum, base_ct_.terms[i].coeff);
|
||||
}
|
||||
if (AtMinOrMaxInt64(sum.value())) {
|
||||
++total_num_overflow_abort_;
|
||||
return false;
|
||||
}
|
||||
CHECK_GT(sum, base_ct.rhs);
|
||||
CHECK_GT(sum, base_ct_.rhs);
|
||||
|
||||
// Compute the correct threshold so that if we round down larger weights to
|
||||
// p/q. We have sum of the weight in cover == base_rhs.
|
||||
@@ -1225,13 +1200,13 @@ bool CoverCutHelper::GenerateLetchfordSouliLifting(const CutData& base_ct,
|
||||
std::sort(cover_weights.begin(), cover_weights.end());
|
||||
for (int i = 0; i < cover_size; ++i) {
|
||||
q = IntegerValue(cover_weights.size() - i);
|
||||
if (previous_sum + cover_weights[i] * q > base_ct.rhs) {
|
||||
p = base_ct.rhs - previous_sum;
|
||||
if (previous_sum + cover_weights[i] * q > base_ct_.rhs) {
|
||||
p = base_ct_.rhs - previous_sum;
|
||||
break;
|
||||
}
|
||||
previous_sum += cover_weights[i];
|
||||
}
|
||||
CHECK_GE(q, 1) << cover_size << " " << base_size;
|
||||
CHECK_GE(q, 1);
|
||||
|
||||
// Compute thresholds.
|
||||
// For the first q values, thresholds[i] is the smallest integer such that
|
||||
@@ -1251,7 +1226,7 @@ bool CoverCutHelper::GenerateLetchfordSouliLifting(const CutData& base_ct,
|
||||
for (int i = q.value(); i < cover_size; ++i) {
|
||||
thresholds.push_back(thresholds.back() + cover_weights[i]);
|
||||
}
|
||||
CHECK_EQ(thresholds.back(), base_ct.rhs + 1);
|
||||
CHECK_EQ(thresholds.back(), base_ct_.rhs + 1);
|
||||
|
||||
// Generate the cut.
|
||||
//
|
||||
@@ -1264,8 +1239,10 @@ bool CoverCutHelper::GenerateLetchfordSouliLifting(const CutData& base_ct,
|
||||
temp_cut_.rhs = IntegerValue(cover_size - 1);
|
||||
temp_cut_.terms.clear();
|
||||
|
||||
num_lifting_ = 0;
|
||||
const int base_size = static_cast<int>(base_ct_.terms.size());
|
||||
for (int i = 0; i < base_size; ++i) {
|
||||
const CutTerm& term = base_ct.terms[i];
|
||||
const CutTerm& term = base_ct_.terms[i];
|
||||
const IntegerValue coeff = term.coeff;
|
||||
IntegerValue cut_coeff(1);
|
||||
if (coeff < thresholds[0]) {
|
||||
@@ -1279,13 +1256,15 @@ bool CoverCutHelper::GenerateLetchfordSouliLifting(const CutData& base_ct,
|
||||
if (coeff < thresholds[i]) break;
|
||||
cut_coeff = IntegerValue(i + 1);
|
||||
}
|
||||
if (cut_coeff != 0 && i >= cover_size) ++num_lifting_;
|
||||
if (cut_coeff > 1 && i < cover_size) ++num_lifting_; // happen?
|
||||
}
|
||||
|
||||
temp_cut_.terms.push_back(term);
|
||||
temp_cut_.terms.back().coeff = cut_coeff;
|
||||
}
|
||||
if (!cut_builder_.ConvertToLinearConstraint(temp_cut_, &alt_cut_)) {
|
||||
alt_cut_.Clear();
|
||||
if (!cut_builder_.ConvertToLinearConstraint(temp_cut_, &cut_)) {
|
||||
cut_.Clear();
|
||||
++total_num_overflow_abort_;
|
||||
return false;
|
||||
}
|
||||
@@ -1451,14 +1430,6 @@ CutGenerator CreateSquareCutGenerator(AffineExpression y, AffineExpression x,
|
||||
return result;
|
||||
}
|
||||
|
||||
void ImpliedBoundsProcessor::ProcessUpperBoundedConstraint(
|
||||
const absl::StrongVector<IntegerVariable, double>& lp_values,
|
||||
LinearConstraint* cut) {
|
||||
ProcessUpperBoundedConstraintWithSlackCreation(
|
||||
/*substitute_only_inner_variables=*/false, IntegerVariable(0), lp_values,
|
||||
cut, nullptr);
|
||||
}
|
||||
|
||||
ImpliedBoundsProcessor::BestImpliedBoundInfo
|
||||
ImpliedBoundsProcessor::GetCachedImpliedBoundInfo(IntegerVariable var) const {
|
||||
auto it = cache_.find(var);
|
||||
@@ -1550,136 +1521,98 @@ void ImpliedBoundsProcessor::RecomputeCacheAndSeparateSomeImpliedBoundCuts(
|
||||
}
|
||||
}
|
||||
|
||||
void ImpliedBoundsProcessor::ProcessUpperBoundedConstraintWithSlackCreation(
|
||||
bool substitute_only_inner_variables, IntegerVariable first_slack,
|
||||
const absl::StrongVector<IntegerVariable, double>& lp_values,
|
||||
LinearConstraint* cut, std::vector<SlackInfo>* slack_infos) {
|
||||
if (cache_.empty()) return; // Nothing to do.
|
||||
tmp_terms_.clear();
|
||||
IntegerValue new_ub = cut->ub;
|
||||
bool changed = false;
|
||||
// Important: The cut_builder_ must have been reset.
|
||||
bool ImpliedBoundsProcessor::TryToExpandWithLowerImpliedbound(
|
||||
IntegerValue factor_t, int i, bool complement, CutData* cut,
|
||||
CutDataBuilder* builder) {
|
||||
CutTerm& term = cut->terms[i];
|
||||
|
||||
// TODO(user): we could relax a bit this test.
|
||||
int64_t overflow_detection = 0;
|
||||
// We only want to expand non-Boolean and non-slack term!
|
||||
if (term.bound_diff <= 1) return false;
|
||||
if (!term.IsSimple()) return false;
|
||||
CHECK_EQ(IntTypeAbs(term.expr_coeffs[0]), 1);
|
||||
|
||||
const int size = cut->vars.size();
|
||||
for (int i = 0; i < size; ++i) {
|
||||
// Note that the caller is supposed to choose between var/NegationOf(var)
|
||||
// by changing the cut.
|
||||
IntegerVariable var = cut->vars[i];
|
||||
IntegerValue coeff = cut->coeffs[i];
|
||||
|
||||
// Find the best implied bound to use.
|
||||
// TODO(user): We could also use implied upper bound, that is try with
|
||||
// NegationOf(var).
|
||||
const BestImpliedBoundInfo info = GetCachedImpliedBoundInfo(var);
|
||||
const int old_size = tmp_terms_.size();
|
||||
const IntegerValue lb = integer_trail_->LevelZeroLowerBound(var);
|
||||
const IntegerValue bound_diff = info.implied_bound - lb;
|
||||
const double slack_lp_value = info.SlackLpValue(lb);
|
||||
|
||||
// Shall we keep the original term ?
|
||||
bool keep_term = false;
|
||||
if (bound_diff <= 0) keep_term = true;
|
||||
if (info.bool_var == kNoIntegerVariable) {
|
||||
keep_term = true;
|
||||
} else {
|
||||
if (integer_trail_->IsFixed(info.bool_var)) keep_term = true;
|
||||
if (CapProd(std::abs(coeff.value()), bound_diff.value()) ==
|
||||
std::numeric_limits<int64_t>::max()) {
|
||||
keep_term = true;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(user): On some problem, not replacing the variable at their bound
|
||||
// by an implied bounds seems beneficial. This is especially the case on
|
||||
// g200x740.mps.gz
|
||||
//
|
||||
// Note that in ComputeCut() the variable with an LP value at the bound do
|
||||
// not contribute to the cut efficacity (no loss) but do contribute to the
|
||||
// various heuristic based on the coefficient magnitude.
|
||||
if (substitute_only_inner_variables) {
|
||||
const IntegerValue ub = integer_trail_->LevelZeroUpperBound(var);
|
||||
if (lp_values[var] - ToDouble(lb) < 1e-2) keep_term = true;
|
||||
if (ToDouble(ub) - lp_values[var] < 1e-2) keep_term = true;
|
||||
}
|
||||
|
||||
// This is when we do not add slack.
|
||||
if (slack_infos == nullptr) {
|
||||
// We do not want to loose anything, so we only replace if the slack lp is
|
||||
// zero.
|
||||
if (slack_lp_value > 1e-6) keep_term = true;
|
||||
}
|
||||
|
||||
if (keep_term) {
|
||||
tmp_terms_.push_back({var, coeff});
|
||||
} else {
|
||||
// Substitute.
|
||||
SlackInfo slack_info;
|
||||
slack_info.lp_value = slack_lp_value;
|
||||
slack_info.lb = 0;
|
||||
slack_info.ub = integer_trail_->LevelZeroUpperBound(var) - lb;
|
||||
|
||||
if (info.is_positive) {
|
||||
// X = Indicator * diff + lb + Slack
|
||||
tmp_terms_.push_back({info.bool_var, coeff * bound_diff});
|
||||
if (!AddProductTo(-coeff, lb, &new_ub)) {
|
||||
VLOG(2) << "Overflow";
|
||||
return;
|
||||
}
|
||||
if (slack_infos != nullptr) {
|
||||
tmp_terms_.push_back({first_slack, coeff});
|
||||
first_slack += 2;
|
||||
|
||||
// slack = X - Indicator * bound_diff - lb;
|
||||
slack_info.terms.push_back({var, IntegerValue(1)});
|
||||
slack_info.terms.push_back({info.bool_var, -bound_diff});
|
||||
slack_info.offset = -lb;
|
||||
slack_infos->push_back(slack_info);
|
||||
}
|
||||
} else {
|
||||
// X = (1 - Indicator) * (diff) + lb + Slack
|
||||
// X = -Indicator * (diff) + lb + diff + Slack
|
||||
tmp_terms_.push_back({info.bool_var, -coeff * bound_diff});
|
||||
if (!AddProductTo(-coeff, lb + bound_diff, &new_ub)) {
|
||||
VLOG(2) << "Overflow";
|
||||
return;
|
||||
}
|
||||
if (slack_infos != nullptr) {
|
||||
tmp_terms_.push_back({first_slack, coeff});
|
||||
first_slack += 2;
|
||||
|
||||
// slack = X + Indicator * bound_diff - lb - diff;
|
||||
slack_info.terms.push_back({var, IntegerValue(1)});
|
||||
slack_info.terms.push_back({info.bool_var, +bound_diff});
|
||||
slack_info.offset = -lb - bound_diff;
|
||||
slack_infos->push_back(slack_info);
|
||||
}
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Add all the new terms coefficient to the overflow detection to avoid
|
||||
// issue when merging terms referring to the same variable.
|
||||
for (int i = old_size; i < tmp_terms_.size(); ++i) {
|
||||
overflow_detection =
|
||||
CapAdd(overflow_detection, std::abs(tmp_terms_[i].second.value()));
|
||||
}
|
||||
}
|
||||
|
||||
if (overflow_detection >= kMaxIntegerValue) {
|
||||
VLOG(2) << "Overflow";
|
||||
return;
|
||||
}
|
||||
if (!changed) return;
|
||||
|
||||
// Update the cut.
|
||||
// Try lower bounded direction for implied bound.
|
||||
// This kind should always be beneficial if it exists:
|
||||
//
|
||||
// Note that because of our overflow_detection variable, there should be
|
||||
// no integer overflow when we merge identical terms.
|
||||
cut->lb = kMinIntegerValue; // Not relevant.
|
||||
cut->ub = new_ub;
|
||||
CleanTermsAndFillConstraint(&tmp_terms_, cut);
|
||||
// Because X = bound_diff * B + S
|
||||
// We can replace coeff * X by the expression before applying f:
|
||||
// = f(coeff * bound_diff) * B + f(coeff) * [X - bound_diff * B]
|
||||
// = f(coeff) * X + (f(coeff * bound_diff) - f(coeff) * bound_diff] * B
|
||||
// So we can "lift" B into the cut with a non-negative coefficient.
|
||||
//
|
||||
// Note that this lifting is really the same as if we used that implied
|
||||
// bound before since in f(coeff * bound_diff) * B + f(coeff) * S, if we
|
||||
// replace S by its value [X - bound_diff * B] we get the same result.
|
||||
//
|
||||
// TODO(user): Ignore if bound_diff == 1 ? But we can still merge B with
|
||||
// another entry if it exists, so it can still be good in this case.
|
||||
//
|
||||
// TODO(user): Only do it if coeff_b > 0 ? But again we could still merge
|
||||
// B with an existing Boolean for a better cut even if coeff_b == 0.
|
||||
const IntegerVariable ib_var = term.expr_coeffs[0] > 0
|
||||
? term.expr_vars[0]
|
||||
: NegationOf(term.expr_vars[0]);
|
||||
const ImpliedBoundsProcessor::BestImpliedBoundInfo info =
|
||||
GetCachedImpliedBoundInfo(ib_var);
|
||||
const IntegerValue lb = -term.expr_offset;
|
||||
const IntegerValue bound_diff = info.implied_bound - lb;
|
||||
if (bound_diff <= 0) return false;
|
||||
if (info.bool_var == kNoIntegerVariable) return false;
|
||||
if (ProdOverflow(factor_t, CapProdI(term.coeff, bound_diff))) return false;
|
||||
|
||||
// We have X = info.diff * Boolean + slack.
|
||||
CutTerm bool_term;
|
||||
bool_term.coeff = term.coeff * bound_diff;
|
||||
bool_term.expr_vars[0] = info.bool_var;
|
||||
bool_term.expr_coeffs[1] = 0;
|
||||
bool_term.bound_diff = IntegerValue(1);
|
||||
bool_term.lp_value = info.bool_lp_value;
|
||||
if (info.is_positive) {
|
||||
bool_term.expr_coeffs[0] = IntegerValue(1);
|
||||
bool_term.expr_offset = IntegerValue(0);
|
||||
} else {
|
||||
bool_term.expr_coeffs[0] = IntegerValue(-1);
|
||||
bool_term.expr_offset = IntegerValue(1);
|
||||
}
|
||||
|
||||
// Create slack.
|
||||
// The expression is term.exp - bound_diff * bool_term
|
||||
// The variable shouldn't be the same.
|
||||
DCHECK_NE(term.expr_vars[0], bool_term.expr_vars[0]);
|
||||
CutTerm slack_term;
|
||||
slack_term.expr_vars[0] = term.expr_vars[0];
|
||||
slack_term.expr_coeffs[0] = term.expr_coeffs[0];
|
||||
slack_term.expr_vars[1] = bool_term.expr_vars[0];
|
||||
slack_term.expr_coeffs[1] = -bound_diff * bool_term.expr_coeffs[0];
|
||||
slack_term.expr_offset =
|
||||
term.expr_offset - bound_diff * bool_term.expr_offset;
|
||||
|
||||
slack_term.lp_value = info.SlackLpValue(lb);
|
||||
slack_term.coeff = term.coeff;
|
||||
slack_term.bound_diff = term.bound_diff;
|
||||
|
||||
// It should be good to use IB, but sometime we have things like
|
||||
// 7.3 = 2 * bool@1 + 5.3 and the expanded Boolean is at its upper bound.
|
||||
// It is always good to complement such variable.
|
||||
//
|
||||
// Note that here we do more and just complement anything closer to UB.
|
||||
//
|
||||
// TODO(user): Because of merges, we might have entry with a coefficient of
|
||||
// zero than are not useful. Remove them.
|
||||
if (complement) {
|
||||
if (bool_term.lp_value > 0.5) {
|
||||
bool_term.Complement(&cut->rhs);
|
||||
}
|
||||
if (slack_term.lp_value >
|
||||
0.5 * static_cast<double>(slack_term.bound_diff.value())) {
|
||||
slack_term.Complement(&cut->rhs);
|
||||
}
|
||||
}
|
||||
|
||||
term = slack_term;
|
||||
builder->AddOrMergeTerm(bool_term, factor_t, cut);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string SingleNodeFlow::DebugString() const {
|
||||
@@ -2011,58 +1944,6 @@ bool FlowCoverCutHelper::GenerateCut(const SingleNodeFlow& data) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ImpliedBoundsProcessor::DebugSlack(IntegerVariable first_slack,
|
||||
const LinearConstraint& initial_cut,
|
||||
const LinearConstraint& cut,
|
||||
const std::vector<SlackInfo>& info) {
|
||||
tmp_terms_.clear();
|
||||
IntegerValue new_ub = cut.ub;
|
||||
for (int i = 0; i < cut.vars.size(); ++i) {
|
||||
// Simple copy for non-slack variables.
|
||||
if (cut.vars[i] < first_slack) {
|
||||
tmp_terms_.push_back({cut.vars[i], cut.coeffs[i]});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Replace slack by its definition.
|
||||
const IntegerValue multiplier = cut.coeffs[i];
|
||||
const int index = (cut.vars[i].value() - first_slack.value()) / 2;
|
||||
for (const std::pair<IntegerVariable, IntegerValue>& term :
|
||||
info[index].terms) {
|
||||
tmp_terms_.push_back({term.first, term.second * multiplier});
|
||||
}
|
||||
new_ub -= multiplier * info[index].offset;
|
||||
}
|
||||
|
||||
LinearConstraint tmp_cut;
|
||||
tmp_cut.lb = kMinIntegerValue; // Not relevant.
|
||||
tmp_cut.ub = new_ub;
|
||||
CleanTermsAndFillConstraint(&tmp_terms_, &tmp_cut);
|
||||
MakeAllVariablesPositive(&tmp_cut);
|
||||
|
||||
// We need to canonicalize the initial_cut too for comparison. Note that we
|
||||
// only use this for debug, so we don't care too much about the memory and
|
||||
// extra time.
|
||||
// TODO(user): Expose CanonicalizeConstraint() from the manager.
|
||||
LinearConstraint tmp_copy;
|
||||
tmp_terms_.clear();
|
||||
for (int i = 0; i < initial_cut.vars.size(); ++i) {
|
||||
tmp_terms_.push_back({initial_cut.vars[i], initial_cut.coeffs[i]});
|
||||
}
|
||||
tmp_copy.lb = kMinIntegerValue; // Not relevant.
|
||||
tmp_copy.ub = new_ub;
|
||||
CleanTermsAndFillConstraint(&tmp_terms_, &tmp_copy);
|
||||
MakeAllVariablesPositive(&tmp_copy);
|
||||
|
||||
if (tmp_cut == tmp_copy) return true;
|
||||
|
||||
LOG(INFO) << first_slack;
|
||||
LOG(INFO) << tmp_copy.DebugString();
|
||||
LOG(INFO) << cut.DebugString();
|
||||
LOG(INFO) << tmp_cut.DebugString();
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
int64_t SumOfKMinValues(const absl::btree_set<int64_t>& values, int k) {
|
||||
|
||||
@@ -59,6 +59,7 @@ struct CutGenerator {
|
||||
// and implied bound substitution.
|
||||
struct CutTerm {
|
||||
bool IsBoolean() const { return bound_diff == 1; }
|
||||
bool IsSimple() const { return expr_coeffs[1] == 0; }
|
||||
bool HasRelevantLpValue() const { return lp_value > 1e-2; }
|
||||
double LpDistToMaxValue() const {
|
||||
return static_cast<double>(bound_diff.value()) - lp_value;
|
||||
@@ -78,7 +79,7 @@ struct CutTerm {
|
||||
|
||||
// X = the given LinearExpression.
|
||||
// We only support size 1 or 2 here which allow to inline the memory.
|
||||
int expr_size = 0;
|
||||
// When a coefficient is zero, we don't care about the variable.
|
||||
IntegerValue expr_offset = IntegerValue(0);
|
||||
std::array<IntegerVariable, 2> expr_vars;
|
||||
std::array<IntegerValue, 2> expr_coeffs;
|
||||
@@ -157,45 +158,9 @@ class ImpliedBoundsProcessor {
|
||||
void RecomputeCacheAndSeparateSomeImpliedBoundCuts(
|
||||
const absl::StrongVector<IntegerVariable, double>& lp_values);
|
||||
|
||||
// Processes and updates the given cut.
|
||||
//
|
||||
// Note that we always look for implied bound under the variable as they
|
||||
// appear. One can change coefficient sign and a variable to its negation to
|
||||
// try the other way around.
|
||||
void ProcessUpperBoundedConstraint(
|
||||
const absl::StrongVector<IntegerVariable, double>& lp_values,
|
||||
LinearConstraint* cut);
|
||||
|
||||
// Same as ProcessUpperBoundedConstraint() but instead of just using
|
||||
// var >= coeff * binary + lb we use var == slack + coeff * binary + lb where
|
||||
// slack is a new temporary variable that we create.
|
||||
//
|
||||
// The new slack will be such that slack_infos[(slack - first_slack) / 2]
|
||||
// contains its definition so that we can properly handle it in the cut
|
||||
// generation and substitute it back later.
|
||||
struct SlackInfo {
|
||||
// This slack is equal to sum of terms + offset.
|
||||
std::vector<std::pair<IntegerVariable, IntegerValue>> terms;
|
||||
IntegerValue offset;
|
||||
|
||||
// The slack bounds and current lp_value.
|
||||
IntegerValue lb = IntegerValue(0);
|
||||
IntegerValue ub = IntegerValue(0);
|
||||
double lp_value = 0.0;
|
||||
};
|
||||
void ProcessUpperBoundedConstraintWithSlackCreation(
|
||||
bool substitute_only_inner_variables, IntegerVariable first_slack,
|
||||
const absl::StrongVector<IntegerVariable, double>& lp_values,
|
||||
LinearConstraint* cut, std::vector<SlackInfo>* slack_infos);
|
||||
|
||||
// Only used for debugging.
|
||||
//
|
||||
// Substituting back the slack created by the function above should give
|
||||
// exactly the same cut as the original one.
|
||||
bool DebugSlack(IntegerVariable first_slack,
|
||||
const LinearConstraint& initial_cut,
|
||||
const LinearConstraint& cut,
|
||||
const std::vector<SlackInfo>& info);
|
||||
bool TryToExpandWithLowerImpliedbound(IntegerValue factor_t, int i,
|
||||
bool complement, CutData* cut,
|
||||
CutDataBuilder* builder);
|
||||
|
||||
// Add a new variable that could be used in the new cuts.
|
||||
// Note that the cache must be computed to take this into account.
|
||||
@@ -242,9 +207,6 @@ class ImpliedBoundsProcessor {
|
||||
// Data from the constructor.
|
||||
IntegerTrail* integer_trail_;
|
||||
ImpliedBounds* implied_bounds_;
|
||||
|
||||
// Temporary memory used by ProcessUpperBoundedConstraint().
|
||||
mutable std::vector<std::pair<IntegerVariable, IntegerValue>> tmp_terms_;
|
||||
};
|
||||
|
||||
// A single node flow relaxation is a constraint of the form
|
||||
@@ -312,15 +274,14 @@ class FlowCoverCutHelper {
|
||||
IntegerTrail* integer_trail, ImpliedBoundsProcessor* ib_helper);
|
||||
|
||||
// Try to generate a cut for the given single node flow problem. Returns true
|
||||
// if a cut was generated. It can be accessed by cut()/mutable_cut().
|
||||
// if a cut was generated. It can be accessed by cut().
|
||||
bool GenerateCut(const SingleNodeFlow& data);
|
||||
|
||||
// If successful, info about the last generated cut.
|
||||
LinearConstraint* mutable_cut() { return &cut_; }
|
||||
const LinearConstraint& cut() const { return cut_; }
|
||||
|
||||
// Single line of text that we append to the cut log line.
|
||||
std::string Info() {
|
||||
std::string Info() const {
|
||||
return absl::StrCat("lift=", num_lifting_, " slack=", slack_.value(),
|
||||
" #in=", num_in_ignored_, "|", num_in_flow_, "|",
|
||||
num_in_bin_, " #out:", num_out_capa_, "|",
|
||||
@@ -420,6 +381,7 @@ std::function<IntegerValue(IntegerValue)> GetSuperAdditiveRoundingFunction(
|
||||
struct RoundingOptions {
|
||||
IntegerValue max_scaling = IntegerValue(60);
|
||||
bool use_ib_before_heuristic = true;
|
||||
bool prefer_positive_ib = true;
|
||||
};
|
||||
class IntegerRoundingCutHelper {
|
||||
public:
|
||||
@@ -427,18 +389,16 @@ class IntegerRoundingCutHelper {
|
||||
|
||||
// Returns true on success. The cut can be accessed via cut().
|
||||
bool ComputeCut(RoundingOptions options, const CutData& base_ct,
|
||||
ImpliedBoundsProcessor* ib_processor);
|
||||
ImpliedBoundsProcessor* ib_processor = nullptr);
|
||||
|
||||
// If successful, info about the last generated cut.
|
||||
LinearConstraint* mutable_cut() { return &cut_; }
|
||||
const LinearConstraint& cut() const { return cut_; }
|
||||
|
||||
// Returns the number of implied bound lifted Booleans in the last
|
||||
// ComputeCut() call. Useful for investigation.
|
||||
int NumLiftedBooleans() const { return num_ib_used_; }
|
||||
|
||||
void SetSharedStatistics(SharedStatistics* stats) { shared_stats_ = stats; }
|
||||
|
||||
// Single line of text that we append to the cut log line.
|
||||
std::string Info() const { return absl::StrCat("ib_lift=", num_ib_used_); }
|
||||
|
||||
private:
|
||||
bool HasComplementedImpliedBound(const CutTerm& entry,
|
||||
ImpliedBoundsProcessor* ib_processor);
|
||||
@@ -447,10 +407,6 @@ class IntegerRoundingCutHelper {
|
||||
IntegerValue remainder_threshold,
|
||||
const CutData& cut);
|
||||
|
||||
bool TryToExpandWithLowerImpliedbound(IntegerValue factor_t, int i,
|
||||
bool complement, CutData* cut,
|
||||
ImpliedBoundsProcessor* ib_processor);
|
||||
|
||||
// The helper is just here to reuse the memory for these vectors.
|
||||
std::vector<IntegerValue> divisors_;
|
||||
std::vector<IntegerValue> remainders_;
|
||||
@@ -479,7 +435,6 @@ class IntegerRoundingCutHelper {
|
||||
|
||||
int64_t total_num_initial_ibs_ = 0;
|
||||
int64_t total_num_initial_merges_ = 0;
|
||||
int64_t total_num_initial_complements_ = 0;
|
||||
};
|
||||
|
||||
// Helper to find knapsack cover cuts.
|
||||
@@ -489,21 +444,9 @@ class CoverCutHelper {
|
||||
|
||||
// Try to find a cut with a knapsack heuristic.
|
||||
// If this returns true, you can get the cut via cut().
|
||||
bool TrySimpleKnapsack(const CutData& input);
|
||||
bool TrySimpleKnapsack(const CutData& input,
|
||||
ImpliedBoundsProcessor* ib_processor = nullptr);
|
||||
|
||||
// If successful, info about the last generated cut.
|
||||
LinearConstraint* mutable_cut() { return &cut_; }
|
||||
const LinearConstraint& cut() const { return cut_; }
|
||||
|
||||
// Single line of text that we append to the cut log line.
|
||||
const std::string Info() { return absl::StrCat("lift=", num_lifting_); }
|
||||
|
||||
// Provides an alternative cut with a different lifting procedure.
|
||||
LinearConstraint* mutable_alt_cut() { return &alt_cut_; }
|
||||
const LinearConstraint& alt_cut() const { return alt_cut_; }
|
||||
|
||||
// Visible for testing.
|
||||
//
|
||||
// Applies the lifting procedure described in "On Lifted Cover Inequalities: A
|
||||
// New Lifting Procedure with Unusual Properties", Adam N. Letchford, Georgia
|
||||
// Souli.
|
||||
@@ -529,11 +472,25 @@ class CoverCutHelper {
|
||||
// - Also, f() should be super additive on the value <= rhs, i.e. f(a + b) >=
|
||||
// f(a) + f(b), so it is always good to use implied bounds of the form X =
|
||||
// bound * B + Slack.
|
||||
bool GenerateLetchfordSouliLifting(const CutData& base_ct, int cover_size);
|
||||
bool TryWithLetchfordSouliLifting(
|
||||
const CutData& input, ImpliedBoundsProcessor* ib_processor = nullptr);
|
||||
|
||||
// If successful, info about the last generated cut.
|
||||
const LinearConstraint& cut() const { return cut_; }
|
||||
|
||||
// Single line of text that we append to the cut log line.
|
||||
std::string Info() const { return absl::StrCat("lift=", num_lifting_); }
|
||||
|
||||
void SetSharedStatistics(SharedStatistics* stats) { shared_stats_ = stats; }
|
||||
|
||||
private:
|
||||
// This changes base_ct_, returns false on overflow.
|
||||
bool MakeAllTermsPositive();
|
||||
|
||||
// This looks at base_ct_ and reoder the terms so that the first ones are in
|
||||
// the cover. return zero if no interesting cover was found.
|
||||
int GetCoverSize(int relevant_size, IntegerValue* rhs);
|
||||
|
||||
// Here to reuse memory.
|
||||
CutData base_ct_;
|
||||
CutData temp_cut_;
|
||||
@@ -542,13 +499,13 @@ class CoverCutHelper {
|
||||
// Stats.
|
||||
SharedStatistics* shared_stats_ = nullptr;
|
||||
int64_t num_lifting_ = 0;
|
||||
|
||||
int64_t total_num_lifting_ = 0;
|
||||
int64_t total_num_cover_not_bool_ = 0;
|
||||
int64_t total_num_ibs_ = 0;
|
||||
int64_t total_num_overflow_abort_ = 0;
|
||||
|
||||
// Stores the cut for output.
|
||||
LinearConstraint cut_;
|
||||
LinearConstraint alt_cut_;
|
||||
};
|
||||
|
||||
// A cut generator for z = x * y (x and y >= 0).
|
||||
@@ -648,7 +605,7 @@ bool BuildMaxAffineUpConstraint(
|
||||
CutGenerator CreateMaxAffineCutGenerator(
|
||||
LinearExpression target, IntegerVariable var,
|
||||
std::vector<std::pair<IntegerValue, IntegerValue>> affines,
|
||||
const std::string cut_name, Model* model);
|
||||
std::string cut_name, Model* model);
|
||||
|
||||
// Extracts the variables that have a Literal view from base variables and
|
||||
// create a generator that will returns constraint of the form "at_most_one"
|
||||
|
||||
@@ -856,148 +856,124 @@ bool LinearProgrammingConstraint::AddCutFromConstraints(
|
||||
expanded_lp_solution_, flow_cover_cut_helper_.Info());
|
||||
}
|
||||
|
||||
// We will create "artificial" variables after this index that will be
|
||||
// substituted back into LP variables afterwards. Also not that we only use
|
||||
// positive variable indices for these new variables, so that algorithm that
|
||||
// take their negation will not mess up the indexing.
|
||||
const IntegerVariable first_new_var(expanded_lp_solution_.size());
|
||||
CHECK_EQ(first_new_var.value() % 2, 0);
|
||||
|
||||
// TODO(user): for TrySimpleKnapsack() and IntegerRoundingCut() the implied
|
||||
// bounds heuristic is not the same. Clean this up.
|
||||
saved_cut_ = cut_;
|
||||
for (int heuristic = 0; heuristic < 2; ++heuristic) {
|
||||
if (heuristic > 0) cut_ = saved_cut_;
|
||||
|
||||
// Unlike for the knapsack cuts, it might not be always beneficial to
|
||||
// process the implied bounds even though it seems to be better in average.
|
||||
//
|
||||
// TODO(user): Perform more experiments, in particular with which bound we
|
||||
// use and if we complement or not before the MIR rounding. Other solvers
|
||||
// seems to try different complementation strategies in a "postprocessing"
|
||||
// and we don't. Try this too.
|
||||
//
|
||||
// TODO(user): substitute_only_inner_variables = true is really helpful on
|
||||
// some instance like "mtest4ma.mps". We should probably have a dedicated
|
||||
// heuristic depending on the type of cut we do below.
|
||||
tmp_ib_slack_infos_.clear();
|
||||
if (heuristic > 0) {
|
||||
const bool substitute_only_inner_variables =
|
||||
absl::Bernoulli(*random_, 0.5);
|
||||
implied_bounds_processor_.ProcessUpperBoundedConstraintWithSlackCreation(
|
||||
substitute_only_inner_variables, first_new_var, expanded_lp_solution_,
|
||||
&cut_, &tmp_ib_slack_infos_);
|
||||
DCHECK(implied_bounds_processor_.DebugSlack(first_new_var, saved_cut_,
|
||||
cut_, tmp_ib_slack_infos_));
|
||||
|
||||
// No need to recompute the same thing if we didn't do any substitution.
|
||||
if (tmp_ib_slack_infos_.empty()) break;
|
||||
}
|
||||
|
||||
// Fills data for computing the cut.
|
||||
tmp_lp_values_.clear();
|
||||
tmp_var_lbs_.clear();
|
||||
tmp_var_ubs_.clear();
|
||||
for (const IntegerVariable var : cut_.vars) {
|
||||
if (var >= first_new_var) {
|
||||
CHECK(VariableIsPositive(var));
|
||||
const auto& info =
|
||||
tmp_ib_slack_infos_[(var.value() - first_new_var.value()) / 2];
|
||||
tmp_lp_values_.push_back(info.lp_value);
|
||||
tmp_var_lbs_.push_back(info.lb);
|
||||
tmp_var_ubs_.push_back(info.ub);
|
||||
} else {
|
||||
tmp_lp_values_.push_back(expanded_lp_solution_[var]);
|
||||
tmp_var_lbs_.push_back(integer_trail_->LevelZeroLowerBound(var));
|
||||
tmp_var_ubs_.push_back(integer_trail_->LevelZeroUpperBound(var));
|
||||
CHECK_GT(tmp_var_ubs_.back(), tmp_var_lbs_.back());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(user): Keep track of the potential overflow here.
|
||||
if (!base_ct_.FillFromParallelVectors(cut_, tmp_lp_values_, tmp_var_lbs_,
|
||||
tmp_var_ubs_)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add slack.
|
||||
const IntegerVariable first_slack(
|
||||
first_new_var + IntegerVariable(2 * tmp_ib_slack_infos_.size()));
|
||||
tmp_slack_rows_.clear();
|
||||
for (const auto& pair : integer_multipliers) {
|
||||
const RowIndex row = pair.first;
|
||||
const IntegerValue coeff = pair.second;
|
||||
const auto status = simplex_.GetConstraintStatus(row);
|
||||
if (status == glop::ConstraintStatus::FIXED_VALUE) continue;
|
||||
|
||||
CutTerm entry;
|
||||
entry.coeff = IntTypeAbs(coeff);
|
||||
entry.lp_value = 0.0;
|
||||
entry.bound_diff =
|
||||
CapSub(integer_lp_[row].ub.value(), integer_lp_[row].lb.value());
|
||||
entry.expr_size = 1;
|
||||
entry.expr_vars[0] =
|
||||
first_slack + 2 * IntegerVariable(tmp_slack_rows_.size());
|
||||
if (coeff > 0) {
|
||||
// Slack = ub - constraint;
|
||||
entry.expr_coeffs[0] = IntegerValue(-1);
|
||||
entry.expr_offset = integer_lp_[row].ub;
|
||||
} else {
|
||||
// Slack = constraint - lb;
|
||||
entry.expr_coeffs[0] = IntegerValue(1);
|
||||
entry.expr_offset = -integer_lp_[row].lb;
|
||||
}
|
||||
|
||||
base_ct_.terms.push_back(entry);
|
||||
tmp_slack_rows_.push_back(row);
|
||||
}
|
||||
|
||||
// Try cover approach to find cut.
|
||||
// We always use IB substitution with positive coeff here.
|
||||
if (cover_cut_helper_.TrySimpleKnapsack(base_ct_)) {
|
||||
at_least_one_added |= PostprocessAndAddCut(
|
||||
absl::StrCat(name, "_K"), cover_cut_helper_.Info(), first_new_var,
|
||||
first_slack, tmp_ib_slack_infos_, cover_cut_helper_.mutable_cut());
|
||||
at_least_one_added |= PostprocessAndAddCut(
|
||||
absl::StrCat(name, "_K2"), "", first_new_var, first_slack,
|
||||
tmp_ib_slack_infos_, cover_cut_helper_.mutable_alt_cut());
|
||||
}
|
||||
|
||||
// Try integer rounding heuristic to find cut.
|
||||
RoundingOptions options;
|
||||
options.max_scaling = parameters_.max_integer_rounding_scaling();
|
||||
if (integer_rounding_cut_helper_.ComputeCut(options, base_ct_,
|
||||
&implied_bounds_processor_)) {
|
||||
at_least_one_added |= PostprocessAndAddCut(
|
||||
name,
|
||||
absl::StrCat("num_lifted_booleans=",
|
||||
integer_rounding_cut_helper_.NumLiftedBooleans()),
|
||||
first_new_var, first_slack, tmp_ib_slack_infos_,
|
||||
integer_rounding_cut_helper_.mutable_cut());
|
||||
}
|
||||
// Note that this always complement the terms to have an lp value closer to
|
||||
// zero.
|
||||
//
|
||||
// TODO(user): Keep track of the potential overflow here.
|
||||
if (!base_ct_.FillFromLinearConstraint(cut_, expanded_lp_solution_,
|
||||
integer_trail_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If there are no integer (all Booleans), no need to try implied bounds
|
||||
// heurititics. By setting this to nullptr, we are a bit faster.
|
||||
bool some_ints = false;
|
||||
bool some_relevant_positions = false;
|
||||
for (const CutTerm& term : base_ct_.terms) {
|
||||
if (term.bound_diff > 1) some_ints = true;
|
||||
if (term.HasRelevantLpValue()) some_relevant_positions = true;
|
||||
}
|
||||
|
||||
// If all value are integer, we will not be able to cut anything.
|
||||
if (!some_relevant_positions) return false;
|
||||
|
||||
ImpliedBoundsProcessor* ib_processor =
|
||||
some_ints ? &implied_bounds_processor_ : nullptr;
|
||||
|
||||
// Add constraint slack.
|
||||
const IntegerVariable first_slack(expanded_lp_solution_.size());
|
||||
CHECK_EQ(first_slack.value() % 2, 0);
|
||||
tmp_slack_rows_.clear();
|
||||
for (const auto& pair : integer_multipliers) {
|
||||
const RowIndex row = pair.first;
|
||||
const IntegerValue coeff = pair.second;
|
||||
const auto status = simplex_.GetConstraintStatus(row);
|
||||
if (status == glop::ConstraintStatus::FIXED_VALUE) continue;
|
||||
|
||||
CutTerm entry;
|
||||
entry.coeff = IntTypeAbs(coeff);
|
||||
entry.lp_value = 0.0;
|
||||
entry.bound_diff =
|
||||
CapSub(integer_lp_[row].ub.value(), integer_lp_[row].lb.value());
|
||||
entry.expr_vars[0] =
|
||||
first_slack + 2 * IntegerVariable(tmp_slack_rows_.size());
|
||||
entry.expr_coeffs[1] = 0;
|
||||
if (coeff > 0) {
|
||||
// Slack = ub - constraint;
|
||||
entry.expr_coeffs[0] = IntegerValue(-1);
|
||||
entry.expr_offset = integer_lp_[row].ub;
|
||||
} else {
|
||||
// Slack = constraint - lb;
|
||||
entry.expr_coeffs[0] = IntegerValue(1);
|
||||
entry.expr_offset = -integer_lp_[row].lb;
|
||||
}
|
||||
|
||||
base_ct_.terms.push_back(entry);
|
||||
tmp_slack_rows_.push_back(row);
|
||||
}
|
||||
|
||||
// Try cover approach to find cut.
|
||||
// TODO(user): Optimize by merging common steps (sort mainly).
|
||||
if (cover_cut_helper_.TrySimpleKnapsack(base_ct_, ib_processor)) {
|
||||
at_least_one_added |= PostprocessAndAddCut(
|
||||
absl::StrCat(name, "_KB"), cover_cut_helper_.Info(), first_slack,
|
||||
cover_cut_helper_.cut());
|
||||
}
|
||||
if (cover_cut_helper_.TryWithLetchfordSouliLifting(base_ct_, ib_processor)) {
|
||||
at_least_one_added |= PostprocessAndAddCut(
|
||||
absl::StrCat(name, "_KL"), cover_cut_helper_.Info(), first_slack,
|
||||
cover_cut_helper_.cut());
|
||||
}
|
||||
|
||||
// Try integer rounding heuristic to find cut.
|
||||
RoundingOptions options;
|
||||
options.max_scaling = parameters_.max_integer_rounding_scaling();
|
||||
|
||||
options.use_ib_before_heuristic = false;
|
||||
if (integer_rounding_cut_helper_.ComputeCut(options, base_ct_,
|
||||
ib_processor)) {
|
||||
at_least_one_added |= PostprocessAndAddCut(
|
||||
absl::StrCat(name, "_R"), integer_rounding_cut_helper_.Info(),
|
||||
first_slack, integer_rounding_cut_helper_.cut());
|
||||
}
|
||||
|
||||
options.use_ib_before_heuristic = true;
|
||||
options.prefer_positive_ib = false;
|
||||
if (ib_processor != nullptr && integer_rounding_cut_helper_.ComputeCut(
|
||||
options, base_ct_, ib_processor)) {
|
||||
at_least_one_added |= PostprocessAndAddCut(
|
||||
absl::StrCat(name, "_RB"), integer_rounding_cut_helper_.Info(),
|
||||
first_slack, integer_rounding_cut_helper_.cut());
|
||||
}
|
||||
|
||||
options.use_ib_before_heuristic = true;
|
||||
options.prefer_positive_ib = true;
|
||||
if (ib_processor != nullptr && integer_rounding_cut_helper_.ComputeCut(
|
||||
options, base_ct_, ib_processor)) {
|
||||
at_least_one_added |= PostprocessAndAddCut(
|
||||
absl::StrCat(name, "_RBP"), integer_rounding_cut_helper_.Info(),
|
||||
first_slack, integer_rounding_cut_helper_.cut());
|
||||
}
|
||||
|
||||
return at_least_one_added;
|
||||
}
|
||||
|
||||
bool LinearProgrammingConstraint::PostprocessAndAddCut(
|
||||
const std::string& name, const std::string& info,
|
||||
IntegerVariable first_new_var, IntegerVariable first_slack,
|
||||
const std::vector<ImpliedBoundsProcessor::SlackInfo>& ib_slack_infos,
|
||||
LinearConstraint* cut) {
|
||||
IntegerVariable first_slack, const LinearConstraint& cut) {
|
||||
// Compute the activity. Warning: the cut no longer have the same size so we
|
||||
// cannot use tmp_lp_values_. Note that the substitution below shouldn't
|
||||
// change the activity by definition.
|
||||
double activity = 0.0;
|
||||
for (int i = 0; i < cut->vars.size(); ++i) {
|
||||
if (cut->vars[i] < first_new_var) {
|
||||
activity +=
|
||||
ToDouble(cut->coeffs[i]) * expanded_lp_solution_[cut->vars[i]];
|
||||
for (int i = 0; i < cut.vars.size(); ++i) {
|
||||
if (cut.vars[i] < first_slack) {
|
||||
activity += ToDouble(cut.coeffs[i]) * expanded_lp_solution_[cut.vars[i]];
|
||||
}
|
||||
}
|
||||
const double kMinViolation = 1e-4;
|
||||
const double violation = activity - ToDouble(cut->ub);
|
||||
const double violation = activity - ToDouble(cut.ub);
|
||||
if (violation < kMinViolation) {
|
||||
VLOG(3) << "Bad cut " << activity << " <= " << ToDouble(cut->ub);
|
||||
VLOG(3) << "Bad cut " << activity << " <= " << ToDouble(cut.ub);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1005,44 +981,19 @@ bool LinearProgrammingConstraint::PostprocessAndAddCut(
|
||||
{
|
||||
int num_slack = 0;
|
||||
tmp_scattered_vector_.ClearAndResize(integer_variables_.size());
|
||||
IntegerValue cut_ub = cut->ub;
|
||||
IntegerValue cut_ub = cut.ub;
|
||||
bool overflow = false;
|
||||
for (int i = 0; i < cut->vars.size(); ++i) {
|
||||
const IntegerVariable var = cut->vars[i];
|
||||
for (int i = 0; i < cut.vars.size(); ++i) {
|
||||
const IntegerVariable var = cut.vars[i];
|
||||
|
||||
// Simple copy for non-slack variables.
|
||||
if (var < first_new_var) {
|
||||
if (var < first_slack) {
|
||||
const glop::ColIndex col =
|
||||
mirror_lp_variable_.at(PositiveVariable(var));
|
||||
if (VariableIsPositive(var)) {
|
||||
tmp_scattered_vector_.Add(col, cut->coeffs[i]);
|
||||
tmp_scattered_vector_.Add(col, cut.coeffs[i]);
|
||||
} else {
|
||||
tmp_scattered_vector_.Add(col, -cut->coeffs[i]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Replace slack from bound substitution.
|
||||
if (var < first_slack) {
|
||||
const IntegerValue multiplier = cut->coeffs[i];
|
||||
const int index = (var.value() - first_new_var.value()) / 2;
|
||||
CHECK_LT(index, ib_slack_infos.size());
|
||||
|
||||
tmp_terms_.clear();
|
||||
for (const std::pair<IntegerVariable, IntegerValue>& term :
|
||||
ib_slack_infos[index].terms) {
|
||||
tmp_terms_.push_back(
|
||||
{mirror_lp_variable_.at(PositiveVariable(term.first)),
|
||||
VariableIsPositive(term.first) ? term.second : -term.second});
|
||||
}
|
||||
if (!tmp_scattered_vector_.AddLinearExpressionMultiple(multiplier,
|
||||
tmp_terms_)) {
|
||||
overflow = true;
|
||||
break;
|
||||
}
|
||||
if (!AddProductTo(multiplier, -ib_slack_infos[index].offset, &cut_ub)) {
|
||||
overflow = true;
|
||||
break;
|
||||
tmp_scattered_vector_.Add(col, -cut.coeffs[i]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -1051,7 +1002,7 @@ bool LinearProgrammingConstraint::PostprocessAndAddCut(
|
||||
++num_slack;
|
||||
const int slack_index = (var.value() - first_slack.value()) / 2;
|
||||
const glop::RowIndex row = tmp_slack_rows_[slack_index];
|
||||
const IntegerValue multiplier = cut->coeffs[i];
|
||||
const IntegerValue multiplier = cut.coeffs[i];
|
||||
if (!tmp_scattered_vector_.AddLinearExpressionMultiple(
|
||||
multiplier, integer_lp_[row].terms)) {
|
||||
overflow = true;
|
||||
@@ -1066,23 +1017,18 @@ bool LinearProgrammingConstraint::PostprocessAndAddCut(
|
||||
|
||||
VLOG(3) << " num_slack: " << num_slack;
|
||||
tmp_scattered_vector_.ConvertToLinearConstraint(integer_variables_, cut_ub,
|
||||
cut);
|
||||
&cut_);
|
||||
}
|
||||
|
||||
// Display some stats used for investigation of cut generation.
|
||||
const std::string extra_info =
|
||||
absl::StrCat(info, " num_ib_substitutions=", ib_slack_infos.size());
|
||||
|
||||
const double new_violation =
|
||||
ComputeActivity(*cut, expanded_lp_solution_) - ToDouble(cut->ub);
|
||||
ComputeActivity(cut_, expanded_lp_solution_) - ToDouble(cut_.ub);
|
||||
if (std::abs(violation - new_violation) >= 1e-4) {
|
||||
VLOG(1) << "Violation discrepancy after slack removal. "
|
||||
<< " before = " << violation << " after = " << new_violation;
|
||||
}
|
||||
|
||||
DivideByGCD(cut);
|
||||
return constraint_manager_.AddCut(*cut, name, expanded_lp_solution_,
|
||||
extra_info);
|
||||
DivideByGCD(&cut_);
|
||||
return constraint_manager_.AddCut(cut_, name, expanded_lp_solution_, info);
|
||||
}
|
||||
|
||||
// TODO(user): This can be still too slow on some problems like
|
||||
|
||||
@@ -290,11 +290,9 @@ class LinearProgrammingConstraint : public PropagatorInterface,
|
||||
integer_multipliers);
|
||||
|
||||
// Second half of AddCutFromConstraints().
|
||||
bool PostprocessAndAddCut(
|
||||
const std::string& name, const std::string& info,
|
||||
IntegerVariable first_new_var, IntegerVariable first_slack,
|
||||
const std::vector<ImpliedBoundsProcessor::SlackInfo>& ib_slack_infos,
|
||||
LinearConstraint* cut);
|
||||
bool PostprocessAndAddCut(const std::string& name, const std::string& info,
|
||||
IntegerVariable first_slack,
|
||||
const LinearConstraint& cut);
|
||||
|
||||
// Computes and adds the corresponding type of cuts.
|
||||
// This can currently only be called at the root node.
|
||||
@@ -449,7 +447,6 @@ class LinearProgrammingConstraint : public PropagatorInterface,
|
||||
std::vector<IntegerValue> tmp_var_lbs_;
|
||||
std::vector<IntegerValue> tmp_var_ubs_;
|
||||
std::vector<glop::RowIndex> tmp_slack_rows_;
|
||||
std::vector<ImpliedBoundsProcessor::SlackInfo> tmp_ib_slack_infos_;
|
||||
std::vector<std::pair<glop::ColIndex, IntegerValue>> tmp_terms_;
|
||||
|
||||
// Used by AddCGCuts().
|
||||
|
||||
Reference in New Issue
Block a user