21#include "absl/container/flat_hash_set.h"
31const LinearConstraintManager::ConstraintIndex kInvalidConstraintIndex(-1);
33size_t ComputeHashOfTerms(
const LinearConstraint&
ct) {
34 DCHECK(std::is_sorted(
ct.vars.begin(),
ct.vars.end()));
36 const int num_terms =
ct.vars.size();
37 for (
int i = 0; i < num_terms; ++i) {
48 absl::StrAppend(&result,
" managed constraints: ", constraint_infos_.size(),
50 if (num_merged_constraints_ > 0) {
51 absl::StrAppend(&result,
" merged constraints: ", num_merged_constraints_,
54 if (num_shortened_constraints_ > 0) {
56 &result,
" shortened constraints: ", num_shortened_constraints_,
"\n");
58 if (num_splitted_constraints_ > 0) {
60 &result,
" splitted constraints: ", num_splitted_constraints_,
"\n");
62 if (num_coeff_strenghtening_ > 0) {
63 absl::StrAppend(&result,
64 " coefficient strenghtenings: ", num_coeff_strenghtening_,
67 if (num_simplifications_ > 0) {
68 absl::StrAppend(&result,
" num simplifications: ", num_simplifications_,
71 absl::StrAppend(&result,
" total cuts added: ", num_cuts_,
" (out of ",
72 num_add_cut_calls_,
" calls)\n");
73 for (
const auto& entry : type_to_num_cuts_) {
74 absl::StrAppend(&result,
" - '", entry.first,
"': ", entry.second,
"\n");
76 if (!result.empty()) result.pop_back();
80void LinearConstraintManager::RescaleActiveCounts(
const double scaling_factor) {
81 for (ConstraintIndex i(0); i < constraint_infos_.size(); ++i) {
82 constraint_infos_[i].active_count *= scaling_factor;
84 constraint_active_count_increase_ *= scaling_factor;
85 VLOG(2) <<
"Rescaled active counts by " << scaling_factor;
88bool LinearConstraintManager::MaybeRemoveSomeInactiveConstraints(
89 glop::BasisState* solution_state) {
90 if (solution_state->IsEmpty())
return false;
91 const glop::RowIndex num_rows(lp_constraints_.size());
92 const glop::ColIndex num_cols =
95 for (
int i = 0; i < num_rows; ++i) {
96 const ConstraintIndex constraint_index = lp_constraints_[i];
105 solution_state->statuses[num_cols + glop::ColIndex(i)];
107 constraint_infos_[constraint_index].inactive_count++;
108 if (constraint_infos_[constraint_index].inactive_count >
110 constraint_infos_[constraint_index].is_in_lp =
false;
115 constraint_infos_[constraint_index].inactive_count = 0;
118 lp_constraints_[new_size] = constraint_index;
119 solution_state->statuses[num_cols + glop::ColIndex(new_size)] = row_status;
122 const int num_removed_constraints = lp_constraints_.size() - new_size;
123 lp_constraints_.resize(new_size);
124 solution_state->statuses.resize(num_cols + glop::ColIndex(new_size));
125 if (num_removed_constraints > 0) {
126 VLOG(2) <<
"Removed " << num_removed_constraints <<
" constraints";
128 return num_removed_constraints > 0;
138 SimplifyConstraint(&
ct);
144 const size_t key = ComputeHashOfTerms(
ct);
146 const ConstraintIndex ct_index = equiv_constraints_[key];
147 if (constraint_infos_[ct_index].constraint.vars ==
ct.vars &&
148 constraint_infos_[ct_index].constraint.coeffs ==
ct.coeffs) {
149 if (added !=
nullptr) *added =
false;
150 if (
ct.lb > constraint_infos_[ct_index].constraint.lb) {
151 if (constraint_infos_[ct_index].is_in_lp) current_lp_is_changed_ =
true;
152 constraint_infos_[ct_index].constraint.lb =
ct.lb;
153 if (added !=
nullptr) *added =
true;
155 if (
ct.ub < constraint_infos_[ct_index].constraint.ub) {
156 if (constraint_infos_[ct_index].is_in_lp) current_lp_is_changed_ =
true;
157 constraint_infos_[ct_index].constraint.ub =
ct.ub;
158 if (added !=
nullptr) *added =
true;
160 ++num_merged_constraints_;
165 if (added !=
nullptr) *added =
true;
166 const ConstraintIndex ct_index(constraint_infos_.size());
171 equiv_constraints_[key] = ct_index;
172 ct_info.
active_count = constraint_active_count_increase_;
173 constraint_infos_.push_back(std::move(ct_info));
177void LinearConstraintManager::ComputeObjectiveParallelism(
178 const ConstraintIndex ct_index) {
179 CHECK(objective_is_defined_);
181 if (!objective_norm_computed_) {
182 objective_l2_norm_ = std::sqrt(sum_of_squared_objective_coeffs_);
183 objective_norm_computed_ =
true;
187 constraint_infos_[ct_index].objective_parallelism_computed =
true;
188 if (constraint_infos_[ct_index].l2_norm == 0.0) {
189 constraint_infos_[ct_index].objective_parallelism = 0.0;
193 const LinearConstraint& lc = constraint_infos_[ct_index].constraint;
194 double unscaled_objective_parallelism = 0.0;
195 for (
int i = 0; i < lc.vars.size(); ++i) {
196 const IntegerVariable
var = lc.vars[i];
197 const auto it = objective_map_.find(
var);
198 if (it == objective_map_.end())
continue;
199 unscaled_objective_parallelism += it->second *
ToDouble(lc.coeffs[i]);
201 const double objective_parallelism =
202 unscaled_objective_parallelism /
203 (constraint_infos_[ct_index].l2_norm * objective_l2_norm_);
204 constraint_infos_[ct_index].objective_parallelism =
205 std::abs(objective_parallelism);
213 std::string extra_info) {
214 ++num_add_cut_calls_;
215 if (
ct.vars.empty())
return false;
218 const double violation =
223 if (violation / l2_norm < 1e-5)
return false;
226 const ConstraintIndex ct_index =
Add(std::move(
ct), &added);
230 if (!added)
return false;
234 constraint_infos_[ct_index].is_deletable =
true;
236 VLOG(1) <<
"Cut '" << type_name <<
"'"
237 <<
" size=" << constraint_infos_[ct_index].constraint.vars.size()
240 <<
" norm=" << l2_norm <<
" violation=" << violation
241 <<
" eff=" << violation / l2_norm <<
" " << extra_info;
244 num_deletable_constraints_++;
245 type_to_num_cuts_[type_name]++;
249void LinearConstraintManager::PermanentlyRemoveSomeConstraints() {
250 std::vector<double> deletable_constraint_counts;
251 for (ConstraintIndex i(0); i < constraint_infos_.size(); ++i) {
252 if (constraint_infos_[i].is_deletable && !constraint_infos_[i].is_in_lp) {
253 deletable_constraint_counts.push_back(constraint_infos_[i].active_count);
256 if (deletable_constraint_counts.empty())
return;
257 std::sort(deletable_constraint_counts.begin(),
258 deletable_constraint_counts.end());
262 double active_count_threshold = std::numeric_limits<double>::infinity();
264 deletable_constraint_counts.size()) {
265 active_count_threshold =
269 ConstraintIndex new_size(0);
270 equiv_constraints_.clear();
272 constraint_infos_.size());
273 int num_deleted_constraints = 0;
274 for (ConstraintIndex i(0); i < constraint_infos_.size(); ++i) {
275 if (constraint_infos_[i].is_deletable && !constraint_infos_[i].is_in_lp &&
276 constraint_infos_[i].active_count <= active_count_threshold &&
278 ++num_deleted_constraints;
283 constraint_infos_[new_size] = std::move(constraint_infos_[i]);
285 index_mapping[i] = new_size;
288 equiv_constraints_[constraint_infos_[new_size].hash] = new_size;
291 constraint_infos_.resize(new_size.value());
294 for (
int i = 0; i < lp_constraints_.size(); ++i) {
295 lp_constraints_[i] = index_mapping[lp_constraints_[i]];
298 if (num_deleted_constraints > 0) {
299 VLOG(1) <<
"Constraint manager cleanup: #deleted:"
300 << num_deleted_constraints;
302 num_deletable_constraints_ -= num_deleted_constraints;
306 IntegerValue coeff) {
307 if (coeff == IntegerValue(0))
return;
308 objective_is_defined_ =
true;
313 const double coeff_as_double =
ToDouble(coeff);
314 const auto insert = objective_map_.insert({
var, coeff_as_double});
316 <<
"SetObjectiveCoefficient() called twice with same variable";
317 sum_of_squared_objective_coeffs_ += coeff_as_double * coeff_as_double;
321 bool term_changed =
false;
323 IntegerValue min_sum(0);
324 IntegerValue max_sum(0);
325 IntegerValue max_magnitude(0);
327 const int num_terms =
ct->vars.size();
328 for (
int i = 0; i < num_terms; ++i) {
329 const IntegerVariable
var =
ct->vars[i];
330 const IntegerValue coeff =
ct->coeffs[i];
336 if (lb == ub)
continue;
341 min_sum += coeff * lb;
342 max_sum += coeff * ub;
344 min_sum += coeff * ub;
345 max_sum += coeff * lb;
350 if (new_size < num_terms) {
352 ++num_shortened_constraints_;
354 for (
int i = 0; i < num_terms; ++i) {
355 const IntegerVariable
var =
ct->vars[i];
356 const IntegerValue coeff =
ct->coeffs[i];
360 const IntegerValue rhs_adjust = lb * coeff;
365 ct->vars[new_size] =
var;
366 ct->coeffs[new_size] = coeff;
369 ct->vars.resize(new_size);
370 ct->coeffs.resize(new_size);
394 ++num_splitted_constraints_;
397 ++num_coeff_strenghtening_;
398 const int num_terms =
ct->vars.size();
399 const IntegerValue target = max_sum -
ct->ub;
400 for (
int i = 0; i < num_terms; ++i) {
401 const IntegerValue coeff =
ct->coeffs[i];
402 if (coeff > target) {
403 const IntegerVariable
var =
ct->vars[i];
405 ct->coeffs[i] = target;
406 ct->ub -= (coeff - target) * ub;
407 }
else if (coeff < -target) {
408 const IntegerVariable
var =
ct->vars[i];
410 ct->coeffs[i] = -target;
411 ct->ub += (-target - coeff) * lb;
419 ++num_splitted_constraints_;
422 ++num_coeff_strenghtening_;
423 const int num_terms =
ct->vars.size();
424 const IntegerValue target =
ct->lb - min_sum;
425 for (
int i = 0; i < num_terms; ++i) {
426 const IntegerValue coeff =
ct->coeffs[i];
427 if (coeff > target) {
428 const IntegerVariable
var =
ct->vars[i];
430 ct->coeffs[i] = target;
431 ct->lb -= (coeff - target) * lb;
432 }
else if (coeff < -target) {
433 const IntegerVariable
var =
ct->vars[i];
435 ct->coeffs[i] = -target;
436 ct->lb += (-target - coeff) * ub;
448 VLOG(3) <<
"Enter ChangeLP, scan " << constraint_infos_.size()
450 const double saved_dtime = dtime_;
451 std::vector<ConstraintIndex> new_constraints;
452 std::vector<double> new_constraints_efficacies;
453 std::vector<double> new_constraints_orthogonalities;
455 const bool simplify_constraints =
462 bool rescale_active_count =
false;
463 const double tolerance = 1e-6;
464 for (ConstraintIndex i(0); i < constraint_infos_.size(); ++i) {
466 if (simplify_constraints &&
467 SimplifyConstraint(&constraint_infos_[i].constraint)) {
468 ++num_simplifications_;
476 constraint_infos_[i].objective_parallelism_computed =
false;
477 constraint_infos_[i].l2_norm =
480 if (constraint_infos_[i].is_in_lp) current_lp_is_changed_ =
true;
481 equiv_constraints_.erase(constraint_infos_[i].
hash);
482 constraint_infos_[i].hash =
483 ComputeHashOfTerms(constraint_infos_[i].constraint);
487 equiv_constraints_[constraint_infos_[i].hash] = i;
490 if (constraint_infos_[i].is_in_lp)
continue;
495 static_cast<double>(constraint_infos_[i].constraint.vars.size());
496 const double activity =
498 const double lb_violation =
499 ToDouble(constraint_infos_[i].constraint.lb) - activity;
500 const double ub_violation =
501 activity -
ToDouble(constraint_infos_[i].constraint.ub);
502 const double violation =
std::max(lb_violation, ub_violation);
503 if (violation >= tolerance) {
504 constraint_infos_[i].inactive_count = 0;
505 new_constraints.push_back(i);
506 new_constraints_efficacies.push_back(violation /
507 constraint_infos_[i].l2_norm);
508 new_constraints_orthogonalities.push_back(1.0);
510 if (objective_is_defined_ &&
511 !constraint_infos_[i].objective_parallelism_computed) {
512 ComputeObjectiveParallelism(i);
513 }
else if (!objective_is_defined_) {
514 constraint_infos_[i].objective_parallelism = 0.0;
517 constraint_infos_[i].current_score =
518 new_constraints_efficacies.back() +
519 constraint_infos_[i].objective_parallelism;
521 if (constraint_infos_[i].is_deletable) {
522 constraint_infos_[i].active_count += constraint_active_count_increase_;
523 if (constraint_infos_[i].active_count >
525 rescale_active_count =
true;
532 if (solution_state !=
nullptr) {
533 const glop::RowIndex num_rows(lp_constraints_.size());
534 const glop::ColIndex num_cols =
537 for (
int i = 0; i < num_rows; ++i) {
538 const ConstraintIndex constraint_index = lp_constraints_[i];
540 solution_state->
statuses[num_cols + glop::ColIndex(i)];
542 constraint_infos_[constraint_index].is_deletable) {
543 constraint_infos_[constraint_index].active_count +=
544 constraint_active_count_increase_;
545 if (constraint_infos_[constraint_index].active_count >
547 rescale_active_count =
true;
553 if (rescale_active_count) {
559 constraint_active_count_increase_ *=
565 if (MaybeRemoveSomeInactiveConstraints(solution_state)) {
566 current_lp_is_changed_ =
true;
579 const int kBlowupFactor = 4;
581 static_cast<int>(new_constraints.size()));
582 if (lp_constraints_.empty()) {
583 constraint_limit =
std::min(1000,
static_cast<int>(new_constraints.size()));
585 VLOG(3) <<
" - size = " << new_constraints.size()
586 <<
", limit = " << constraint_limit;
588 std::stable_sort(new_constraints.begin(), new_constraints.end(),
589 [&](ConstraintIndex
a, ConstraintIndex
b) {
590 return constraint_infos_[a].current_score >
591 constraint_infos_[b].current_score;
593 if (new_constraints.size() > kBlowupFactor * constraint_limit) {
594 VLOG(3) <<
"Resize candidate constraints from " << new_constraints.size()
595 <<
" down to " << kBlowupFactor * constraint_limit;
596 new_constraints.resize(kBlowupFactor * constraint_limit);
600 int num_skipped_checks = 0;
601 const int kCheckFrequency = 100;
602 ConstraintIndex last_added_candidate = kInvalidConstraintIndex;
603 for (
int i = 0; i < constraint_limit; ++i) {
606 double best_score = 0.0;
607 ConstraintIndex best_candidate = kInvalidConstraintIndex;
608 for (
int j = 0; j < new_constraints.size(); ++j) {
610 if (++num_skipped_checks >= kCheckFrequency) {
611 if (time_limit_->
LimitReached())
return current_lp_is_changed_;
612 num_skipped_checks = 0;
615 const ConstraintIndex new_constraint = new_constraints[j];
616 if (constraint_infos_[new_constraint].is_in_lp)
continue;
618 if (last_added_candidate != kInvalidConstraintIndex) {
619 const double current_orthogonality =
621 constraint_infos_[last_added_candidate].constraint,
622 constraint_infos_[new_constraint].constraint)) /
623 (constraint_infos_[last_added_candidate].l2_norm *
624 constraint_infos_[new_constraint].l2_norm));
625 new_constraints_orthogonalities[j] =
626 std::min(new_constraints_orthogonalities[j], current_orthogonality);
634 if (new_constraints_orthogonalities[j] <
641 const double score = new_constraints_orthogonalities[j] +
642 constraint_infos_[new_constraint].current_score;
644 if (score > best_score || best_candidate == kInvalidConstraintIndex) {
646 best_candidate = new_constraint;
650 if (best_candidate != kInvalidConstraintIndex) {
652 constraint_infos_[best_candidate].is_in_lp =
true;
657 current_lp_is_changed_ =
true;
658 lp_constraints_.push_back(best_candidate);
659 last_added_candidate = best_candidate;
665 VLOG(2) <<
"Added " << num_added <<
" constraints.";
672 if (num_deletable_constraints_ > sat_parameters_.
max_num_cuts()) {
673 PermanentlyRemoveSomeConstraints();
680 if (current_lp_is_changed_) {
681 current_lp_is_changed_ =
false;
688 for (ConstraintIndex i(0); i < constraint_infos_.size(); ++i) {
689 if (constraint_infos_[i].is_in_lp)
continue;
690 constraint_infos_[i].is_in_lp =
true;
691 lp_constraints_.push_back(i);
699 if (debug_solution.empty())
return true;
701 IntegerValue activity(0);
702 for (
int i = 0; i < cut.
vars.size(); ++i) {
703 const IntegerVariable
var = cut.
vars[i];
704 const IntegerValue coeff = cut.
coeffs[i];
705 activity += coeff * debug_solution[
var];
707 if (activity > cut.
ub || activity < cut.
lb) {
708 LOG(
INFO) <<
"activity " << activity <<
" not in [" << cut.
lb <<
","
718 if (
ct.vars.empty())
return;
720 const double violation =
723 cuts_.Add({
name,
ct}, violation / l2_norm);
729 for (
const CutCandidate& candidate : cuts_.UnorderedElements()) {
730 manager->
AddCut(candidate.cut, candidate.name, lp_solution);
#define CHECK_GE(val1, val2)
#define CHECK_GT(val1, val2)
#define DCHECK(condition)
#define VLOG(verboselevel)
bool LimitReached()
Returns true when the external limit is true, or the deterministic time is over the deterministic lim...
void AdvanceDeterministicTime(double deterministic_duration)
Advances the deterministic time.
void resize(IntType size)
int64_t num_level_zero_enqueues() const
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
bool ChangeLp(const absl::StrongVector< IntegerVariable, double > &lp_solution, glop::BasisState *solution_state)
bool DebugCheckConstraint(const LinearConstraint &cut)
void SetObjectiveCoefficient(IntegerVariable var, IntegerValue coeff)
ConstraintIndex Add(LinearConstraint ct, bool *added=nullptr)
std::string Statistics() const
void AddAllConstraintsToLp()
bool AddCut(LinearConstraint ct, std::string type_name, const absl::StrongVector< IntegerVariable, double > &lp_solution, std::string extra_info="")
T Get(std::function< T(const Model &)> f) const
Similar to Add() but this is const.
::PROTOBUF_NAMESPACE_ID::int32 new_constraints_batch_size() const
::PROTOBUF_NAMESPACE_ID::int32 max_consecutive_inactive_count() const
::PROTOBUF_NAMESPACE_ID::int32 max_num_cuts() const
double cut_max_active_count_value() const
::PROTOBUF_NAMESPACE_ID::int32 cut_cleanup_target() const
double cut_active_count_decay() const
double min_orthogonality_for_lp_constraints() const
void AddCut(LinearConstraint ct, const std::string &name, const absl::StrongVector< IntegerVariable, double > &lp_solution)
void TransferToManager(const absl::StrongVector< IntegerVariable, double > &lp_solution, LinearConstraintManager *manager)
bool ContainsKey(const Collection &collection, const Key &key)
ColIndex RowToColIndex(RowIndex row)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
IntType IntTypeAbs(IntType t)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue)
double ScalarProduct(const LinearConstraint &constraint1, const LinearConstraint &constraint2)
void CanonicalizeConstraint(LinearConstraint *ct)
bool NoDuplicateVariable(const LinearConstraint &ct)
double ComputeL2Norm(const LinearConstraint &constraint)
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
IntegerValue ComputeInfinityNorm(const LinearConstraint &constraint)
bool VariableIsPositive(IntegerVariable i)
void DivideByGCD(LinearConstraint *constraint)
double ComputeActivity(const LinearConstraint &constraint, const absl::StrongVector< IntegerVariable, double > &values)
double ToDouble(IntegerValue value)
Collection of objects used to extend the Constraint Solver library.
uint64_t Hash(uint64_t num, uint64_t c)
VariableStatusRow statuses
std::vector< IntegerValue > coeffs
std::vector< IntegerVariable > vars
LinearConstraint constraint