25 #include "absl/container/flat_hash_map.h"
26 #include "absl/meta/type_traits.h"
27 #include "absl/strings/str_cat.h"
35 #include "ortools/sat/sat_parameters.pb.h"
45 const LinearConstraintManager::ConstraintIndex kInvalidConstraintIndex(-1);
47 size_t ComputeHashOfTerms(
const LinearConstraint&
ct) {
48 DCHECK(std::is_sorted(
ct.vars.begin(),
ct.vars.end()));
50 const int num_terms =
ct.vars.size();
51 for (
int i = 0; i < num_terms; ++i) {
62 absl::StrAppend(&result,
" managed constraints: ",
64 if (num_merged_constraints_ > 0) {
65 absl::StrAppend(&result,
" merged constraints: ",
68 if (num_shortened_constraints_ > 0) {
69 absl::StrAppend(&result,
" shortened constraints: ",
72 if (num_split_constraints_ > 0) {
73 absl::StrAppend(&result,
" split constraints: ",
76 if (num_coeff_strenghtening_ > 0) {
77 absl::StrAppend(&result,
" coefficient strenghtenings: ",
80 if (num_simplifications_ > 0) {
81 absl::StrAppend(&result,
" num simplifications: ",
84 absl::StrAppend(&result,
" total cuts added: ",
FormatCounter(num_cuts_),
86 for (
const auto& entry : type_to_num_cuts_) {
87 absl::StrAppend(&result,
" - '", entry.first,
90 if (!result.empty()) result.pop_back();
98 std::vector<std::pair<std::string, int64_t>> cut_stats;
99 for (
const auto& entry : type_to_num_cuts_) {
100 cut_stats.push_back({absl::StrCat(
"cut/", entry.first), entry.second});
105 void LinearConstraintManager::RescaleActiveCounts(
const double scaling_factor) {
106 for (ConstraintIndex i(0); i < constraint_infos_.size(); ++i) {
107 constraint_infos_[i].active_count *= scaling_factor;
109 constraint_active_count_increase_ *= scaling_factor;
110 VLOG(2) <<
"Rescaled active counts by " << scaling_factor;
113 bool LinearConstraintManager::MaybeRemoveSomeInactiveConstraints(
114 glop::BasisState* solution_state) {
115 if (solution_state->IsEmpty())
return false;
116 const glop::RowIndex num_rows(lp_constraints_.size());
117 const glop::ColIndex num_cols =
120 for (
int i = 0; i < num_rows; ++i) {
121 const ConstraintIndex constraint_index = lp_constraints_[i];
130 solution_state->statuses[num_cols + glop::ColIndex(i)];
132 constraint_infos_[constraint_index].inactive_count++;
133 if (constraint_infos_[constraint_index].inactive_count >
134 sat_parameters_.max_consecutive_inactive_count()) {
135 constraint_infos_[constraint_index].is_in_lp =
false;
140 constraint_infos_[constraint_index].inactive_count = 0;
143 lp_constraints_[new_size] = constraint_index;
144 solution_state->statuses[num_cols + glop::ColIndex(new_size)] = row_status;
147 const int num_removed_constraints = lp_constraints_.size() - new_size;
148 lp_constraints_.resize(new_size);
149 solution_state->statuses.resize(num_cols + glop::ColIndex(new_size));
150 if (num_removed_constraints > 0) {
151 VLOG(2) <<
"Removed " << num_removed_constraints <<
" constraints";
153 return num_removed_constraints > 0;
161 CHECK(!
ct.vars.empty());
163 SimplifyConstraint(&
ct);
169 const size_t key = ComputeHashOfTerms(
ct);
170 if (equiv_constraints_.contains(key)) {
171 const ConstraintIndex ct_index = equiv_constraints_[key];
172 if (constraint_infos_[ct_index].constraint.vars ==
ct.vars &&
173 constraint_infos_[ct_index].constraint.coeffs ==
ct.coeffs) {
174 if (added !=
nullptr) *added =
false;
175 if (
ct.lb > constraint_infos_[ct_index].constraint.lb) {
176 if (constraint_infos_[ct_index].is_in_lp) current_lp_is_changed_ =
true;
177 constraint_infos_[ct_index].constraint.lb =
ct.lb;
178 if (added !=
nullptr) *added =
true;
180 if (
ct.ub < constraint_infos_[ct_index].constraint.ub) {
181 if (constraint_infos_[ct_index].is_in_lp) current_lp_is_changed_ =
true;
182 constraint_infos_[ct_index].constraint.ub =
ct.ub;
183 if (added !=
nullptr) *added =
true;
185 ++num_merged_constraints_;
190 if (added !=
nullptr) *added =
true;
191 const ConstraintIndex ct_index(constraint_infos_.size());
196 equiv_constraints_[key] = ct_index;
197 ct_info.
active_count = constraint_active_count_increase_;
198 constraint_infos_.push_back(std::move(ct_info));
202 void LinearConstraintManager::ComputeObjectiveParallelism(
203 const ConstraintIndex ct_index) {
204 CHECK(objective_is_defined_);
206 if (!objective_norm_computed_) {
207 objective_l2_norm_ = std::sqrt(sum_of_squared_objective_coeffs_);
208 objective_norm_computed_ =
true;
210 CHECK_GT(objective_l2_norm_, 0.0);
212 constraint_infos_[ct_index].objective_parallelism_computed =
true;
213 if (constraint_infos_[ct_index].
l2_norm == 0.0) {
214 constraint_infos_[ct_index].objective_parallelism = 0.0;
218 const LinearConstraint& lc = constraint_infos_[ct_index].constraint;
219 double unscaled_objective_parallelism = 0.0;
220 for (
int i = 0; i < lc.vars.size(); ++i) {
221 const IntegerVariable
var = lc.vars[i];
222 const auto it = objective_map_.find(
var);
223 if (it == objective_map_.end())
continue;
224 unscaled_objective_parallelism += it->second *
ToDouble(lc.coeffs[i]);
226 const double objective_parallelism =
227 unscaled_objective_parallelism /
228 (constraint_infos_[ct_index].l2_norm * objective_l2_norm_);
229 constraint_infos_[ct_index].objective_parallelism =
230 std::abs(objective_parallelism);
238 std::string extra_info) {
239 ++num_add_cut_calls_;
240 if (
ct.vars.empty())
return false;
243 const double violation =
248 if (violation /
l2_norm < 1e-5) {
249 VLOG(2) <<
"BAD Cut '" << type_name <<
"'"
250 <<
" size=" <<
ct.vars.size()
252 <<
" norm=" <<
l2_norm <<
" violation=" << violation
253 <<
" eff=" << violation /
l2_norm <<
" " << extra_info;
258 const ConstraintIndex ct_index =
Add(
ct, &added);
262 if (!added)
return false;
266 constraint_infos_[ct_index].is_deletable =
true;
268 VLOG(2) <<
"Cut '" << type_name <<
"'"
269 <<
" size=" << constraint_infos_[ct_index].constraint.vars.size()
272 <<
" norm=" <<
l2_norm <<
" violation=" << violation
273 <<
" eff=" << violation /
l2_norm <<
" " << extra_info;
276 num_deletable_constraints_++;
277 type_to_num_cuts_[type_name]++;
281 void LinearConstraintManager::PermanentlyRemoveSomeConstraints() {
282 std::vector<double> deletable_constraint_counts;
283 for (ConstraintIndex i(0); i < constraint_infos_.size(); ++i) {
284 if (constraint_infos_[i].is_deletable && !constraint_infos_[i].is_in_lp) {
285 deletable_constraint_counts.push_back(constraint_infos_[i].active_count);
288 if (deletable_constraint_counts.empty())
return;
289 std::sort(deletable_constraint_counts.begin(),
290 deletable_constraint_counts.end());
294 double active_count_threshold = std::numeric_limits<double>::infinity();
295 if (sat_parameters_.cut_cleanup_target() <
296 deletable_constraint_counts.size()) {
297 active_count_threshold =
298 deletable_constraint_counts[sat_parameters_.cut_cleanup_target()];
301 ConstraintIndex new_size(0);
302 equiv_constraints_.clear();
304 constraint_infos_.size());
305 int num_deleted_constraints = 0;
306 for (ConstraintIndex i(0); i < constraint_infos_.size(); ++i) {
307 if (constraint_infos_[i].is_deletable && !constraint_infos_[i].is_in_lp &&
308 constraint_infos_[i].active_count <= active_count_threshold &&
309 num_deleted_constraints < sat_parameters_.cut_cleanup_target()) {
310 ++num_deleted_constraints;
315 constraint_infos_[new_size] = std::move(constraint_infos_[i]);
317 index_mapping[i] = new_size;
320 equiv_constraints_[constraint_infos_[new_size].hash] = new_size;
323 constraint_infos_.resize(new_size.value());
326 for (
int i = 0; i < lp_constraints_.size(); ++i) {
327 lp_constraints_[i] = index_mapping[lp_constraints_[i]];
330 if (num_deleted_constraints > 0) {
331 VLOG(2) <<
"Constraint manager cleanup: #deleted:"
332 << num_deleted_constraints;
334 num_deletable_constraints_ -= num_deleted_constraints;
338 IntegerValue coeff) {
339 if (coeff == IntegerValue(0))
return;
340 objective_is_defined_ =
true;
345 const double coeff_as_double =
ToDouble(coeff);
346 const auto insert = objective_map_.insert({
var, coeff_as_double});
348 <<
"SetObjectiveCoefficient() called twice with same variable";
349 sum_of_squared_objective_coeffs_ += coeff_as_double * coeff_as_double;
354 bool term_changed =
false;
356 IntegerValue min_sum(0);
357 IntegerValue max_sum(0);
358 IntegerValue max_magnitude(0);
361 const int num_terms =
ct->vars.size();
362 for (
int i = 0; i < num_terms; ++i) {
363 const IntegerVariable
var =
ct->vars[i];
364 const IntegerValue coeff =
ct->coeffs[i];
370 if (lb == ub)
continue;
373 const IntegerValue magnitude =
IntTypeAbs(coeff);
374 max_magnitude =
std::max(max_magnitude, magnitude);
375 min_magnitude =
std::min(min_magnitude, magnitude);
377 min_sum += coeff * lb;
378 max_sum += coeff * ub;
380 min_sum += coeff * ub;
381 max_sum += coeff * lb;
386 if (new_size < num_terms) {
388 ++num_shortened_constraints_;
390 for (
int i = 0; i < num_terms; ++i) {
391 const IntegerVariable
var =
ct->vars[i];
392 const IntegerValue coeff =
ct->coeffs[i];
396 const IntegerValue rhs_adjust = lb * coeff;
401 ct->vars[new_size] =
var;
402 ct->coeffs[new_size] = coeff;
405 ct->vars.resize(new_size);
406 ct->coeffs.resize(new_size);
428 const IntegerValue threshold = max_sum -
ct->ub;
429 const IntegerValue second_threshold =
std::max(
430 CeilRatio(threshold, IntegerValue(2)), threshold - min_magnitude);
431 if (max_magnitude > second_threshold) {
433 ++num_split_constraints_;
436 ++num_coeff_strenghtening_;
437 const int num_terms =
ct->vars.size();
438 for (
int i = 0; i < num_terms; ++i) {
443 const IntegerValue coeff =
ct->coeffs[i];
444 if (coeff > threshold) {
445 const IntegerVariable
var =
ct->vars[i];
447 ct->coeffs[i] = threshold;
448 ct->ub -= (coeff - threshold) * ub;
449 }
else if (coeff > second_threshold && coeff < threshold) {
450 const IntegerVariable
var =
ct->vars[i];
452 ct->coeffs[i] = second_threshold;
453 ct->ub -= (coeff - second_threshold) * ub;
454 }
else if (coeff < -threshold) {
455 const IntegerVariable
var =
ct->vars[i];
457 ct->coeffs[i] = -threshold;
458 ct->ub -= (coeff + threshold) * lb;
459 }
else if (coeff < -second_threshold && coeff > -threshold) {
460 const IntegerVariable
var =
ct->vars[i];
462 ct->coeffs[i] = -second_threshold;
463 ct->ub -= (coeff + second_threshold) * lb;
471 const IntegerValue threshold =
ct->lb - min_sum;
472 const IntegerValue second_threshold =
std::max(
473 CeilRatio(threshold, IntegerValue(2)), threshold - min_magnitude);
474 if (max_magnitude > second_threshold) {
476 ++num_split_constraints_;
479 ++num_coeff_strenghtening_;
480 const int num_terms =
ct->vars.size();
481 for (
int i = 0; i < num_terms; ++i) {
482 const IntegerValue coeff =
ct->coeffs[i];
483 if (coeff > threshold) {
484 const IntegerVariable
var =
ct->vars[i];
486 ct->coeffs[i] = threshold;
487 ct->lb -= (coeff - threshold) * lb;
488 }
else if (coeff > second_threshold && coeff < threshold) {
489 const IntegerVariable
var =
ct->vars[i];
491 ct->coeffs[i] = second_threshold;
492 ct->lb -= (coeff - second_threshold) * lb;
493 }
else if (coeff < -threshold) {
494 const IntegerVariable
var =
ct->vars[i];
496 ct->coeffs[i] = -threshold;
497 ct->lb -= (coeff + threshold) * ub;
498 }
else if (coeff < -second_threshold && coeff > -threshold) {
499 const IntegerVariable
var =
ct->vars[i];
501 ct->coeffs[i] = -second_threshold;
502 ct->lb -= (coeff + second_threshold) * ub;
515 VLOG(3) <<
"Enter ChangeLP, scan " << constraint_infos_.size()
517 const double saved_dtime = dtime_;
518 std::vector<ConstraintIndex> new_constraints;
519 std::vector<double> new_constraints_efficacies;
520 std::vector<double> new_constraints_orthogonalities;
522 const bool simplify_constraints =
529 bool rescale_active_count =
false;
530 const double tolerance = 1e-6;
531 for (ConstraintIndex i(0); i < constraint_infos_.size(); ++i) {
533 if (simplify_constraints &&
534 SimplifyConstraint(&constraint_infos_[i].constraint)) {
535 ++num_simplifications_;
543 constraint_infos_[i].objective_parallelism_computed =
false;
544 constraint_infos_[i].l2_norm =
547 if (constraint_infos_[i].is_in_lp) current_lp_is_changed_ =
true;
548 equiv_constraints_.erase(constraint_infos_[i].
hash);
549 constraint_infos_[i].hash =
550 ComputeHashOfTerms(constraint_infos_[i].constraint);
554 equiv_constraints_[constraint_infos_[i].hash] = i;
557 if (constraint_infos_[i].is_in_lp)
continue;
562 static_cast<double>(constraint_infos_[i].constraint.vars.size());
563 const double activity =
565 const double lb_violation =
566 ToDouble(constraint_infos_[i].constraint.lb) - activity;
567 const double ub_violation =
568 activity -
ToDouble(constraint_infos_[i].constraint.ub);
569 const double violation =
std::max(lb_violation, ub_violation);
570 if (violation >= tolerance) {
571 constraint_infos_[i].inactive_count = 0;
572 new_constraints.push_back(i);
573 new_constraints_efficacies.push_back(violation /
575 new_constraints_orthogonalities.push_back(1.0);
577 if (objective_is_defined_ &&
578 !constraint_infos_[i].objective_parallelism_computed) {
579 ComputeObjectiveParallelism(i);
580 }
else if (!objective_is_defined_) {
581 constraint_infos_[i].objective_parallelism = 0.0;
584 constraint_infos_[i].current_score =
585 new_constraints_efficacies.back() +
586 constraint_infos_[i].objective_parallelism;
588 if (constraint_infos_[i].is_deletable) {
589 constraint_infos_[i].active_count += constraint_active_count_increase_;
590 if (constraint_infos_[i].active_count >
591 sat_parameters_.cut_max_active_count_value()) {
592 rescale_active_count =
true;
599 if (solution_state !=
nullptr) {
600 const glop::RowIndex num_rows(lp_constraints_.size());
601 const glop::ColIndex num_cols =
604 for (
int i = 0; i < num_rows; ++i) {
605 const ConstraintIndex constraint_index = lp_constraints_[i];
607 solution_state->
statuses[num_cols + glop::ColIndex(i)];
609 constraint_infos_[constraint_index].is_deletable) {
610 constraint_infos_[constraint_index].active_count +=
611 constraint_active_count_increase_;
612 if (constraint_infos_[constraint_index].active_count >
613 sat_parameters_.cut_max_active_count_value()) {
614 rescale_active_count =
true;
620 if (rescale_active_count) {
621 CHECK_GT(sat_parameters_.cut_max_active_count_value(), 0.0);
622 RescaleActiveCounts(1.0 / sat_parameters_.cut_max_active_count_value());
626 constraint_active_count_increase_ *=
627 1.0 / sat_parameters_.cut_active_count_decay();
632 if (MaybeRemoveSomeInactiveConstraints(solution_state)) {
633 current_lp_is_changed_ =
true;
646 const int kBlowupFactor = 4;
647 int constraint_limit =
std::min(sat_parameters_.new_constraints_batch_size(),
648 static_cast<int>(new_constraints.size()));
649 if (lp_constraints_.empty()) {
650 constraint_limit =
std::min(1000,
static_cast<int>(new_constraints.size()));
652 VLOG(3) <<
" - size = " << new_constraints.size()
653 <<
", limit = " << constraint_limit;
655 std::stable_sort(new_constraints.begin(), new_constraints.end(),
656 [&](ConstraintIndex
a, ConstraintIndex
b) {
657 return constraint_infos_[a].current_score >
658 constraint_infos_[b].current_score;
660 if (new_constraints.size() > kBlowupFactor * constraint_limit) {
661 VLOG(3) <<
"Resize candidate constraints from " << new_constraints.size()
662 <<
" down to " << kBlowupFactor * constraint_limit;
663 new_constraints.resize(kBlowupFactor * constraint_limit);
667 int num_skipped_checks = 0;
668 const int kCheckFrequency = 100;
669 ConstraintIndex last_added_candidate = kInvalidConstraintIndex;
670 for (
int i = 0; i < constraint_limit; ++i) {
673 double best_score = 0.0;
674 ConstraintIndex best_candidate = kInvalidConstraintIndex;
675 for (
int j = 0; j < new_constraints.size(); ++j) {
677 if (++num_skipped_checks >= kCheckFrequency) {
678 if (time_limit_->
LimitReached())
return current_lp_is_changed_;
679 num_skipped_checks = 0;
682 const ConstraintIndex new_constraint = new_constraints[j];
683 if (constraint_infos_[new_constraint].is_in_lp)
continue;
685 if (last_added_candidate != kInvalidConstraintIndex) {
686 const double current_orthogonality =
688 constraint_infos_[last_added_candidate].constraint,
689 constraint_infos_[new_constraint].constraint)) /
690 (constraint_infos_[last_added_candidate].l2_norm *
691 constraint_infos_[new_constraint].l2_norm));
692 new_constraints_orthogonalities[j] =
693 std::min(new_constraints_orthogonalities[j], current_orthogonality);
701 if (new_constraints_orthogonalities[j] <
702 sat_parameters_.min_orthogonality_for_lp_constraints()) {
708 const double score = new_constraints_orthogonalities[j] +
709 constraint_infos_[new_constraint].current_score;
710 CHECK_GE(score, 0.0);
711 if (score > best_score || best_candidate == kInvalidConstraintIndex) {
713 best_candidate = new_constraint;
717 if (best_candidate != kInvalidConstraintIndex) {
719 constraint_infos_[best_candidate].is_in_lp =
true;
724 current_lp_is_changed_ =
true;
725 lp_constraints_.push_back(best_candidate);
726 last_added_candidate = best_candidate;
730 if (num_new_constraints !=
nullptr) {
731 *num_new_constraints = num_added;
735 VLOG(2) <<
"Added " << num_added <<
" constraints.";
742 if (num_deletable_constraints_ > sat_parameters_.max_num_cuts()) {
743 PermanentlyRemoveSomeConstraints();
750 if (current_lp_is_changed_) {
751 current_lp_is_changed_ =
false;
758 for (ConstraintIndex i(0); i < constraint_infos_.size(); ++i) {
759 if (constraint_infos_[i].is_in_lp)
continue;
760 constraint_infos_[i].is_in_lp =
true;
761 lp_constraints_.push_back(i);
770 IntegerValue activity(0);
771 for (
int i = 0; i < cut.
vars.size(); ++i) {
772 const IntegerVariable
var = cut.
vars[i];
773 const IntegerValue coeff = cut.
coeffs[i];
774 CHECK(debug_solution.ivar_has_value[
var]);
775 activity += coeff * debug_solution.ivar_values[
var];
777 if (activity > cut.
ub || activity < cut.
lb) {
779 LOG(INFO) <<
"activity " << activity <<
" not in [" << cut.
lb <<
","
789 if (
ct.vars.empty())
return;
791 const double violation =
801 manager->
AddCut(candidate.cut, candidate.name, lp_solution);
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
~LinearConstraintManager()
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 ChangeLp(const absl::StrongVector< IntegerVariable, double > &lp_solution, glop::BasisState *solution_state, int *num_new_constraints=nullptr)
bool AddCut(const 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.
T * Mutable() const
Same as Get(), but returns a mutable version of the object.
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)
const std::vector< Element > & UnorderedElements() const
void Add(Element e, double score)
ColIndex RowToColIndex(RowIndex row)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
IntType IntTypeAbs(IntType t)
IntegerValue CeilRatio(IntegerValue dividend, IntegerValue positive_divisor)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
double ScalarProduct(const LinearConstraint &constraint1, const LinearConstraint &constraint2)
void CanonicalizeConstraint(LinearConstraint *ct)
bool NoDuplicateVariable(const LinearConstraint &ct)
double ComputeL2Norm(const LinearConstraint &constraint)
std::string FormatCounter(int64_t num)
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
std::string DebugString() const
LinearConstraint constraint
#define VLOG(verboselevel)
#define VLOG_IS_ON(verboselevel)