21 #include "absl/container/flat_hash_set.h"
30 const LinearConstraintManager::ConstraintIndex kInvalidConstraintIndex(-1);
32 size_t ComputeHashOfTerms(
const LinearConstraint&
ct) {
33 DCHECK(std::is_sorted(
ct.vars.begin(),
ct.vars.end()));
35 const int num_terms =
ct.vars.size();
36 for (
int i = 0; i < num_terms; ++i) {
46 if (num_merged_constraints_ > 0) {
47 VLOG(2) <<
"num_merged_constraints: " << num_merged_constraints_;
49 if (num_shortened_constraints_ > 0) {
50 VLOG(2) <<
"num_shortened_constraints: " << num_shortened_constraints_;
52 if (num_splitted_constraints_ > 0) {
53 VLOG(2) <<
"num_splitted_constraints: " << num_splitted_constraints_;
55 if (num_coeff_strenghtening_ > 0) {
56 VLOG(2) <<
"num_coeff_strenghtening: " << num_coeff_strenghtening_;
58 if (sat_parameters_.log_search_progress() && num_cuts_ > 0) {
59 LOG(INFO) <<
"Total cuts added: " << num_cuts_;
60 for (
const auto& entry : type_to_num_cuts_) {
61 LOG(INFO) <<
"Added " << entry.second <<
" cuts of type '" << entry.first
67 void LinearConstraintManager::RescaleActiveCounts(
const double scaling_factor) {
68 for (ConstraintIndex i(0); i < constraint_infos_.size(); ++i) {
69 constraint_infos_[i].active_count *= scaling_factor;
71 constraint_active_count_increase_ *= scaling_factor;
72 VLOG(2) <<
"Rescaled active counts by " << scaling_factor;
75 bool LinearConstraintManager::MaybeRemoveSomeInactiveConstraints(
76 glop::BasisState* solution_state) {
77 if (solution_state->IsEmpty())
return false;
78 const glop::RowIndex num_rows(lp_constraints_.size());
79 const glop::ColIndex num_cols =
83 for (
int i = 0; i < num_rows; ++i) {
84 const ConstraintIndex constraint_index = lp_constraints_[i];
93 solution_state->statuses[num_cols + glop::ColIndex(i)];
95 constraint_infos_[constraint_index].inactive_count++;
96 if (constraint_infos_[constraint_index].inactive_count >
97 sat_parameters_.max_consecutive_inactive_count()) {
98 constraint_infos_[constraint_index].is_in_lp =
false;
103 constraint_infos_[constraint_index].inactive_count = 0;
106 lp_constraints_[new_size] = constraint_index;
107 solution_state->statuses[num_cols + glop::ColIndex(new_size)] = row_status;
110 const int num_removed_constraints = lp_constraints_.size() - new_size;
111 lp_constraints_.resize(new_size);
112 solution_state->statuses.resize(num_cols + glop::ColIndex(new_size));
113 if (num_removed_constraints > 0) {
114 VLOG(2) <<
"Removed " << num_removed_constraints <<
" constraints";
116 return num_removed_constraints > 0;
124 CHECK(!
ct.vars.empty());
126 SimplifyConstraint(&
ct);
132 const size_t key = ComputeHashOfTerms(
ct);
134 const ConstraintIndex ct_index = equiv_constraints_[key];
135 if (constraint_infos_[ct_index].constraint.vars ==
ct.vars &&
136 constraint_infos_[ct_index].constraint.coeffs ==
ct.coeffs) {
137 if (added !=
nullptr) *added =
false;
138 if (
ct.lb > constraint_infos_[ct_index].constraint.lb) {
139 if (constraint_infos_[ct_index].is_in_lp) current_lp_is_changed_ =
true;
140 constraint_infos_[ct_index].constraint.lb =
ct.lb;
141 if (added !=
nullptr) *added =
true;
143 if (
ct.ub < constraint_infos_[ct_index].constraint.ub) {
144 if (constraint_infos_[ct_index].is_in_lp) current_lp_is_changed_ =
true;
145 constraint_infos_[ct_index].constraint.ub =
ct.ub;
146 if (added !=
nullptr) *added =
true;
148 ++num_merged_constraints_;
153 if (added !=
nullptr) *added =
true;
154 const ConstraintIndex ct_index(constraint_infos_.size());
159 equiv_constraints_[key] = ct_index;
160 ct_info.
active_count = constraint_active_count_increase_;
161 constraint_infos_.push_back(std::move(ct_info));
165 void LinearConstraintManager::ComputeObjectiveParallelism(
166 const ConstraintIndex ct_index) {
167 CHECK(objective_is_defined_);
169 if (!objective_norm_computed_) {
171 for (
const double coeff : dense_objective_coeffs_) {
172 sum += coeff * coeff;
174 objective_l2_norm_ = std::sqrt(sum);
175 objective_norm_computed_ =
true;
177 CHECK_GT(objective_l2_norm_, 0.0);
179 constraint_infos_[ct_index].objective_parallelism_computed =
true;
180 if (constraint_infos_[ct_index].l2_norm == 0.0) {
181 constraint_infos_[ct_index].objective_parallelism = 0.0;
185 const LinearConstraint& lc = constraint_infos_[ct_index].constraint;
186 double unscaled_objective_parallelism = 0.0;
187 for (
int i = 0; i < lc.vars.size(); ++i) {
188 const IntegerVariable
var = lc.vars[i];
190 if (
var < dense_objective_coeffs_.
size()) {
191 unscaled_objective_parallelism +=
192 ToDouble(lc.coeffs[i]) * dense_objective_coeffs_[
var];
195 const double objective_parallelism =
196 unscaled_objective_parallelism /
197 (constraint_infos_[ct_index].l2_norm * objective_l2_norm_);
198 constraint_infos_[ct_index].objective_parallelism =
199 std::abs(objective_parallelism);
207 std::string extra_info) {
208 if (
ct.vars.empty())
return false;
211 const double violation =
216 if (violation / l2_norm < 1e-5)
return false;
219 const ConstraintIndex ct_index =
Add(std::move(
ct), &added);
223 if (!added)
return false;
227 constraint_infos_[ct_index].is_deletable =
true;
229 VLOG(1) <<
"Cut '" << type_name <<
"'"
230 <<
" size=" << constraint_infos_[ct_index].constraint.vars.size()
233 <<
" norm=" << l2_norm <<
" violation=" << violation
234 <<
" eff=" << violation / l2_norm <<
" " << extra_info;
237 num_deletable_constraints_++;
238 type_to_num_cuts_[type_name]++;
242 void LinearConstraintManager::PermanentlyRemoveSomeConstraints() {
243 std::vector<double> deletable_constraint_counts;
244 for (ConstraintIndex i(0); i < constraint_infos_.size(); ++i) {
245 if (constraint_infos_[i].is_deletable && !constraint_infos_[i].is_in_lp) {
246 deletable_constraint_counts.push_back(constraint_infos_[i].active_count);
249 if (deletable_constraint_counts.empty())
return;
250 std::sort(deletable_constraint_counts.begin(),
251 deletable_constraint_counts.end());
255 double active_count_threshold = std::numeric_limits<double>::infinity();
256 if (sat_parameters_.cut_cleanup_target() <
257 deletable_constraint_counts.size()) {
258 active_count_threshold =
259 deletable_constraint_counts[sat_parameters_.cut_cleanup_target()];
262 ConstraintIndex new_size(0);
263 equiv_constraints_.clear();
265 constraint_infos_.size());
266 int num_deleted_constraints = 0;
267 for (ConstraintIndex i(0); i < constraint_infos_.size(); ++i) {
268 if (constraint_infos_[i].is_deletable && !constraint_infos_[i].is_in_lp &&
269 constraint_infos_[i].active_count <= active_count_threshold &&
270 num_deleted_constraints < sat_parameters_.cut_cleanup_target()) {
271 ++num_deleted_constraints;
276 constraint_infos_[new_size] = std::move(constraint_infos_[i]);
278 index_mapping[i] = new_size;
281 equiv_constraints_[constraint_infos_[new_size].hash] = new_size;
284 constraint_infos_.resize(new_size.value());
287 for (
int i = 0; i < lp_constraints_.size(); ++i) {
288 lp_constraints_[i] = index_mapping[lp_constraints_[i]];
291 if (num_deleted_constraints > 0) {
292 VLOG(1) <<
"Constraint manager cleanup: #deleted:"
293 << num_deleted_constraints;
295 num_deletable_constraints_ -= num_deleted_constraints;
299 IntegerValue coeff) {
300 if (coeff == IntegerValue(0))
return;
301 objective_is_defined_ =
true;
306 if (
var.value() >= dense_objective_coeffs_.
size()) {
307 dense_objective_coeffs_.
resize(
var.value() + 1, 0.0);
313 bool term_changed =
false;
315 IntegerValue min_sum(0);
316 IntegerValue max_sum(0);
317 IntegerValue max_magnitude(0);
319 const int num_terms =
ct->vars.size();
320 for (
int i = 0; i < num_terms; ++i) {
321 const IntegerVariable
var =
ct->vars[i];
322 const IntegerValue coeff =
ct->coeffs[i];
328 if (lb == ub)
continue;
333 min_sum += coeff * lb;
334 max_sum += coeff * ub;
336 min_sum += coeff * ub;
337 max_sum += coeff * lb;
342 if (new_size < num_terms) {
344 ++num_shortened_constraints_;
346 for (
int i = 0; i < num_terms; ++i) {
347 const IntegerVariable
var =
ct->vars[i];
348 const IntegerValue coeff =
ct->coeffs[i];
352 const IntegerValue rhs_adjust = lb * coeff;
357 ct->vars[new_size] =
var;
358 ct->coeffs[new_size] = coeff;
361 ct->vars.resize(new_size);
362 ct->coeffs.resize(new_size);
386 ++num_splitted_constraints_;
389 ++num_coeff_strenghtening_;
390 const int num_terms =
ct->vars.size();
391 const IntegerValue target = max_sum -
ct->ub;
392 for (
int i = 0; i < num_terms; ++i) {
393 const IntegerValue coeff =
ct->coeffs[i];
394 if (coeff > target) {
395 const IntegerVariable
var =
ct->vars[i];
397 ct->coeffs[i] = target;
398 ct->ub -= (coeff - target) * ub;
399 }
else if (coeff < -target) {
400 const IntegerVariable
var =
ct->vars[i];
402 ct->coeffs[i] = -target;
403 ct->ub += (-target - coeff) * lb;
411 ++num_splitted_constraints_;
414 ++num_coeff_strenghtening_;
415 const int num_terms =
ct->vars.size();
416 const IntegerValue target =
ct->lb - min_sum;
417 for (
int i = 0; i < num_terms; ++i) {
418 const IntegerValue coeff =
ct->coeffs[i];
419 if (coeff > target) {
420 const IntegerVariable
var =
ct->vars[i];
422 ct->coeffs[i] = target;
423 ct->lb -= (coeff - target) * lb;
424 }
else if (coeff < -target) {
425 const IntegerVariable
var =
ct->vars[i];
427 ct->coeffs[i] = -target;
428 ct->lb += (-target - coeff) * ub;
440 VLOG(3) <<
"Enter ChangeLP, scan " << constraint_infos_.size()
442 std::vector<ConstraintIndex> new_constraints;
443 std::vector<double> new_constraints_efficacies;
444 std::vector<double> new_constraints_orthogonalities;
446 const bool simplify_constraints =
452 bool rescale_active_count =
false;
453 const double tolerance = 1e-6;
454 for (ConstraintIndex i(0); i < constraint_infos_.size(); ++i) {
456 if (simplify_constraints &&
457 SimplifyConstraint(&constraint_infos_[i].constraint)) {
464 constraint_infos_[i].objective_parallelism_computed =
false;
465 constraint_infos_[i].l2_norm =
468 if (constraint_infos_[i].is_in_lp) current_lp_is_changed_ =
true;
469 equiv_constraints_.erase(constraint_infos_[i].
hash);
470 constraint_infos_[i].hash =
471 ComputeHashOfTerms(constraint_infos_[i].constraint);
475 equiv_constraints_[constraint_infos_[i].hash] = i;
478 if (constraint_infos_[i].is_in_lp)
continue;
480 const double activity =
482 const double lb_violation =
483 ToDouble(constraint_infos_[i].constraint.lb) - activity;
484 const double ub_violation =
485 activity -
ToDouble(constraint_infos_[i].constraint.ub);
486 const double violation =
std::max(lb_violation, ub_violation);
487 if (violation >= tolerance) {
488 constraint_infos_[i].inactive_count = 0;
489 new_constraints.push_back(i);
490 new_constraints_efficacies.push_back(violation /
491 constraint_infos_[i].l2_norm);
492 new_constraints_orthogonalities.push_back(1.0);
494 if (objective_is_defined_ &&
495 !constraint_infos_[i].objective_parallelism_computed) {
496 ComputeObjectiveParallelism(i);
497 }
else if (!objective_is_defined_) {
498 constraint_infos_[i].objective_parallelism = 0.0;
501 constraint_infos_[i].current_score =
502 new_constraints_efficacies.back() +
503 constraint_infos_[i].objective_parallelism;
505 if (constraint_infos_[i].is_deletable) {
506 constraint_infos_[i].active_count += constraint_active_count_increase_;
507 if (constraint_infos_[i].active_count >
508 sat_parameters_.cut_max_active_count_value()) {
509 rescale_active_count =
true;
516 if (solution_state !=
nullptr) {
517 const glop::RowIndex num_rows(lp_constraints_.size());
518 const glop::ColIndex num_cols =
521 for (
int i = 0; i < num_rows; ++i) {
522 const ConstraintIndex constraint_index = lp_constraints_[i];
524 solution_state->
statuses[num_cols + glop::ColIndex(i)];
526 constraint_infos_[constraint_index].is_deletable) {
527 constraint_infos_[constraint_index].active_count +=
528 constraint_active_count_increase_;
529 if (constraint_infos_[constraint_index].active_count >
530 sat_parameters_.cut_max_active_count_value()) {
531 rescale_active_count =
true;
537 if (rescale_active_count) {
538 CHECK_GT(sat_parameters_.cut_max_active_count_value(), 0.0);
539 RescaleActiveCounts(1.0 / sat_parameters_.cut_max_active_count_value());
543 constraint_active_count_increase_ *=
544 1.0 / sat_parameters_.cut_active_count_decay();
549 if (MaybeRemoveSomeInactiveConstraints(solution_state)) {
550 current_lp_is_changed_ =
true;
563 const int kBlowupFactor = 4;
564 int constraint_limit =
std::min(sat_parameters_.new_constraints_batch_size(),
565 static_cast<int>(new_constraints.size()));
566 if (lp_constraints_.empty()) {
567 constraint_limit =
std::min(1000,
static_cast<int>(new_constraints.size()));
569 VLOG(3) <<
" - size = " << new_constraints.size()
570 <<
", limit = " << constraint_limit;
572 std::stable_sort(new_constraints.begin(), new_constraints.end(),
573 [&](ConstraintIndex
a, ConstraintIndex
b) {
574 return constraint_infos_[a].current_score >
575 constraint_infos_[b].current_score;
577 if (new_constraints.size() > kBlowupFactor * constraint_limit) {
578 VLOG(3) <<
"Resize candidate constraints from " << new_constraints.size()
579 <<
" down to " << kBlowupFactor * constraint_limit;
580 new_constraints.resize(kBlowupFactor * constraint_limit);
584 int num_skipped_checks = 0;
585 const int kCheckFrequency = 100;
586 ConstraintIndex last_added_candidate = kInvalidConstraintIndex;
587 for (
int i = 0; i < constraint_limit; ++i) {
590 double best_score = 0.0;
591 ConstraintIndex best_candidate = kInvalidConstraintIndex;
592 for (
int j = 0; j < new_constraints.size(); ++j) {
594 if (++num_skipped_checks >= kCheckFrequency) {
595 if (time_limit_->
LimitReached())
return current_lp_is_changed_;
596 num_skipped_checks = 0;
599 const ConstraintIndex new_constraint = new_constraints[j];
600 if (constraint_infos_[new_constraint].is_in_lp)
continue;
602 if (last_added_candidate != kInvalidConstraintIndex) {
603 const double current_orthogonality =
605 constraint_infos_[last_added_candidate].constraint,
606 constraint_infos_[new_constraint].constraint)) /
607 (constraint_infos_[last_added_candidate].l2_norm *
608 constraint_infos_[new_constraint].l2_norm));
609 new_constraints_orthogonalities[j] =
610 std::min(new_constraints_orthogonalities[j], current_orthogonality);
618 if (new_constraints_orthogonalities[j] <
619 sat_parameters_.min_orthogonality_for_lp_constraints()) {
625 const double score = new_constraints_orthogonalities[j] +
626 constraint_infos_[new_constraint].current_score;
627 CHECK_GE(score, 0.0);
628 if (score > best_score || best_candidate == kInvalidConstraintIndex) {
630 best_candidate = new_constraint;
634 if (best_candidate != kInvalidConstraintIndex) {
636 constraint_infos_[best_candidate].is_in_lp =
true;
641 current_lp_is_changed_ =
true;
642 lp_constraints_.push_back(best_candidate);
643 last_added_candidate = best_candidate;
649 VLOG(2) <<
"Added " << num_added <<
" constraints.";
656 if (num_deletable_constraints_ > sat_parameters_.max_num_cuts()) {
657 PermanentlyRemoveSomeConstraints();
662 if (current_lp_is_changed_) {
663 current_lp_is_changed_ =
false;
670 for (ConstraintIndex i(0); i < constraint_infos_.size(); ++i) {
671 if (constraint_infos_[i].is_in_lp)
continue;
672 constraint_infos_[i].is_in_lp =
true;
673 lp_constraints_.push_back(i);
681 if (debug_solution.empty())
return true;
683 IntegerValue activity(0);
684 for (
int i = 0; i < cut.
vars.size(); ++i) {
685 const IntegerVariable
var = cut.
vars[i];
686 const IntegerValue coeff = cut.
coeffs[i];
687 activity += coeff * debug_solution[
var];
689 if (activity > cut.
ub || activity < cut.
lb) {
690 LOG(INFO) <<
"activity " << activity <<
" not in [" << cut.
lb <<
","