25#include "absl/container/flat_hash_map.h"
26#include "absl/container/flat_hash_set.h"
27#include "absl/log/check.h"
28#include "absl/log/log.h"
29#include "absl/random/distributions.h"
30#include "absl/types/span.h"
45 const Index index = IndexFromLiteral(literal_ref);
46 if (index >= something_changed_.size()) {
47 something_changed_.Resize(index + 1);
48 enforcement_to_vars_.resize(index.value() + 1);
50 if (var >= tmp_num_occurrences_.size()) {
51 tmp_num_occurrences_.resize(var + 1, 0);
53 const auto insert = deductions_.insert({{index, var}, domain});
56 something_changed_.Set(index);
57 enforcement_to_vars_[index].push_back(var);
60 const Domain& old_domain = insert.first->second;
63 something_changed_.Set(index);
70 const Index index = IndexFromLiteral(literal_ref);
71 const auto it = deductions_.find({index, var});
77 absl::Span<const int> clause) {
78 std::vector<std::pair<int, Domain>> result;
82 for (
const int ref : clause) {
83 const Index index = IndexFromLiteral(ref);
84 if (index >= something_changed_.size())
return result;
85 if (something_changed_[index]) {
89 if (abort)
return result;
92 std::vector<int> to_process;
93 std::vector<int> to_clean;
94 for (
const int ref : clause) {
95 const Index index = IndexFromLiteral(ref);
96 for (
const int var : enforcement_to_vars_[index]) {
97 if (tmp_num_occurrences_[var] == 0) {
98 to_clean.push_back(var);
100 tmp_num_occurrences_[var]++;
101 if (tmp_num_occurrences_[var] == clause.size()) {
102 to_process.push_back(var);
108 for (
const int var : to_clean) {
109 tmp_num_occurrences_[var] = 0;
113 std::vector<Domain> domains(to_process.size());
114 for (
const int ref : clause) {
115 const Index index = IndexFromLiteral(ref);
116 for (
int i = 0;
i < to_process.size(); ++
i) {
117 domains[
i] = domains[
i].UnionWith(deductions_.at({index, to_process[i]}));
121 for (
int i = 0;
i < to_process.size(); ++
i) {
122 result.push_back({to_process[
i], std::move(domains[
i])});
132bool SortAndMergeTerms(std::vector<std::pair<int, int64_t>>* terms) {
133 std::sort(terms->begin(), terms->end());
137 int64_t current_coeff = 0;
138 for (
const auto& entry : *terms) {
140 if (entry.first == current_var) {
141 current_coeff =
CapAdd(current_coeff, entry.second);
144 if (current_coeff != 0) {
145 (*terms)[new_size++] = {current_var, current_coeff};
147 current_var = entry.first;
148 current_coeff = entry.second;
151 if (current_coeff != 0) {
152 (*terms)[new_size++] = {current_var, current_coeff};
154 terms->resize(new_size);
162 if (factor == 0)
return true;
167 std::vector<std::pair<int, int64_t>> terms;
169 const int initial_size = out->
vars().size();
170 for (
int i = 0;
i < initial_size; ++
i) {
171 const int var = out->
vars(
i);
172 const int64_t coeff = out->
coeffs(
i);
174 terms.push_back({var, coeff});
178 const int to_add_size = to_add.
linear().
vars().size();
179 for (
int i = 0;
i < to_add_size; ++
i) {
183 terms.push_back({var,
CapProd(coeff, factor)});
188 if (!SortAndMergeTerms(&terms))
return false;
193 for (
const auto [var, coeff] : terms) {
218 int64_t var_coeff = 0;
219 const int initial_ct_size = ct->
linear().
vars().size();
220 for (
int i = 0;
i < initial_ct_size; ++
i) {
228 if (var_coeff == 0)
return false;
230 CHECK_EQ(std::abs(var_coeff_in_definition), 1);
231 const int64_t factor = var_coeff_in_definition > 0 ? -var_coeff : var_coeff;
236 num_at_most_ones_ = 0;
237 amo_indices_.clear();
242 const int complexity_limit = 50;
243 for (
const int literal : amo) {
244 const Index
i = IndexFromLiteral(literal);
245 if (
i >= amo_indices_.size()) amo_indices_.resize(
i + 1);
246 if (amo_indices_[
i].size() >= complexity_limit) ++num_skipped;
248 if (num_skipped + 1 >= amo.size())
return;
251 const int unique_index = num_at_most_ones_++;
252 for (
const int literal : amo) {
253 const Index
i = IndexFromLiteral(literal);
254 if (amo_indices_[
i].size() < complexity_limit) {
255 amo_indices_[
i].push_back(unique_index);
263 const auto type = ct.constraint_case();
269 if (ct.enforcement_literal().size() == 1) {
270 const int a = ct.enforcement_literal(0);
271 for (
const int b : ct.bool_and().literals()) {
280int64_t ActivityBoundHelper::ComputeActivity(
281 bool compute_min, absl::Span<
const std::pair<int, int64_t>> terms,
282 std::vector<std::array<int64_t, 2>>* conditional) {
283 tmp_terms_for_compute_activity_.clear();
284 tmp_terms_for_compute_activity_.reserve(terms.size());
286 for (
auto [lit, coeff] : terms) {
287 if (compute_min) coeff = -coeff;
289 tmp_terms_for_compute_activity_.push_back({lit, coeff});
292 tmp_terms_for_compute_activity_.push_back({
NegatedRef(lit), -coeff});
296 const int64_t internal_result =
297 ComputeMaxActivityInternal(tmp_terms_for_compute_activity_, conditional);
300 if (conditional !=
nullptr) {
301 const int num_terms = terms.size();
302 for (
int i = 0;
i < num_terms; ++
i) {
303 if (tmp_terms_for_compute_activity_[
i].first != terms[
i].first) {
305 std::swap((*conditional)[
i][0], (*conditional)[
i][1]);
307 (*conditional)[
i][0] += offset;
308 (*conditional)[
i][1] += offset;
310 (*conditional)[
i][0] = -(*conditional)[
i][0];
311 (*conditional)[
i][1] = -(*conditional)[
i][1];
315 if (compute_min)
return -(offset + internal_result);
316 return offset + internal_result;
323void ActivityBoundHelper::PartitionIntoAmo(
324 absl::Span<
const std::pair<int, int64_t>> terms) {
325 auto amo_sums = amo_sums_.ClearedView(num_at_most_ones_);
327 const int num_terms = terms.size();
329 to_sort_.reserve(num_terms);
330 for (
int i = 0;
i < num_terms; ++
i) {
331 const Index index = IndexFromLiteral(terms[
i].first);
332 const int64_t coeff = terms[
i].second;
334 if (index < amo_indices_.size()) {
335 for (
const int a : amo_indices_[index]) {
336 amo_sums[a] += coeff;
340 TermWithIndex{.coeff = coeff, .index = index, .span_index =
i});
342 std::sort(to_sort_.begin(), to_sort_.end(),
343 [](
const TermWithIndex& a,
const TermWithIndex&
b) {
346 return std::tie(a.coeff, a.index) > std::tie(b.coeff, b.index);
350 partition_.resize(num_terms);
351 for (
const TermWithIndex& term : to_sort_) {
352 const int original_i = term.span_index;
353 const Index index = term.index;
354 const int64_t coeff = term.coeff;
356 int64_t best_sum = 0;
358 if (index < amo_indices_.size()) {
359 for (
const int a : amo_indices_[index]) {
362 const int64_t sum_left = amo_sums[a];
364 partition_[original_i] = -sum_left - 1;
369 amo_sums[a] -= coeff;
370 if (sum_left > best_sum) {
380 partition_[original_i] = num_parts++;
382 amo_sums[best] = -num_parts - 1;
383 partition_[original_i] = num_parts;
387 for (
const int p : partition_) CHECK_LT(p, num_parts);
388 CHECK_LE(num_parts, num_terms);
392std::vector<absl::Span<const int>>
394 auto amo_sums = amo_sums_.ClearedView(num_at_most_ones_);
395 for (
const int ref : literals) {
396 const Index index = IndexFromLiteral(ref);
397 if (index < amo_indices_.size()) {
398 for (
const int a : amo_indices_[index]) {
405 const int num_literals = literals.size();
406 partition_.resize(num_literals);
407 for (
int i = 0;
i < literals.size(); ++
i) {
408 const Index index = IndexFromLiteral(literals[
i]);
410 int64_t best_sum = 0;
412 if (index < amo_indices_.size()) {
413 for (
const int a : amo_indices_[index]) {
414 const int64_t sum_left = amo_sums[a];
419 partition_[
i] = -sum_left - 1;
425 if (sum_left > best_sum) {
435 amo_sums[best] = -num_parts - 1;
437 partition_[
i] = num_parts++;
441 part_to_literals_.ResetFromFlatMapping(partition_, literals);
442 DCHECK_EQ(part_to_literals_.size(), num_parts);
443 return part_to_literals_.AsVectorOfSpan();
447 auto amo_sums = amo_sums_.ClearedView(num_at_most_ones_);
448 for (
int i = 0;
i < literals.size(); ++
i) {
449 bool has_max_size =
false;
450 const Index index = IndexFromLiteral(literals[
i]);
451 if (index >= amo_indices_.size())
return false;
452 for (
const int a : amo_indices_[index]) {
453 if (amo_sums[a]++ ==
i) has_max_size =
true;
455 if (!has_max_size)
return false;
460int64_t ActivityBoundHelper::ComputeMaxActivityInternal(
461 absl::Span<
const std::pair<int, int64_t>> terms,
462 std::vector<std::array<int64_t, 2>>* conditional) {
463 PartitionIntoAmo(terms);
466 const int num_terms = terms.size();
467 max_by_partition_.assign(num_terms, 0);
468 second_max_by_partition_.assign(num_terms, 0);
469 for (
int i = 0;
i < num_terms; ++
i) {
470 const int p = partition_[
i];
471 const int64_t coeff = terms[
i].second;
472 if (coeff >= max_by_partition_[p]) {
473 second_max_by_partition_[p] = max_by_partition_[p];
474 max_by_partition_[p] = coeff;
475 }
else if (coeff > second_max_by_partition_[p]) {
476 second_max_by_partition_[p] = coeff;
481 int64_t max_activity = 0;
482 for (
int p = 0; p < partition_.size(); ++p) {
483 max_activity += max_by_partition_[p];
485 if (conditional !=
nullptr) {
486 conditional->resize(num_terms);
487 for (
int i = 0;
i < num_terms; ++
i) {
488 const int64_t coeff = terms[
i].second;
489 const int p = partition_[
i];
490 const int64_t max_used = max_by_partition_[p];
494 if (coeff == max_used) {
496 (*conditional)[
i][0] =
497 max_activity - max_used + second_max_by_partition_[p];
498 (*conditional)[
i][1] = max_activity;
501 (*conditional)[
i][0] = max_activity;
502 (*conditional)[
i][1] = max_activity - max_used + coeff;
509bool ActivityBoundHelper::AppearInTriggeredAmo(
int literal)
const {
510 const Index index = IndexFromLiteral(literal);
511 if (index >= amo_indices_.size())
return false;
512 for (
const int amo : amo_indices_[index]) {
513 if (triggered_amo_.contains(amo))
return true;
520 absl::flat_hash_set<int>* literals_at_true) {
523 literals_at_true->clear();
524 triggered_amo_.clear();
528 if (literals_at_true->contains(ref))
continue;
529 if (literals_at_true->contains(
NegatedRef(ref)))
return false;
530 literals_at_true->insert(ref);
537 if (AppearInTriggeredAmo(
NegatedRef(ref)))
continue;
539 const Index index = IndexFromLiteral(ref);
540 if (index < amo_indices_.size()) {
541 for (
const int a : amo_indices_[index]) {
544 const auto [_, inserted] = triggered_amo_.insert(a);
545 if (!inserted)
return false;
554 for (
const int ref : refs) {
556 if (literals_at_true->contains(ref))
continue;
557 if (literals_at_true->contains(
NegatedRef(ref)))
continue;
558 for (
const int to_test : {ref,
NegatedRef(ref)}) {
559 const Index index = IndexFromLiteral(to_test);
560 if (index < amo_indices_.size()) {
561 for (
const int a : amo_indices_[index]) {
562 if (triggered_amo_.contains(a)) {
565 if (literals_at_true->contains(to_test))
return false;
566 literals_at_true->insert(
NegatedRef(to_test));
578 absl::Span<
const std::pair<int, int64_t>> boolean_terms,
580 if (boolean_terms.empty())
return 0;
582 triggered_amo_.clear();
583 tmp_boolean_terms_in_some_amo_.clear();
584 tmp_boolean_terms_in_some_amo_.reserve(boolean_terms.size());
585 int num_enforcement_to_check = 0;
587 const Index negated_index = IndexFromLiteral(
NegatedRef(enf_lit));
588 if (negated_index >= amo_indices_.size())
continue;
589 if (amo_indices_[negated_index].empty())
continue;
590 triggered_amo_.insert(amo_indices_[negated_index].
begin(),
591 amo_indices_[negated_index].
end());
592 ++num_enforcement_to_check;
594 int non_amo_min_activity = 0;
595 int non_amo_max_activity = 0;
596 auto log_work = [&]() {
597 VLOG(1) <<
"RemoveEnforcementThatMakesConstraintTrivial: "
598 "aborting because too expensive: "
599 << num_enforcement_to_check <<
" " << boolean_terms.size();
602 static const int kMaxWork = 1e9;
604 for (
int i = 0;
i < boolean_terms.size(); ++
i) {
605 const int ref = boolean_terms[
i].first;
606 const int64_t coeff = boolean_terms[
i].second;
607 if (AppearInTriggeredAmo(ref) || AppearInTriggeredAmo(
NegatedRef(ref))) {
608 tmp_boolean_terms_in_some_amo_.push_back(
i);
611 non_amo_max_activity += coeff;
613 non_amo_min_activity += coeff;
617 if (work > kMaxWork)
return log_work();
621 const Index negated_index = IndexFromLiteral(
NegatedRef(enf_lit));
622 if (negated_index >= amo_indices_.size())
continue;
623 if (amo_indices_[negated_index].empty())
continue;
625 triggered_amo_.clear();
626 triggered_amo_.insert(amo_indices_[negated_index].
begin(),
627 amo_indices_[negated_index].
end());
630 int64_t min_activity = non_amo_min_activity;
631 int64_t max_activity = non_amo_max_activity;
632 for (
const int i : tmp_boolean_terms_in_some_amo_) {
633 const int ref = boolean_terms[
i].first;
634 const int64_t coeff = boolean_terms[
i].second;
637 if (ref == enf_lit || ref ==
NegatedRef(enf_lit))
break;
639 const bool is_true = AppearInTriggeredAmo(
NegatedRef(ref));
640 const bool is_false = AppearInTriggeredAmo(ref);
642 if (work > kMaxWork)
return log_work();
645 if (is_true && is_false)
break;
647 if (is_false)
continue;
649 min_activity += coeff;
650 max_activity += coeff;
654 max_activity += coeff;
656 min_activity += coeff;
660 if (
Domain(min_activity, max_activity)
661 .AdditionWith(other_terms)
662 .IsIncludedIn(rhs)) {
663 tmp_set_.insert(enf_lit);
667 if (!tmp_set_.empty()) {
670 if (tmp_set_.contains(ref))
continue;
675 return old_size - new_size;
681 absl::Span<const int> clause) {
683 for (
const int ref : clause) {
684 const Index index = IndexFromLiteral(ref);
685 while (index >= literal_to_hash_.size()) {
687 literal_to_hash_.push_back(absl::Uniform<uint64_t>(random_));
689 hash ^= literal_to_hash_[index];
692 if (c >= clause_to_hash_.size()) clause_to_hash_.resize(c + 1, 0);
693 clause_to_hash_[c] = hash;
697 absl::Span<const int> literals) {
699 for (
const int ref : literals) {
700 const Index index = IndexFromLiteral(
NegatedRef(ref));
701 while (index >= literal_to_hash_.size()) {
703 literal_to_hash_.push_back(absl::Uniform<uint64_t>(random_));
705 hash ^= literal_to_hash_[index];
712 int64_t* coeff1,
int* var2, int64_t* coeff2) {
713 const int size = lin1.
vars().size();
714 CHECK_EQ(size, lin2.
vars().size());
719 while (
i < size || j < size) {
721 const int v1 =
i < size ? lin1.
vars(
i) : std::numeric_limits<int>::max();
722 const int v2 = j < size ? lin2.
vars(j) : std::numeric_limits<int>::max();
734 if (*coeff1 != 0)
return false;
743 if (*coeff2 != 0)
return false;
751 if (*coeff1 != 0 || *coeff2 != 0)
return false;
760 return *coeff1 != 0 && *coeff2 != 0;
Domain MultiplicationBy(int64_t coeff, bool *exact=nullptr) const
Domain IntersectionWith(const Domain &domain) const
Domain AdditionWith(const Domain &domain) const
bool IsIncludedIn(const Domain &domain) const
static Domain AllValues()
void AddAllAtMostOnes(const CpModelProto &proto)
void AddAtMostOne(absl::Span< const int > amo)
int RemoveEnforcementThatMakesConstraintTrivial(absl::Span< const std::pair< int, int64_t > > boolean_terms, const Domain &other_terms, const Domain &rhs, ConstraintProto *ct)
std::vector< absl::Span< const int > > PartitionLiteralsIntoAmo(absl::Span< const int > literals)
bool PresolveEnforcement(absl::Span< const int > refs, ConstraintProto *ct, absl::flat_hash_set< int > *literals_at_true)
bool IsAmo(absl::Span< const int > literals)
int NumAmoForVariable(int var) const
void RegisterClause(int c, absl::Span< const int > clause)
uint64_t HashOfNegatedLiterals(absl::Span< const int > literals)
ConstraintCase constraint_case() const
::int32_t enforcement_literal(int index) const
void set_enforcement_literal(int index, ::int32_t value)
const ::operations_research::sat::LinearConstraintProto & linear() const
::operations_research::sat::LinearConstraintProto *PROTOBUF_NONNULL mutable_linear()
::google::protobuf::RepeatedField<::int32_t > *PROTOBUF_NONNULL mutable_enforcement_literal()
const ::operations_research::sat::ConstraintProto & constraints(int index) const
std::vector< std::pair< int, Domain > > ProcessClause(absl::Span< const int > clause)
void AddDeduction(int literal_ref, int var, Domain domain)
Domain ImpliedDomain(int literal_ref, int var) const
::int32_t vars(int index) const
void add_vars(::int32_t value)
void add_coeffs(::int64_t value)
::int64_t coeffs(int index) const
bool FindSingleLinearDifference(const LinearConstraintProto &lin1, const LinearConstraintProto &lin2, int *var1, int64_t *coeff1, int *var2, int64_t *coeff2)
bool RefIsPositive(int ref)
bool SubstituteVariable(int var, int64_t var_coeff_in_definition, const ConstraintProto &definition, ConstraintProto *ct)
bool AddLinearConstraintMultiple(int64_t factor, const ConstraintProto &to_add, ConstraintProto *to_modify)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
bool AtMinOrMaxInt64(int64_t x)
int64_t CapAdd(int64_t x, int64_t y)
ClosedInterval::Iterator end(ClosedInterval interval)
int64_t CapProd(int64_t x, int64_t y)
ClosedInterval::Iterator begin(ClosedInterval interval)