26#include "absl/container/btree_map.h"
27#include "absl/container/flat_hash_map.h"
28#include "absl/log/check.h"
29#include "absl/meta/type_traits.h"
30#include "absl/strings/str_cat.h"
31#include "absl/strings/string_view.h"
40#include "ortools/sat/sat_parameters.pb.h"
52const LinearConstraintManager::ConstraintIndex kInvalidConstraintIndex(-1);
54size_t ComputeHashOfTerms(
const LinearConstraint&
ct) {
55 DCHECK(std::is_sorted(
ct.vars.get(),
ct.vars.get() +
ct.num_terms));
57 const int num_terms =
ct.num_terms;
58 for (
int i = 0;
i < num_terms; ++
i) {
68 if (!VLOG_IS_ON(1))
return;
71 std::vector<std::pair<std::string, int64_t>> cut_stats;
72 for (
const auto& entry : type_to_num_cuts_) {
73 cut_stats.push_back({absl::StrCat(
"cut/", entry.first), entry.second});
78void LinearConstraintManager::RescaleActiveCounts(
const double scaling_factor) {
79 for (ConstraintIndex
i(0);
i < constraint_infos_.size(); ++
i) {
80 constraint_infos_[
i].active_count *= scaling_factor;
82 constraint_active_count_increase_ *= scaling_factor;
83 VLOG(3) <<
"Rescaled active counts by " << scaling_factor;
86bool LinearConstraintManager::MaybeRemoveSomeInactiveConstraints(
87 glop::BasisState* solution_state) {
88 if (solution_state->IsEmpty())
return false;
89 const glop::RowIndex num_rows(lp_constraints_.size());
90 const glop::ColIndex num_cols =
91 solution_state->statuses.size() - RowToColIndex(num_rows);
93 for (
int i = 0;
i < num_rows; ++
i) {
103 solution_state->statuses[num_cols + glop::ColIndex(
i)];
107 sat_parameters_.max_consecutive_inactive_count()) {
117 solution_state->statuses[num_cols + glop::ColIndex(new_size)] = row_status;
120 const int num_removed_constraints = lp_constraints_.size() - new_size;
121 lp_constraints_.resize(new_size);
122 solution_state->statuses.resize(num_cols + glop::ColIndex(new_size));
123 if (num_removed_constraints > 0) {
124 VLOG(3) <<
"Removed " << num_removed_constraints <<
" constraints";
126 return num_removed_constraints > 0;
134 DCHECK_GT(
ct.num_terms, 0);
137 SimplifyConstraint(&
ct);
140 CHECK(std::is_sorted(
ct.VarsAsSpan().begin(),
ct.VarsAsSpan().end()));
144 const size_t key = ComputeHashOfTerms(
ct);
145 if (equiv_constraints_.contains(key)) {
146 const ConstraintIndex
ct_index = equiv_constraints_[key];
147 if (constraint_infos_[
ct_index].constraint.IsEqualIgnoringBounds(
ct)) {
148 bool tightened =
false;
149 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;
154 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;
159 if (added !=
nullptr) *added = tightened;
161 ++num_merged_constraints_;
162 FillDerivedFields(&constraint_infos_[
ct_index]);
168 if (added !=
nullptr) *added =
true;
169 const ConstraintIndex
ct_index(constraint_infos_.size());
173 FillDerivedFields(&ct_info);
176 ct_info.
active_count = constraint_active_count_increase_;
177 constraint_infos_.push_back(std::move(ct_info));
182 IntegerValue new_lb) {
183 const ConstraintIndex
index = lp_constraints_[index_in_lp.value()];
186 ++num_constraint_updates_;
187 current_lp_is_changed_ =
true;
193 IntegerValue new_ub) {
194 const ConstraintIndex
index = lp_constraints_[index_in_lp.value()];
197 ++num_constraint_updates_;
198 current_lp_is_changed_ =
true;
203void LinearConstraintManager::ComputeObjectiveParallelism(
205 CHECK(objective_is_defined_);
207 if (!objective_norm_computed_) {
208 objective_l2_norm_ = std::sqrt(sum_of_squared_objective_coeffs_);
209 objective_norm_computed_ =
true;
211 CHECK_GT(objective_l2_norm_, 0.0);
213 constraint_infos_[
ct_index].objective_parallelism_computed =
true;
215 constraint_infos_[
ct_index].objective_parallelism = 0.0;
219 const LinearConstraint& lc = constraint_infos_[
ct_index].constraint;
220 double unscaled_objective_parallelism = 0.0;
221 for (
int i = 0;
i < lc.num_terms; ++
i) {
222 const IntegerVariable
var = lc.vars[
i];
223 const auto it = objective_map_.find(
var);
224 if (it == objective_map_.end())
continue;
225 unscaled_objective_parallelism += it->second *
ToDouble(lc.coeffs[
i]);
227 const double objective_parallelism =
228 unscaled_objective_parallelism /
229 (constraint_infos_[
ct_index].l2_norm * objective_l2_norm_);
230 constraint_infos_[
ct_index].objective_parallelism =
231 std::abs(objective_parallelism);
237 std::string extra_info) {
238 ++num_add_cut_calls_;
239 if (
ct.num_terms == 0)
return false;
242 const double violation =
247 if (violation /
l2_norm < 1e-4) {
248 VLOG(3) <<
"BAD Cut '" << type_name <<
"'" <<
" size=" <<
ct.num_terms
250 <<
" norm=" <<
l2_norm <<
" violation=" << violation
251 <<
" eff=" << violation /
l2_norm <<
" " << extra_info;
266 if (!added)
return false;
270 constraint_infos_[
ct_index].is_deletable =
true;
272 VLOG(2) <<
"Cut '" << type_name <<
"'"
273 <<
" size=" << constraint_infos_[
ct_index].constraint.num_terms
276 <<
" norm=" <<
l2_norm <<
" violation=" << violation
277 <<
" eff=" << violation /
l2_norm <<
" " << extra_info;
280 num_deletable_constraints_++;
281 type_to_num_cuts_[type_name]++;
285void LinearConstraintManager::PermanentlyRemoveSomeConstraints() {
286 std::vector<double> deletable_constraint_counts;
287 for (ConstraintIndex
i(0);
i < constraint_infos_.size(); ++
i) {
288 if (constraint_infos_[
i].is_deletable && !constraint_infos_[
i].is_in_lp) {
289 deletable_constraint_counts.push_back(constraint_infos_[
i].active_count);
292 if (deletable_constraint_counts.empty())
return;
293 std::sort(deletable_constraint_counts.begin(),
294 deletable_constraint_counts.end());
298 double active_count_threshold = std::numeric_limits<double>::infinity();
299 if (sat_parameters_.cut_cleanup_target() <
300 deletable_constraint_counts.size()) {
301 active_count_threshold =
302 deletable_constraint_counts[sat_parameters_.cut_cleanup_target()];
305 ConstraintIndex new_size(0);
306 equiv_constraints_.clear();
308 constraint_infos_.size());
309 int num_deleted_constraints = 0;
310 for (ConstraintIndex
i(0);
i < constraint_infos_.size(); ++
i) {
311 if (constraint_infos_[
i].is_deletable && !constraint_infos_[
i].is_in_lp &&
312 constraint_infos_[
i].active_count <= active_count_threshold &&
313 num_deleted_constraints < sat_parameters_.cut_cleanup_target()) {
314 ++num_deleted_constraints;
319 constraint_infos_[new_size] = std::move(constraint_infos_[
i]);
321 index_mapping[
i] = new_size;
324 equiv_constraints_[constraint_infos_[new_size].hash] = new_size;
327 constraint_infos_.resize(new_size.value());
330 for (
int i = 0;
i < lp_constraints_.size(); ++
i) {
331 lp_constraints_[
i] = index_mapping[lp_constraints_[
i]];
334 if (num_deleted_constraints > 0) {
335 VLOG(3) <<
"Constraint manager cleanup: #deleted:"
336 << num_deleted_constraints;
338 num_deletable_constraints_ -= num_deleted_constraints;
342 IntegerValue coeff) {
343 if (coeff == IntegerValue(0))
return;
344 objective_is_defined_ =
true;
349 const double coeff_as_double =
ToDouble(coeff);
350 const auto insert = objective_map_.insert({
var, coeff_as_double});
352 <<
"SetObjectiveCoefficient() called twice with same variable";
353 sum_of_squared_objective_coeffs_ += coeff_as_double * coeff_as_double;
358 bool term_changed =
false;
360 IntegerValue min_sum(0);
361 IntegerValue max_sum(0);
362 IntegerValue max_magnitude(0);
365 const int num_terms =
ct->num_terms;
366 for (
int i = 0;
i < num_terms; ++
i) {
367 const IntegerVariable
var =
ct->vars[
i];
368 const IntegerValue coeff =
ct->coeffs[
i];
374 if (lb == ub)
continue;
377 const IntegerValue magnitude =
IntTypeAbs(coeff);
378 max_magnitude = std::max(max_magnitude, magnitude);
379 min_magnitude = std::min(min_magnitude, magnitude);
381 min_sum += coeff * lb;
382 max_sum += coeff * ub;
384 min_sum += coeff * ub;
385 max_sum += coeff * lb;
390 if (new_size < num_terms) {
392 ++num_shortened_constraints_;
394 for (
int i = 0;
i < num_terms; ++
i) {
395 const IntegerVariable
var =
ct->vars[
i];
396 const IntegerValue coeff =
ct->coeffs[
i];
400 const IntegerValue rhs_adjust = lb * coeff;
405 ct->vars[new_size] =
var;
406 ct->coeffs[new_size] = coeff;
409 ct->resize(new_size);
414 if (min_sum >=
ct->lb && max_sum <= ct->ub) {
422 ct->lb = std::max(
ct->lb, min_sum);
423 ct->ub = std::min(
ct->ub, max_sum);
431 const IntegerValue threshold_ub = max_sum -
ct->ub;
432 const IntegerValue threshold_lb =
ct->lb - min_sum;
433 const IntegerValue threshold = std::max(threshold_lb, threshold_ub);
434 CHECK_GT(threshold, 0);
438 if (threshold_ub > 0 && threshold_lb > 0 && threshold_lb != threshold_ub) {
439 if (max_magnitude > std::min(threshold_lb, threshold_ub)) {
440 ++num_split_constraints_;
446 const IntegerValue second_threshold = std::max(
447 {
CeilRatio(threshold, IntegerValue(2)), threshold - min_magnitude,
448 std::min(threshold_lb, threshold_ub)});
449 if (max_magnitude > second_threshold) {
450 const int num_terms =
ct->num_terms;
451 for (
int i = 0;
i < num_terms; ++
i) {
456 const IntegerValue coeff =
ct->coeffs[
i];
457 const IntegerVariable
var =
ct->vars[
i];
460 if (coeff > threshold) {
462 ++num_coeff_strenghtening_;
463 ct->coeffs[
i] = threshold;
464 ct->ub -= (coeff - threshold) * ub;
465 ct->lb -= (coeff - threshold) * lb;
466 }
else if (coeff > second_threshold && coeff < threshold) {
468 ++num_coeff_strenghtening_;
469 ct->coeffs[
i] = second_threshold;
470 ct->ub -= (coeff - second_threshold) * ub;
471 ct->lb -= (coeff - second_threshold) * lb;
472 }
else if (coeff < -threshold) {
474 ++num_coeff_strenghtening_;
475 ct->coeffs[
i] = -threshold;
476 ct->ub -= (coeff + threshold) * lb;
477 ct->lb -= (coeff + threshold) * ub;
478 }
else if (coeff < -second_threshold && coeff > -threshold) {
480 ++num_coeff_strenghtening_;
481 ct->coeffs[
i] = -second_threshold;
482 ct->ub -= (coeff + second_threshold) * lb;
483 ct->lb -= (coeff + second_threshold) * ub;
491void LinearConstraintManager::FillDerivedFields(ConstraintInfo* info) {
492 IntegerValue min_sum(0);
493 IntegerValue max_sum(0);
494 const int num_terms = info->constraint.num_terms;
495 for (
int i = 0;
i < num_terms; ++
i) {
496 const IntegerVariable
var = info->constraint.vars[
i];
497 const IntegerValue coeff = info->constraint.coeffs[
i];
501 min_sum += coeff * lb;
502 max_sum += coeff * ub;
504 min_sum += coeff * ub;
505 max_sum += coeff * lb;
508 info->constraint.lb = std::max(min_sum, info->constraint.lb);
509 info->constraint.ub = std::min(max_sum, info->constraint.ub);
510 CHECK_NE(
CapSub(info->constraint.ub.value(), info->constraint.lb.value()),
511 std::numeric_limits<int64_t>::max());
512 info->lb_is_trivial = min_sum >= info->constraint.lb;
513 info->ub_is_trivial = max_sum <= info->constraint.ub;
517 int* num_new_constraints) {
518 VLOG(3) <<
"Enter ChangeLP, scan " << constraint_infos_.size()
520 const double saved_dtime = dtime_;
521 std::vector<std::pair<ConstraintIndex, double>> new_constraints_by_score;
522 std::vector<double> new_constraints_efficacies;
523 std::vector<double> new_constraints_orthogonalities;
525 const bool simplify_constraints =
532 bool rescale_active_count =
false;
533 const double tolerance = 1e-6;
534 for (ConstraintIndex
i(0);
i < constraint_infos_.size(); ++
i) {
536 if (simplify_constraints &&
537 SimplifyConstraint(&constraint_infos_[
i].constraint)) {
538 ++num_simplifications_;
546 constraint_infos_[
i].objective_parallelism_computed =
false;
547 constraint_infos_[
i].l2_norm =
549 FillDerivedFields(&constraint_infos_[
i]);
551 if (constraint_infos_[
i].is_in_lp) current_lp_is_changed_ =
true;
552 equiv_constraints_.erase(constraint_infos_[
i].
hash);
553 constraint_infos_[
i].hash =
554 ComputeHashOfTerms(constraint_infos_[
i].constraint);
558 equiv_constraints_[constraint_infos_[
i].hash] =
i;
561 if (constraint_infos_[
i].is_in_lp)
continue;
566 1.7e-9 *
static_cast<double>(constraint_infos_[
i].constraint.num_terms);
567 const double activity =
569 const double lb_violation =
570 ToDouble(constraint_infos_[
i].constraint.lb) - activity;
571 const double ub_violation =
572 activity -
ToDouble(constraint_infos_[
i].constraint.ub);
573 const double violation = std::max(lb_violation, ub_violation);
574 if (violation >= tolerance) {
575 constraint_infos_[
i].inactive_count = 0;
576 if (objective_is_defined_ &&
577 !constraint_infos_[
i].objective_parallelism_computed) {
578 ComputeObjectiveParallelism(
i);
579 }
else if (!objective_is_defined_) {
580 constraint_infos_[
i].objective_parallelism = 0.0;
583 const double score = violation / constraint_infos_[
i].l2_norm +
584 constraint_infos_[
i].objective_parallelism;
585 new_constraints_by_score.push_back({
i, score});
587 if (constraint_infos_[
i].is_deletable) {
588 constraint_infos_[
i].active_count += constraint_active_count_increase_;
589 if (constraint_infos_[
i].active_count >
590 sat_parameters_.cut_max_active_count_value()) {
591 rescale_active_count =
true;
598 if (solution_state !=
nullptr) {
599 const glop::RowIndex num_rows(lp_constraints_.size());
600 const glop::ColIndex num_cols =
601 solution_state->
statuses.
size() - RowToColIndex(num_rows);
603 for (
int i = 0;
i < num_rows; ++
i) {
606 solution_state->
statuses[num_cols + glop::ColIndex(
i)];
610 constraint_active_count_increase_;
612 sat_parameters_.cut_max_active_count_value()) {
613 rescale_active_count =
true;
619 if (rescale_active_count) {
620 CHECK_GT(sat_parameters_.cut_max_active_count_value(), 0.0);
621 RescaleActiveCounts(1.0 / sat_parameters_.cut_max_active_count_value());
625 constraint_active_count_increase_ *=
626 1.0 / sat_parameters_.cut_active_count_decay();
631 if (MaybeRemoveSomeInactiveConstraints(solution_state)) {
632 current_lp_is_changed_ =
true;
645 const int kBlowupFactor = 4;
646 const int current_size =
static_cast<int>(new_constraints_by_score.size());
647 int constraint_limit =
648 std::min(sat_parameters_.new_constraints_batch_size(), current_size);
649 if (lp_constraints_.empty()) {
650 constraint_limit = std::min(1000, current_size);
652 VLOG(3) <<
" - size = " << current_size <<
", limit = " << constraint_limit;
654 std::stable_sort(new_constraints_by_score.begin(),
655 new_constraints_by_score.end(),
656 [&](std::pair<ConstraintIndex, double>
a,
657 std::pair<ConstraintIndex, double>
b) {
658 return a.second > b.second;
660 if (new_constraints_by_score.size() > kBlowupFactor * constraint_limit) {
661 VLOG(3) <<
"Resize candidate constraints from "
662 << new_constraints_by_score.size() <<
" down to "
663 << kBlowupFactor * constraint_limit;
664 new_constraints_by_score.resize(kBlowupFactor * constraint_limit);
668 int num_skipped_checks = 0;
669 const int kCheckFrequency = 100;
670 ConstraintIndex last_added_candidate = kInvalidConstraintIndex;
671 std::vector<double> orthogonality_score(new_constraints_by_score.size(), 1.0);
672 for (
int i = 0;
i < constraint_limit; ++
i) {
677 double best_score = 0.0;
678 ConstraintIndex best_candidate = kInvalidConstraintIndex;
679 for (
int j = 0; j < new_constraints_by_score.size(); ++j) {
681 if (++num_skipped_checks >= kCheckFrequency) {
682 if (time_limit_->
LimitReached())
return current_lp_is_changed_;
683 num_skipped_checks = 0;
686 const ConstraintIndex new_index = new_constraints_by_score[j].first;
687 if (constraint_infos_[new_index].is_in_lp)
continue;
689 if (last_added_candidate != kInvalidConstraintIndex) {
690 const double current_orthogonality =
692 constraint_infos_[last_added_candidate].constraint,
693 constraint_infos_[new_index].constraint)) /
694 (constraint_infos_[last_added_candidate].l2_norm *
695 constraint_infos_[new_index].l2_norm));
696 orthogonality_score[j] =
697 std::min(orthogonality_score[j], current_orthogonality);
705 if (orthogonality_score[j] <
706 sat_parameters_.min_orthogonality_for_lp_constraints()) {
713 orthogonality_score[j] + new_constraints_by_score[j].second;
714 CHECK_GE(score, 0.0);
715 if (score > best_score || best_candidate == kInvalidConstraintIndex) {
717 best_candidate = new_index;
721 if (best_candidate != kInvalidConstraintIndex) {
723 constraint_infos_[best_candidate].is_in_lp =
true;
728 current_lp_is_changed_ =
true;
729 lp_constraints_.push_back(best_candidate);
730 last_added_candidate = best_candidate;
737 if (num_new_constraints !=
nullptr) {
738 *num_new_constraints = num_added;
742 VLOG(3) <<
"Added " << num_added <<
" constraints.";
749 if (num_deletable_constraints_ > sat_parameters_.max_num_cuts()) {
750 PermanentlyRemoveSomeConstraints();
757 if (current_lp_is_changed_) {
758 current_lp_is_changed_ =
false;
765 for (ConstraintIndex
i(0);
i < constraint_infos_.size(); ++
i) {
766 if (constraint_infos_[
i].is_in_lp)
continue;
767 constraint_infos_[
i].is_in_lp =
true;
768 lp_constraints_.push_back(
i);
777 IntegerValue activity(0);
779 const IntegerVariable
var = cut.
vars[
i];
780 const IntegerValue coeff = cut.
coeffs[
i];
781 CHECK(debug_solution.ivar_has_value[
var]);
782 activity += coeff * debug_solution.ivar_values[
var];
784 if (activity > cut.
ub || activity < cut.
lb) {
786 LOG(INFO) <<
"activity " << activity <<
" not in [" << cut.
lb <<
","
796 if (
ct.num_terms == 0)
return;
798 const double violation =
806 manager->
AddCut(std::move(candidate.cut), candidate.name);
void AdvanceDeterministicTime(double deterministic_duration)
void resize(IntType size)
int64_t num_level_zero_enqueues() const
Same as num_enqueues but only count the level zero changes.
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Returns globally valid lower/upper bound on the given integer variable.
~LinearConstraintManager()
bool ChangeLp(glop::BasisState *solution_state, int *num_new_constraints=nullptr)
ConstraintIndex Add(LinearConstraint ct, bool *added=nullptr)
bool AddCut(LinearConstraint ct, std::string type_name, std::string extra_info="")
bool UpdateConstraintUb(glop::RowIndex index_in_lp, IntegerValue new_ub)
bool DebugCheckConstraint(const LinearConstraint &cut)
bool UpdateConstraintLb(glop::RowIndex index_in_lp, IntegerValue new_lb)
These must be level zero bounds.
void AddAllConstraintsToLp()
void SetObjectiveCoefficient(IntegerVariable var, IntegerValue coeff)
T Get(std::function< T(const Model &)> f) const
Similar to Add() but this is const.
Simple class to add statistics by name and print them at the end.
void TransferToManager(LinearConstraintManager *manager)
Empty the local pool and add all its content to the manager.
void AddCut(LinearConstraint ct, absl::string_view name, const util_intops::StrongVector< IntegerVariable, double > &lp_solution)
Adds a cut to the local pool.
void Add(Element e, Score score)
std::vector< Element > * MutableUnorderedElements()
const std::string name
A name for logging purposes.
double ComputeActivity(const LinearConstraint &constraint, const util_intops::StrongVector< IntegerVariable, double > &values)
void DivideByGCD(LinearConstraint *constraint)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
IntType IntTypeAbs(IntType t)
double ComputeL2Norm(const LinearConstraint &ct)
Returns sqrt(sum square(coeff)).
IntegerValue CeilRatio(IntegerValue dividend, IntegerValue positive_divisor)
IntegerValue ComputeInfinityNorm(const LinearConstraint &ct)
Returns the maximum absolute value of the coefficients.
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Returns the vector of the negated variables.
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
bool PossibleOverflow(const IntegerTrail &integer_trail, const LinearConstraint &constraint)
void MakeAllVariablesPositive(LinearConstraint *constraint)
Makes all variables "positive" by transforming a variable to its negation.
bool NoDuplicateVariable(const LinearConstraint &ct)
Returns false if duplicate variables are found in ct.
bool VariableIsPositive(IntegerVariable i)
double ToDouble(IntegerValue value)
double ScalarProduct(const LinearConstraint &ct1, const LinearConstraint &ct2)
In SWIG mode, we don't want anything besides these top-level includes.
int64_t CapSub(int64_t x, int64_t y)
uint64_t Hash(uint64_t num, uint64_t c)
double l2_norm
The L2 norm of the finite values.
VariableStatusRow statuses
LinearConstraint constraint
std::unique_ptr< IntegerValue[]> coeffs
std::unique_ptr< IntegerVariable[]> vars
std::string DebugString() const