25#include "absl/container/btree_map.h"
26#include "absl/container/flat_hash_map.h"
27#include "absl/meta/type_traits.h"
28#include "absl/strings/str_cat.h"
36#include "ortools/sat/sat_parameters.pb.h"
45const LinearConstraintManager::ConstraintIndex kInvalidConstraintIndex(-1);
47size_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: ", constraint_infos_.size(),
64 if (num_merged_constraints_ > 0) {
65 absl::StrAppend(&result,
" merged constraints: ", num_merged_constraints_,
68 if (num_shortened_constraints_ > 0) {
70 &result,
" shortened constraints: ", num_shortened_constraints_,
"\n");
72 if (num_splitted_constraints_ > 0) {
74 &result,
" splitted constraints: ", num_splitted_constraints_,
"\n");
76 if (num_coeff_strenghtening_ > 0) {
77 absl::StrAppend(&result,
78 " coefficient strenghtenings: ", num_coeff_strenghtening_,
81 if (num_simplifications_ > 0) {
82 absl::StrAppend(&result,
" num simplifications: ", num_simplifications_,
85 absl::StrAppend(&result,
" total cuts added: ", num_cuts_,
" (out of ",
86 num_add_cut_calls_,
" calls)\n");
87 for (
const auto& entry : type_to_num_cuts_) {
88 absl::StrAppend(&result,
" - '", entry.first,
"': ", entry.second,
"\n");
90 if (!result.empty()) result.pop_back();
94void LinearConstraintManager::RescaleActiveCounts(
const double scaling_factor) {
95 for (ConstraintIndex i(0); i < constraint_infos_.size(); ++i) {
96 constraint_infos_[i].active_count *= scaling_factor;
98 constraint_active_count_increase_ *= scaling_factor;
99 VLOG(2) <<
"Rescaled active counts by " << scaling_factor;
102bool LinearConstraintManager::MaybeRemoveSomeInactiveConstraints(
103 glop::BasisState* solution_state) {
104 if (solution_state->IsEmpty())
return false;
105 const glop::RowIndex num_rows(lp_constraints_.size());
106 const glop::ColIndex num_cols =
109 for (
int i = 0; i < num_rows; ++i) {
110 const ConstraintIndex constraint_index = lp_constraints_[i];
119 solution_state->statuses[num_cols + glop::ColIndex(i)];
121 constraint_infos_[constraint_index].inactive_count++;
122 if (constraint_infos_[constraint_index].inactive_count >
123 sat_parameters_.max_consecutive_inactive_count()) {
124 constraint_infos_[constraint_index].is_in_lp =
false;
129 constraint_infos_[constraint_index].inactive_count = 0;
132 lp_constraints_[new_size] = constraint_index;
133 solution_state->statuses[num_cols + glop::ColIndex(new_size)] = row_status;
136 const int num_removed_constraints = lp_constraints_.size() - new_size;
137 lp_constraints_.resize(new_size);
138 solution_state->statuses.resize(num_cols + glop::ColIndex(new_size));
139 if (num_removed_constraints > 0) {
140 VLOG(2) <<
"Removed " << num_removed_constraints <<
" constraints";
142 return num_removed_constraints > 0;
152 SimplifyConstraint(&
ct);
158 const size_t key = ComputeHashOfTerms(
ct);
159 if (equiv_constraints_.contains(key)) {
160 const ConstraintIndex ct_index = equiv_constraints_[key];
161 if (constraint_infos_[ct_index].constraint.vars ==
ct.vars &&
162 constraint_infos_[ct_index].constraint.coeffs ==
ct.coeffs) {
163 if (added !=
nullptr) *added =
false;
164 if (
ct.lb > constraint_infos_[ct_index].constraint.lb) {
165 if (constraint_infos_[ct_index].is_in_lp) current_lp_is_changed_ =
true;
166 constraint_infos_[ct_index].constraint.lb =
ct.lb;
167 if (added !=
nullptr) *added =
true;
169 if (
ct.ub < constraint_infos_[ct_index].constraint.ub) {
170 if (constraint_infos_[ct_index].is_in_lp) current_lp_is_changed_ =
true;
171 constraint_infos_[ct_index].constraint.ub =
ct.ub;
172 if (added !=
nullptr) *added =
true;
174 ++num_merged_constraints_;
179 if (added !=
nullptr) *added =
true;
180 const ConstraintIndex ct_index(constraint_infos_.size());
185 equiv_constraints_[key] = ct_index;
186 ct_info.
active_count = constraint_active_count_increase_;
187 constraint_infos_.push_back(std::move(ct_info));
191void LinearConstraintManager::ComputeObjectiveParallelism(
192 const ConstraintIndex ct_index) {
193 CHECK(objective_is_defined_);
195 if (!objective_norm_computed_) {
196 objective_l2_norm_ = std::sqrt(sum_of_squared_objective_coeffs_);
197 objective_norm_computed_ =
true;
201 constraint_infos_[ct_index].objective_parallelism_computed =
true;
202 if (constraint_infos_[ct_index].
l2_norm == 0.0) {
203 constraint_infos_[ct_index].objective_parallelism = 0.0;
207 const LinearConstraint& lc = constraint_infos_[ct_index].constraint;
208 double unscaled_objective_parallelism = 0.0;
209 for (
int i = 0; i < lc.vars.size(); ++i) {
210 const IntegerVariable
var = lc.vars[i];
211 const auto it = objective_map_.find(
var);
212 if (it == objective_map_.end())
continue;
213 unscaled_objective_parallelism += it->second *
ToDouble(lc.coeffs[i]);
215 const double objective_parallelism =
216 unscaled_objective_parallelism /
217 (constraint_infos_[ct_index].l2_norm * objective_l2_norm_);
218 constraint_infos_[ct_index].objective_parallelism =
219 std::abs(objective_parallelism);
227 std::string extra_info) {
228 ++num_add_cut_calls_;
229 if (
ct.vars.empty())
return false;
232 const double violation =
237 if (violation /
l2_norm < 1e-5)
return false;
240 const ConstraintIndex ct_index =
Add(std::move(
ct), &added);
244 if (!added)
return false;
248 constraint_infos_[ct_index].is_deletable =
true;
250 VLOG(1) <<
"Cut '" << type_name <<
"'"
251 <<
" size=" << constraint_infos_[ct_index].constraint.vars.size()
254 <<
" norm=" <<
l2_norm <<
" violation=" << violation
255 <<
" eff=" << violation /
l2_norm <<
" " << extra_info;
258 num_deletable_constraints_++;
259 type_to_num_cuts_[type_name]++;
263void LinearConstraintManager::PermanentlyRemoveSomeConstraints() {
264 std::vector<double> deletable_constraint_counts;
265 for (ConstraintIndex i(0); i < constraint_infos_.size(); ++i) {
266 if (constraint_infos_[i].is_deletable && !constraint_infos_[i].is_in_lp) {
267 deletable_constraint_counts.push_back(constraint_infos_[i].active_count);
270 if (deletable_constraint_counts.empty())
return;
271 std::sort(deletable_constraint_counts.begin(),
272 deletable_constraint_counts.end());
276 double active_count_threshold = std::numeric_limits<double>::infinity();
277 if (sat_parameters_.cut_cleanup_target() <
278 deletable_constraint_counts.size()) {
279 active_count_threshold =
280 deletable_constraint_counts[sat_parameters_.cut_cleanup_target()];
283 ConstraintIndex new_size(0);
284 equiv_constraints_.clear();
286 constraint_infos_.size());
287 int num_deleted_constraints = 0;
288 for (ConstraintIndex i(0); i < constraint_infos_.size(); ++i) {
289 if (constraint_infos_[i].is_deletable && !constraint_infos_[i].is_in_lp &&
290 constraint_infos_[i].active_count <= active_count_threshold &&
291 num_deleted_constraints < sat_parameters_.cut_cleanup_target()) {
292 ++num_deleted_constraints;
297 constraint_infos_[new_size] = std::move(constraint_infos_[i]);
299 index_mapping[i] = new_size;
302 equiv_constraints_[constraint_infos_[new_size].hash] = new_size;
305 constraint_infos_.resize(new_size.value());
308 for (
int i = 0; i < lp_constraints_.size(); ++i) {
309 lp_constraints_[i] = index_mapping[lp_constraints_[i]];
312 if (num_deleted_constraints > 0) {
313 VLOG(1) <<
"Constraint manager cleanup: #deleted:"
314 << num_deleted_constraints;
316 num_deletable_constraints_ -= num_deleted_constraints;
320 IntegerValue
coeff) {
321 if (
coeff == IntegerValue(0))
return;
322 objective_is_defined_ =
true;
328 const auto insert = objective_map_.insert({
var, coeff_as_double});
330 <<
"SetObjectiveCoefficient() called twice with same variable";
331 sum_of_squared_objective_coeffs_ += coeff_as_double * coeff_as_double;
335 bool term_changed =
false;
337 IntegerValue min_sum(0);
338 IntegerValue max_sum(0);
339 IntegerValue max_magnitude(0);
341 const int num_terms =
ct->vars.size();
342 for (
int i = 0; i < num_terms; ++i) {
343 const IntegerVariable
var =
ct->vars[i];
344 const IntegerValue
coeff =
ct->coeffs[i];
350 if (lb == ub)
continue;
355 min_sum +=
coeff * lb;
356 max_sum +=
coeff * ub;
358 min_sum +=
coeff * ub;
359 max_sum +=
coeff * lb;
364 if (new_size < num_terms) {
366 ++num_shortened_constraints_;
368 for (
int i = 0; i < num_terms; ++i) {
369 const IntegerVariable
var =
ct->vars[i];
370 const IntegerValue
coeff =
ct->coeffs[i];
374 const IntegerValue rhs_adjust = lb *
coeff;
379 ct->vars[new_size] =
var;
383 ct->vars.resize(new_size);
384 ct->coeffs.resize(new_size);
408 ++num_splitted_constraints_;
411 ++num_coeff_strenghtening_;
412 const int num_terms =
ct->vars.size();
413 const IntegerValue target = max_sum -
ct->ub;
414 for (
int i = 0; i < num_terms; ++i) {
415 const IntegerValue
coeff =
ct->coeffs[i];
416 if (
coeff > target) {
417 const IntegerVariable
var =
ct->vars[i];
419 ct->coeffs[i] = target;
420 ct->ub -= (
coeff - target) * ub;
421 }
else if (
coeff < -target) {
422 const IntegerVariable
var =
ct->vars[i];
424 ct->coeffs[i] = -target;
425 ct->ub += (-target -
coeff) * lb;
433 ++num_splitted_constraints_;
436 ++num_coeff_strenghtening_;
437 const int num_terms =
ct->vars.size();
438 const IntegerValue target =
ct->lb - min_sum;
439 for (
int i = 0; i < num_terms; ++i) {
440 const IntegerValue
coeff =
ct->coeffs[i];
441 if (
coeff > target) {
442 const IntegerVariable
var =
ct->vars[i];
444 ct->coeffs[i] = target;
445 ct->lb -= (
coeff - target) * lb;
446 }
else if (
coeff < -target) {
447 const IntegerVariable
var =
ct->vars[i];
449 ct->coeffs[i] = -target;
450 ct->lb += (-target -
coeff) * ub;
462 VLOG(3) <<
"Enter ChangeLP, scan " << constraint_infos_.size()
464 const double saved_dtime = dtime_;
465 std::vector<ConstraintIndex> new_constraints;
466 std::vector<double> new_constraints_efficacies;
467 std::vector<double> new_constraints_orthogonalities;
469 const bool simplify_constraints =
476 bool rescale_active_count =
false;
477 const double tolerance = 1e-6;
478 for (ConstraintIndex i(0); i < constraint_infos_.size(); ++i) {
480 if (simplify_constraints &&
481 SimplifyConstraint(&constraint_infos_[i].constraint)) {
482 ++num_simplifications_;
490 constraint_infos_[i].objective_parallelism_computed =
false;
491 constraint_infos_[i].l2_norm =
494 if (constraint_infos_[i].is_in_lp) current_lp_is_changed_ =
true;
495 equiv_constraints_.erase(constraint_infos_[i].
hash);
496 constraint_infos_[i].hash =
497 ComputeHashOfTerms(constraint_infos_[i].constraint);
501 equiv_constraints_[constraint_infos_[i].hash] = i;
504 if (constraint_infos_[i].is_in_lp)
continue;
509 static_cast<double>(constraint_infos_[i].constraint.vars.size());
510 const double activity =
512 const double lb_violation =
513 ToDouble(constraint_infos_[i].constraint.lb) - activity;
514 const double ub_violation =
515 activity -
ToDouble(constraint_infos_[i].constraint.ub);
516 const double violation =
std::max(lb_violation, ub_violation);
517 if (violation >= tolerance) {
518 constraint_infos_[i].inactive_count = 0;
519 new_constraints.push_back(i);
520 new_constraints_efficacies.push_back(violation /
522 new_constraints_orthogonalities.push_back(1.0);
524 if (objective_is_defined_ &&
525 !constraint_infos_[i].objective_parallelism_computed) {
526 ComputeObjectiveParallelism(i);
527 }
else if (!objective_is_defined_) {
528 constraint_infos_[i].objective_parallelism = 0.0;
531 constraint_infos_[i].current_score =
532 new_constraints_efficacies.back() +
533 constraint_infos_[i].objective_parallelism;
535 if (constraint_infos_[i].is_deletable) {
536 constraint_infos_[i].active_count += constraint_active_count_increase_;
537 if (constraint_infos_[i].active_count >
538 sat_parameters_.cut_max_active_count_value()) {
539 rescale_active_count =
true;
546 if (solution_state !=
nullptr) {
547 const glop::RowIndex num_rows(lp_constraints_.size());
548 const glop::ColIndex num_cols =
551 for (
int i = 0; i < num_rows; ++i) {
552 const ConstraintIndex constraint_index = lp_constraints_[i];
554 solution_state->
statuses[num_cols + glop::ColIndex(i)];
556 constraint_infos_[constraint_index].is_deletable) {
557 constraint_infos_[constraint_index].active_count +=
558 constraint_active_count_increase_;
559 if (constraint_infos_[constraint_index].active_count >
560 sat_parameters_.cut_max_active_count_value()) {
561 rescale_active_count =
true;
567 if (rescale_active_count) {
568 CHECK_GT(sat_parameters_.cut_max_active_count_value(), 0.0);
569 RescaleActiveCounts(1.0 / sat_parameters_.cut_max_active_count_value());
573 constraint_active_count_increase_ *=
574 1.0 / sat_parameters_.cut_active_count_decay();
579 if (MaybeRemoveSomeInactiveConstraints(solution_state)) {
580 current_lp_is_changed_ =
true;
593 const int kBlowupFactor = 4;
594 int constraint_limit =
std::min(sat_parameters_.new_constraints_batch_size(),
595 static_cast<int>(new_constraints.size()));
596 if (lp_constraints_.empty()) {
597 constraint_limit =
std::min(1000,
static_cast<int>(new_constraints.size()));
599 VLOG(3) <<
" - size = " << new_constraints.size()
600 <<
", limit = " << constraint_limit;
602 std::stable_sort(new_constraints.begin(), new_constraints.end(),
603 [&](ConstraintIndex
a, ConstraintIndex
b) {
604 return constraint_infos_[a].current_score >
605 constraint_infos_[b].current_score;
607 if (new_constraints.size() > kBlowupFactor * constraint_limit) {
608 VLOG(3) <<
"Resize candidate constraints from " << new_constraints.size()
609 <<
" down to " << kBlowupFactor * constraint_limit;
610 new_constraints.resize(kBlowupFactor * constraint_limit);
614 int num_skipped_checks = 0;
615 const int kCheckFrequency = 100;
616 ConstraintIndex last_added_candidate = kInvalidConstraintIndex;
617 for (
int i = 0; i < constraint_limit; ++i) {
620 double best_score = 0.0;
621 ConstraintIndex best_candidate = kInvalidConstraintIndex;
622 for (
int j = 0; j < new_constraints.size(); ++j) {
624 if (++num_skipped_checks >= kCheckFrequency) {
625 if (time_limit_->
LimitReached())
return current_lp_is_changed_;
626 num_skipped_checks = 0;
629 const ConstraintIndex new_constraint = new_constraints[j];
630 if (constraint_infos_[new_constraint].is_in_lp)
continue;
632 if (last_added_candidate != kInvalidConstraintIndex) {
633 const double current_orthogonality =
635 constraint_infos_[last_added_candidate].constraint,
636 constraint_infos_[new_constraint].constraint)) /
637 (constraint_infos_[last_added_candidate].l2_norm *
638 constraint_infos_[new_constraint].l2_norm));
639 new_constraints_orthogonalities[j] =
640 std::min(new_constraints_orthogonalities[j], current_orthogonality);
648 if (new_constraints_orthogonalities[j] <
649 sat_parameters_.min_orthogonality_for_lp_constraints()) {
655 const double score = new_constraints_orthogonalities[j] +
656 constraint_infos_[new_constraint].current_score;
658 if (score > best_score || best_candidate == kInvalidConstraintIndex) {
660 best_candidate = new_constraint;
664 if (best_candidate != kInvalidConstraintIndex) {
666 constraint_infos_[best_candidate].is_in_lp =
true;
671 current_lp_is_changed_ =
true;
672 lp_constraints_.push_back(best_candidate);
673 last_added_candidate = best_candidate;
679 VLOG(2) <<
"Added " << num_added <<
" constraints.";
686 if (num_deletable_constraints_ > sat_parameters_.max_num_cuts()) {
687 PermanentlyRemoveSomeConstraints();
694 if (current_lp_is_changed_) {
695 current_lp_is_changed_ =
false;
702 for (ConstraintIndex i(0); i < constraint_infos_.size(); ++i) {
703 if (constraint_infos_[i].is_in_lp)
continue;
704 constraint_infos_[i].is_in_lp =
true;
705 lp_constraints_.push_back(i);
713 if (debug_solution.empty())
return true;
715 IntegerValue activity(0);
716 for (
int i = 0; i < cut.
vars.size(); ++i) {
717 const IntegerVariable
var = cut.
vars[i];
719 activity +=
coeff * debug_solution[
var];
721 if (activity > cut.
ub || activity < cut.
lb) {
722 LOG(
INFO) <<
"activity " << activity <<
" not in [" << cut.
lb <<
","
732 if (
ct.vars.empty())
return;
734 const double violation =
744 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.
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)
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::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