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_ <<
" (out of "
60 << num_add_cut_calls_ <<
" calls) worker: '" << model_->
Name()
62 LOG(INFO) <<
"Num simplifications: " << num_simplifications_;
63 for (
const auto& entry : type_to_num_cuts_) {
64 LOG(INFO) <<
"Added " << entry.second <<
" cuts of type '" << entry.first
70 void LinearConstraintManager::RescaleActiveCounts(
const double scaling_factor) {
71 for (ConstraintIndex i(0); i < constraint_infos_.size(); ++i) {
72 constraint_infos_[i].active_count *= scaling_factor;
74 constraint_active_count_increase_ *= scaling_factor;
75 VLOG(2) <<
"Rescaled active counts by " << scaling_factor;
78 bool LinearConstraintManager::MaybeRemoveSomeInactiveConstraints(
79 glop::BasisState* solution_state) {
80 if (solution_state->IsEmpty())
return false;
81 const glop::RowIndex num_rows(lp_constraints_.size());
82 const glop::ColIndex num_cols =
85 for (
int i = 0; i < num_rows; ++i) {
86 const ConstraintIndex constraint_index = lp_constraints_[i];
95 solution_state->statuses[num_cols + glop::ColIndex(i)];
97 constraint_infos_[constraint_index].inactive_count++;
98 if (constraint_infos_[constraint_index].inactive_count >
99 sat_parameters_.max_consecutive_inactive_count()) {
100 constraint_infos_[constraint_index].is_in_lp =
false;
105 constraint_infos_[constraint_index].inactive_count = 0;
108 lp_constraints_[new_size] = constraint_index;
109 solution_state->statuses[num_cols + glop::ColIndex(new_size)] = row_status;
112 const int num_removed_constraints = lp_constraints_.size() - new_size;
113 lp_constraints_.resize(new_size);
114 solution_state->statuses.resize(num_cols + glop::ColIndex(new_size));
115 if (num_removed_constraints > 0) {
116 VLOG(2) <<
"Removed " << num_removed_constraints <<
" constraints";
118 return num_removed_constraints > 0;
126 CHECK(!
ct.vars.empty());
128 SimplifyConstraint(&
ct);
134 const size_t key = ComputeHashOfTerms(
ct);
136 const ConstraintIndex ct_index = equiv_constraints_[key];
137 if (constraint_infos_[ct_index].constraint.vars ==
ct.vars &&
138 constraint_infos_[ct_index].constraint.coeffs ==
ct.coeffs) {
139 if (added !=
nullptr) *added =
false;
140 if (
ct.lb > constraint_infos_[ct_index].constraint.lb) {
141 if (constraint_infos_[ct_index].is_in_lp) current_lp_is_changed_ =
true;
142 constraint_infos_[ct_index].constraint.lb =
ct.lb;
143 if (added !=
nullptr) *added =
true;
145 if (
ct.ub < constraint_infos_[ct_index].constraint.ub) {
146 if (constraint_infos_[ct_index].is_in_lp) current_lp_is_changed_ =
true;
147 constraint_infos_[ct_index].constraint.ub =
ct.ub;
148 if (added !=
nullptr) *added =
true;
150 ++num_merged_constraints_;
155 if (added !=
nullptr) *added =
true;
156 const ConstraintIndex ct_index(constraint_infos_.size());
161 equiv_constraints_[key] = ct_index;
162 ct_info.
active_count = constraint_active_count_increase_;
163 constraint_infos_.push_back(std::move(ct_info));
167 void LinearConstraintManager::ComputeObjectiveParallelism(
168 const ConstraintIndex ct_index) {
169 CHECK(objective_is_defined_);
171 if (!objective_norm_computed_) {
173 for (
const double coeff : dense_objective_coeffs_) {
174 sum += coeff * coeff;
176 objective_l2_norm_ = std::sqrt(sum);
177 objective_norm_computed_ =
true;
179 CHECK_GT(objective_l2_norm_, 0.0);
181 constraint_infos_[ct_index].objective_parallelism_computed =
true;
182 if (constraint_infos_[ct_index].l2_norm == 0.0) {
183 constraint_infos_[ct_index].objective_parallelism = 0.0;
187 const LinearConstraint& lc = constraint_infos_[ct_index].constraint;
188 double unscaled_objective_parallelism = 0.0;
189 for (
int i = 0; i < lc.vars.size(); ++i) {
190 const IntegerVariable
var = lc.vars[i];
192 if (
var < dense_objective_coeffs_.
size()) {
193 unscaled_objective_parallelism +=
194 ToDouble(lc.coeffs[i]) * dense_objective_coeffs_[
var];
197 const double objective_parallelism =
198 unscaled_objective_parallelism /
199 (constraint_infos_[ct_index].l2_norm * objective_l2_norm_);
200 constraint_infos_[ct_index].objective_parallelism =
201 std::abs(objective_parallelism);
209 std::string extra_info) {
210 ++num_add_cut_calls_;
211 if (
ct.vars.empty())
return false;
214 const double violation =
219 if (violation / l2_norm < 1e-5)
return false;
222 const ConstraintIndex ct_index =
Add(std::move(
ct), &added);
226 if (!added)
return false;
230 constraint_infos_[ct_index].is_deletable =
true;
232 VLOG(1) <<
"Cut '" << type_name <<
"'"
233 <<
" size=" << constraint_infos_[ct_index].constraint.vars.size()
236 <<
" norm=" << l2_norm <<
" violation=" << violation
237 <<
" eff=" << violation / l2_norm <<
" " << extra_info;
240 num_deletable_constraints_++;
241 type_to_num_cuts_[type_name]++;
245 void LinearConstraintManager::PermanentlyRemoveSomeConstraints() {
246 std::vector<double> deletable_constraint_counts;
247 for (ConstraintIndex i(0); i < constraint_infos_.size(); ++i) {
248 if (constraint_infos_[i].is_deletable && !constraint_infos_[i].is_in_lp) {
249 deletable_constraint_counts.push_back(constraint_infos_[i].active_count);
252 if (deletable_constraint_counts.empty())
return;
253 std::sort(deletable_constraint_counts.begin(),
254 deletable_constraint_counts.end());
258 double active_count_threshold = std::numeric_limits<double>::infinity();
259 if (sat_parameters_.cut_cleanup_target() <
260 deletable_constraint_counts.size()) {
261 active_count_threshold =
262 deletable_constraint_counts[sat_parameters_.cut_cleanup_target()];
265 ConstraintIndex new_size(0);
266 equiv_constraints_.clear();
268 constraint_infos_.size());
269 int num_deleted_constraints = 0;
270 for (ConstraintIndex i(0); i < constraint_infos_.size(); ++i) {
271 if (constraint_infos_[i].is_deletable && !constraint_infos_[i].is_in_lp &&
272 constraint_infos_[i].active_count <= active_count_threshold &&
273 num_deleted_constraints < sat_parameters_.cut_cleanup_target()) {
274 ++num_deleted_constraints;
279 constraint_infos_[new_size] = std::move(constraint_infos_[i]);
281 index_mapping[i] = new_size;
284 equiv_constraints_[constraint_infos_[new_size].hash] = new_size;
287 constraint_infos_.resize(new_size.value());
290 for (
int i = 0; i < lp_constraints_.size(); ++i) {
291 lp_constraints_[i] = index_mapping[lp_constraints_[i]];
294 if (num_deleted_constraints > 0) {
295 VLOG(1) <<
"Constraint manager cleanup: #deleted:"
296 << num_deleted_constraints;
298 num_deletable_constraints_ -= num_deleted_constraints;
302 IntegerValue coeff) {
303 if (coeff == IntegerValue(0))
return;
304 objective_is_defined_ =
true;
309 if (
var.value() >= dense_objective_coeffs_.
size()) {
310 dense_objective_coeffs_.
resize(
var.value() + 1, 0.0);
316 bool term_changed =
false;
318 IntegerValue min_sum(0);
319 IntegerValue max_sum(0);
320 IntegerValue max_magnitude(0);
322 const int num_terms =
ct->vars.size();
323 for (
int i = 0; i < num_terms; ++i) {
324 const IntegerVariable
var =
ct->vars[i];
325 const IntegerValue coeff =
ct->coeffs[i];
331 if (lb == ub)
continue;
336 min_sum += coeff * lb;
337 max_sum += coeff * ub;
339 min_sum += coeff * ub;
340 max_sum += coeff * lb;
345 if (new_size < num_terms) {
347 ++num_shortened_constraints_;
349 for (
int i = 0; i < num_terms; ++i) {
350 const IntegerVariable
var =
ct->vars[i];
351 const IntegerValue coeff =
ct->coeffs[i];
355 const IntegerValue rhs_adjust = lb * coeff;
360 ct->vars[new_size] =
var;
361 ct->coeffs[new_size] = coeff;
364 ct->vars.resize(new_size);
365 ct->coeffs.resize(new_size);
389 ++num_splitted_constraints_;
392 ++num_coeff_strenghtening_;
393 const int num_terms =
ct->vars.size();
394 const IntegerValue target = max_sum -
ct->ub;
395 for (
int i = 0; i < num_terms; ++i) {
396 const IntegerValue coeff =
ct->coeffs[i];
397 if (coeff > target) {
398 const IntegerVariable
var =
ct->vars[i];
400 ct->coeffs[i] = target;
401 ct->ub -= (coeff - target) * ub;
402 }
else if (coeff < -target) {
403 const IntegerVariable
var =
ct->vars[i];
405 ct->coeffs[i] = -target;
406 ct->ub += (-target - coeff) * lb;
414 ++num_splitted_constraints_;
417 ++num_coeff_strenghtening_;
418 const int num_terms =
ct->vars.size();
419 const IntegerValue target =
ct->lb - min_sum;
420 for (
int i = 0; i < num_terms; ++i) {
421 const IntegerValue coeff =
ct->coeffs[i];
422 if (coeff > target) {
423 const IntegerVariable
var =
ct->vars[i];
425 ct->coeffs[i] = target;
426 ct->lb -= (coeff - target) * lb;
427 }
else if (coeff < -target) {
428 const IntegerVariable
var =
ct->vars[i];
430 ct->coeffs[i] = -target;
431 ct->lb += (-target - coeff) * ub;
443 VLOG(3) <<
"Enter ChangeLP, scan " << constraint_infos_.size()
445 const double saved_dtime = dtime_;
446 std::vector<ConstraintIndex> new_constraints;
447 std::vector<double> new_constraints_efficacies;
448 std::vector<double> new_constraints_orthogonalities;
450 const bool simplify_constraints =
457 bool rescale_active_count =
false;
458 const double tolerance = 1e-6;
459 for (ConstraintIndex i(0); i < constraint_infos_.size(); ++i) {
461 if (simplify_constraints &&
462 SimplifyConstraint(&constraint_infos_[i].constraint)) {
463 ++num_simplifications_;
471 constraint_infos_[i].objective_parallelism_computed =
false;
472 constraint_infos_[i].l2_norm =
475 if (constraint_infos_[i].is_in_lp) current_lp_is_changed_ =
true;
476 equiv_constraints_.erase(constraint_infos_[i].
hash);
477 constraint_infos_[i].hash =
478 ComputeHashOfTerms(constraint_infos_[i].constraint);
482 equiv_constraints_[constraint_infos_[i].hash] = i;
485 if (constraint_infos_[i].is_in_lp)
continue;
490 static_cast<double>(constraint_infos_[i].constraint.vars.size());
491 const double activity =
493 const double lb_violation =
494 ToDouble(constraint_infos_[i].constraint.lb) - activity;
495 const double ub_violation =
496 activity -
ToDouble(constraint_infos_[i].constraint.ub);
497 const double violation =
std::max(lb_violation, ub_violation);
498 if (violation >= tolerance) {
499 constraint_infos_[i].inactive_count = 0;
500 new_constraints.push_back(i);
501 new_constraints_efficacies.push_back(violation /
502 constraint_infos_[i].l2_norm);
503 new_constraints_orthogonalities.push_back(1.0);
505 if (objective_is_defined_ &&
506 !constraint_infos_[i].objective_parallelism_computed) {
507 ComputeObjectiveParallelism(i);
508 }
else if (!objective_is_defined_) {
509 constraint_infos_[i].objective_parallelism = 0.0;
512 constraint_infos_[i].current_score =
513 new_constraints_efficacies.back() +
514 constraint_infos_[i].objective_parallelism;
516 if (constraint_infos_[i].is_deletable) {
517 constraint_infos_[i].active_count += constraint_active_count_increase_;
518 if (constraint_infos_[i].active_count >
519 sat_parameters_.cut_max_active_count_value()) {
520 rescale_active_count =
true;
527 if (solution_state !=
nullptr) {
528 const glop::RowIndex num_rows(lp_constraints_.size());
529 const glop::ColIndex num_cols =
532 for (
int i = 0; i < num_rows; ++i) {
533 const ConstraintIndex constraint_index = lp_constraints_[i];
535 solution_state->
statuses[num_cols + glop::ColIndex(i)];
537 constraint_infos_[constraint_index].is_deletable) {
538 constraint_infos_[constraint_index].active_count +=
539 constraint_active_count_increase_;
540 if (constraint_infos_[constraint_index].active_count >
541 sat_parameters_.cut_max_active_count_value()) {
542 rescale_active_count =
true;
548 if (rescale_active_count) {
549 CHECK_GT(sat_parameters_.cut_max_active_count_value(), 0.0);
550 RescaleActiveCounts(1.0 / sat_parameters_.cut_max_active_count_value());
554 constraint_active_count_increase_ *=
555 1.0 / sat_parameters_.cut_active_count_decay();
560 if (MaybeRemoveSomeInactiveConstraints(solution_state)) {
561 current_lp_is_changed_ =
true;
574 const int kBlowupFactor = 4;
575 int constraint_limit =
std::min(sat_parameters_.new_constraints_batch_size(),
576 static_cast<int>(new_constraints.size()));
577 if (lp_constraints_.empty()) {
578 constraint_limit =
std::min(1000,
static_cast<int>(new_constraints.size()));
580 VLOG(3) <<
" - size = " << new_constraints.size()
581 <<
", limit = " << constraint_limit;
583 std::stable_sort(new_constraints.begin(), new_constraints.end(),
584 [&](ConstraintIndex
a, ConstraintIndex
b) {
585 return constraint_infos_[a].current_score >
586 constraint_infos_[b].current_score;
588 if (new_constraints.size() > kBlowupFactor * constraint_limit) {
589 VLOG(3) <<
"Resize candidate constraints from " << new_constraints.size()
590 <<
" down to " << kBlowupFactor * constraint_limit;
591 new_constraints.resize(kBlowupFactor * constraint_limit);
595 int num_skipped_checks = 0;
596 const int kCheckFrequency = 100;
597 ConstraintIndex last_added_candidate = kInvalidConstraintIndex;
598 for (
int i = 0; i < constraint_limit; ++i) {
601 double best_score = 0.0;
602 ConstraintIndex best_candidate = kInvalidConstraintIndex;
603 for (
int j = 0; j < new_constraints.size(); ++j) {
605 if (++num_skipped_checks >= kCheckFrequency) {
606 if (time_limit_->
LimitReached())
return current_lp_is_changed_;
607 num_skipped_checks = 0;
610 const ConstraintIndex new_constraint = new_constraints[j];
611 if (constraint_infos_[new_constraint].is_in_lp)
continue;
613 if (last_added_candidate != kInvalidConstraintIndex) {
614 const double current_orthogonality =
616 constraint_infos_[last_added_candidate].constraint,
617 constraint_infos_[new_constraint].constraint)) /
618 (constraint_infos_[last_added_candidate].l2_norm *
619 constraint_infos_[new_constraint].l2_norm));
620 new_constraints_orthogonalities[j] =
621 std::min(new_constraints_orthogonalities[j], current_orthogonality);
629 if (new_constraints_orthogonalities[j] <
630 sat_parameters_.min_orthogonality_for_lp_constraints()) {
636 const double score = new_constraints_orthogonalities[j] +
637 constraint_infos_[new_constraint].current_score;
638 CHECK_GE(score, 0.0);
639 if (score > best_score || best_candidate == kInvalidConstraintIndex) {
641 best_candidate = new_constraint;
645 if (best_candidate != kInvalidConstraintIndex) {
647 constraint_infos_[best_candidate].is_in_lp =
true;
652 current_lp_is_changed_ =
true;
653 lp_constraints_.push_back(best_candidate);
654 last_added_candidate = best_candidate;
660 VLOG(2) <<
"Added " << num_added <<
" constraints.";
667 if (num_deletable_constraints_ > sat_parameters_.max_num_cuts()) {
668 PermanentlyRemoveSomeConstraints();
675 if (current_lp_is_changed_) {
676 current_lp_is_changed_ =
false;
683 for (ConstraintIndex i(0); i < constraint_infos_.size(); ++i) {
684 if (constraint_infos_[i].is_in_lp)
continue;
685 constraint_infos_[i].is_in_lp =
true;
686 lp_constraints_.push_back(i);
694 if (debug_solution.empty())
return true;
696 IntegerValue activity(0);
697 for (
int i = 0; i < cut.
vars.size(); ++i) {
698 const IntegerVariable
var = cut.
vars[i];
699 const IntegerValue coeff = cut.
coeffs[i];
700 activity += coeff * debug_solution[
var];
702 if (activity > cut.
ub || activity < cut.
lb) {
703 LOG(INFO) <<
"activity " << activity <<
" not in [" << cut.
lb <<
","
713 if (
ct.vars.empty())
return;
715 const double violation =
718 cuts_.
Add({
name,
ct}, violation / l2_norm);
725 manager->
AddCut(candidate.cut, candidate.name, lp_solution);