26#include "absl/container/flat_hash_map.h"
27#include "absl/container/flat_hash_set.h"
28#include "absl/log/check.h"
29#include "absl/meta/type_traits.h"
30#include "absl/random/distributions.h"
31#include "absl/strings/str_cat.h"
32#include "absl/strings/str_format.h"
33#include "absl/strings/str_join.h"
34#include "absl/types/span.h"
36#include "ortools/sat/cp_model.pb.h"
49 time_limit_->AdvanceDeterministicTime(work_);
51 std::string counter_string;
52 for (
const auto& [counter_name, count] : counters_) {
53 absl::StrAppend(&counter_string,
" #", counter_name,
"=",
58 SOLVER_LOG(logger_, absl::StrFormat(
" %.2es", timer_.Get()),
59 absl::StrFormat(
" %.2ed", work_),
61 absl::StrCat(
"[", name_,
"]"), counter_string,
" ",
62 absl::StrJoin(extra_infos_,
" "));
67 const Index index = IndexFromLiteral(literal_ref);
68 if (index >= something_changed_.size()) {
69 something_changed_.Resize(index + 1);
70 enforcement_to_vars_.resize(index.value() + 1);
72 if (var >= tmp_num_occurrences_.size()) {
73 tmp_num_occurrences_.resize(var + 1, 0);
75 const auto insert = deductions_.insert({{index, var}, domain});
78 something_changed_.Set(index);
79 enforcement_to_vars_[index].push_back(var);
82 const Domain& old_domain = insert.first->second;
85 something_changed_.Set(index);
92 const Index index = IndexFromLiteral(literal_ref);
93 const auto it = deductions_.find({index, var});
99 absl::Span<const int> clause) {
100 std::vector<std::pair<int, Domain>> result;
104 for (
const int ref : clause) {
105 const Index index = IndexFromLiteral(ref);
106 if (index >= something_changed_.size())
return result;
107 if (something_changed_[index]) {
111 if (abort)
return result;
114 std::vector<int> to_process;
115 std::vector<int> to_clean;
116 for (
const int ref : clause) {
117 const Index index = IndexFromLiteral(ref);
118 for (
const int var : enforcement_to_vars_[index]) {
119 if (tmp_num_occurrences_[var] == 0) {
120 to_clean.push_back(var);
122 tmp_num_occurrences_[var]++;
123 if (tmp_num_occurrences_[var] == clause.size()) {
124 to_process.push_back(var);
130 for (
const int var : to_clean) {
131 tmp_num_occurrences_[var] = 0;
135 std::vector<Domain> domains(to_process.size());
136 for (
const int ref : clause) {
137 const Index index = IndexFromLiteral(ref);
138 for (
int i = 0;
i < to_process.size(); ++
i) {
139 domains[
i] = domains[
i].UnionWith(deductions_.at({index, to_process[i]}));
143 for (
int i = 0;
i < to_process.size(); ++
i) {
144 result.push_back({to_process[
i], std::move(domains[
i])});
154bool SortAndMergeTerms(std::vector<std::pair<int, int64_t>>* terms) {
155 std::sort(terms->begin(), terms->end());
159 int64_t current_coeff = 0;
160 for (
const auto& entry : *terms) {
162 if (entry.first == current_var) {
163 current_coeff =
CapAdd(current_coeff, entry.second);
166 if (current_coeff != 0) {
167 (*terms)[new_size++] = {current_var, current_coeff};
169 current_var = entry.first;
170 current_coeff = entry.second;
173 if (current_coeff != 0) {
174 (*terms)[new_size++] = {current_var, current_coeff};
176 terms->resize(new_size);
183 ConstraintProto* to_modify) {
184 if (factor == 0)
return true;
185 DCHECK_EQ(to_add.constraint_case(), ConstraintProto::kLinear);
186 DCHECK_EQ(to_modify->constraint_case(), ConstraintProto::kLinear);
189 std::vector<std::pair<int, int64_t>> terms;
190 LinearConstraintProto* out = to_modify->mutable_linear();
191 const int initial_size = out->vars().size();
192 for (
int i = 0;
i < initial_size; ++
i) {
193 const int var = out->vars(
i);
194 const int64_t coeff = out->coeffs(
i);
196 terms.push_back({var, coeff});
200 const int to_add_size = to_add.linear().vars().size();
201 for (
int i = 0;
i < to_add_size; ++
i) {
202 const int var = to_add.linear().vars(
i);
203 const int64_t coeff = to_add.linear().coeffs(
i);
205 terms.push_back({var,
CapProd(coeff, factor)});
210 if (!SortAndMergeTerms(&terms))
return false;
215 for (
const auto [var, coeff] : terms) {
217 out->add_coeffs(coeff);
233 const ConstraintProto& definition,
234 ConstraintProto* ct) {
240 int64_t var_coeff = 0;
241 const int initial_ct_size = ct->linear().vars().size();
242 for (
int i = 0;
i < initial_ct_size; ++
i) {
243 const int ref = ct->linear().vars(
i);
247 var_coeff += ct->linear().coeffs(
i);
250 if (var_coeff == 0)
return false;
252 CHECK_EQ(std::abs(var_coeff_in_definition), 1);
253 const int64_t factor = var_coeff_in_definition > 0 ? -var_coeff : var_coeff;
258 num_at_most_ones_ = 0;
259 amo_indices_.clear();
264 const int complexity_limit = 50;
265 for (
const int literal : amo) {
266 const Index
i = IndexFromLiteral(literal);
267 if (
i >= amo_indices_.size()) amo_indices_.resize(
i + 1);
268 if (amo_indices_[
i].size() >= complexity_limit) ++num_skipped;
270 if (num_skipped + 1 >= amo.size())
return;
273 const int unique_index = num_at_most_ones_++;
274 for (
const int literal : amo) {
275 const Index
i = IndexFromLiteral(literal);
276 if (amo_indices_[
i].size() < complexity_limit) {
277 amo_indices_[
i].push_back(unique_index);
284 for (
const ConstraintProto& ct : proto.constraints()) {
285 const auto type = ct.constraint_case();
286 if (type == ConstraintProto::kAtMostOne) {
288 }
else if (type == ConstraintProto::kExactlyOne) {
290 }
else if (type == ConstraintProto::kBoolAnd) {
291 if (ct.enforcement_literal().size() == 1) {
292 const int a = ct.enforcement_literal(0);
293 for (
const int b : ct.bool_and().literals()) {
302int64_t ActivityBoundHelper::ComputeActivity(
303 bool compute_min, absl::Span<
const std::pair<int, int64_t>> terms,
304 std::vector<std::array<int64_t, 2>>* conditional) {
305 tmp_terms_for_compute_activity_.clear();
306 tmp_terms_for_compute_activity_.reserve(terms.size());
308 for (
auto [lit, coeff] : terms) {
309 if (compute_min) coeff = -coeff;
311 tmp_terms_for_compute_activity_.push_back({lit, coeff});
314 tmp_terms_for_compute_activity_.push_back({
NegatedRef(lit), -coeff});
318 const int64_t internal_result =
319 ComputeMaxActivityInternal(tmp_terms_for_compute_activity_, conditional);
322 if (conditional !=
nullptr) {
323 const int num_terms = terms.size();
324 for (
int i = 0;
i < num_terms; ++
i) {
325 if (tmp_terms_for_compute_activity_[
i].first != terms[
i].first) {
327 std::swap((*conditional)[
i][0], (*conditional)[
i][1]);
329 (*conditional)[
i][0] += offset;
330 (*conditional)[
i][1] += offset;
332 (*conditional)[
i][0] = -(*conditional)[
i][0];
333 (*conditional)[
i][1] = -(*conditional)[
i][1];
337 if (compute_min)
return -(offset + internal_result);
338 return offset + internal_result;
345void ActivityBoundHelper::PartitionIntoAmo(
346 absl::Span<
const std::pair<int, int64_t>> terms) {
347 auto amo_sums = amo_sums_.ClearedView(num_at_most_ones_);
349 const int num_terms = terms.size();
351 to_sort_.reserve(num_terms);
352 for (
int i = 0;
i < num_terms; ++
i) {
353 const Index index = IndexFromLiteral(terms[
i].first);
354 const int64_t coeff = terms[
i].second;
356 if (index < amo_indices_.size()) {
357 for (
const int a : amo_indices_[index]) {
358 amo_sums[a] += coeff;
362 TermWithIndex{.coeff = coeff, .index = index, .span_index =
i});
364 std::sort(to_sort_.begin(), to_sort_.end(),
365 [](
const TermWithIndex& a,
const TermWithIndex&
b) {
368 return std::tie(a.coeff, a.index) > std::tie(b.coeff, b.index);
372 partition_.resize(num_terms);
373 for (
const TermWithIndex& term : to_sort_) {
374 const int original_i = term.span_index;
375 const Index index = term.index;
376 const int64_t coeff = term.coeff;
378 int64_t best_sum = 0;
380 if (index < amo_indices_.size()) {
381 for (
const int a : amo_indices_[index]) {
384 const int64_t sum_left = amo_sums[a];
386 partition_[original_i] = -sum_left - 1;
391 amo_sums[a] -= coeff;
392 if (sum_left > best_sum) {
402 partition_[original_i] = num_parts++;
404 amo_sums[best] = -num_parts - 1;
405 partition_[original_i] = num_parts;
409 for (
const int p : partition_) CHECK_LT(p, num_parts);
410 CHECK_LE(num_parts, num_terms);
414std::vector<absl::Span<const int>>
416 auto amo_sums = amo_sums_.ClearedView(num_at_most_ones_);
417 for (
const int ref : literals) {
418 const Index index = IndexFromLiteral(ref);
419 if (index < amo_indices_.size()) {
420 for (
const int a : amo_indices_[index]) {
427 const int num_literals = literals.size();
428 partition_.resize(num_literals);
429 for (
int i = 0;
i < literals.size(); ++
i) {
430 const Index index = IndexFromLiteral(literals[
i]);
432 int64_t best_sum = 0;
434 if (index < amo_indices_.size()) {
435 for (
const int a : amo_indices_[index]) {
436 const int64_t sum_left = amo_sums[a];
441 partition_[
i] = -sum_left - 1;
447 if (sum_left > best_sum) {
457 amo_sums[best] = -num_parts - 1;
459 partition_[
i] = num_parts++;
463 part_to_literals_.ResetFromFlatMapping(partition_, literals);
464 DCHECK_EQ(part_to_literals_.size(), num_parts);
465 return part_to_literals_.AsVectorOfSpan();
469 auto amo_sums = amo_sums_.ClearedView(num_at_most_ones_);
470 for (
int i = 0;
i < literals.size(); ++
i) {
471 bool has_max_size =
false;
472 const Index index = IndexFromLiteral(literals[
i]);
473 if (index >= amo_indices_.size())
return false;
474 for (
const int a : amo_indices_[index]) {
475 if (amo_sums[a]++ ==
i) has_max_size =
true;
477 if (!has_max_size)
return false;
482int64_t ActivityBoundHelper::ComputeMaxActivityInternal(
483 absl::Span<
const std::pair<int, int64_t>> terms,
484 std::vector<std::array<int64_t, 2>>* conditional) {
485 PartitionIntoAmo(terms);
488 const int num_terms = terms.size();
489 max_by_partition_.assign(num_terms, 0);
490 second_max_by_partition_.assign(num_terms, 0);
491 for (
int i = 0;
i < num_terms; ++
i) {
492 const int p = partition_[
i];
493 const int64_t coeff = terms[
i].second;
494 if (coeff >= max_by_partition_[p]) {
495 second_max_by_partition_[p] = max_by_partition_[p];
496 max_by_partition_[p] = coeff;
497 }
else if (coeff > second_max_by_partition_[p]) {
498 second_max_by_partition_[p] = coeff;
503 int64_t max_activity = 0;
504 for (
int p = 0; p < partition_.size(); ++p) {
505 max_activity += max_by_partition_[p];
507 if (conditional !=
nullptr) {
508 conditional->resize(num_terms);
509 for (
int i = 0;
i < num_terms; ++
i) {
510 const int64_t coeff = terms[
i].second;
511 const int p = partition_[
i];
512 const int64_t max_used = max_by_partition_[p];
516 if (coeff == max_used) {
518 (*conditional)[
i][0] =
519 max_activity - max_used + second_max_by_partition_[p];
520 (*conditional)[
i][1] = max_activity;
523 (*conditional)[
i][0] = max_activity;
524 (*conditional)[
i][1] = max_activity - max_used + coeff;
531bool ActivityBoundHelper::AppearInTriggeredAmo(
int literal) {
532 const Index index = IndexFromLiteral(literal);
533 if (index >= amo_indices_.size())
return false;
534 for (
const int amo : amo_indices_[index]) {
535 if (triggered_amo_.contains(amo))
return true;
541 absl::Span<const int> refs, ConstraintProto* ct,
542 absl::flat_hash_set<int>* literals_at_true) {
543 if (ct->enforcement_literal().empty())
return true;
545 literals_at_true->clear();
546 triggered_amo_.clear();
548 for (
int i = 0;
i < ct->enforcement_literal().size(); ++
i) {
549 const int ref = ct->enforcement_literal(
i);
550 if (literals_at_true->contains(ref))
continue;
551 if (literals_at_true->contains(
NegatedRef(ref)))
return false;
552 literals_at_true->insert(ref);
559 if (AppearInTriggeredAmo(
NegatedRef(ref)))
continue;
561 const Index index = IndexFromLiteral(ref);
562 if (index < amo_indices_.size()) {
563 for (
const int a : amo_indices_[index]) {
566 const auto [_, inserted] = triggered_amo_.insert(a);
567 if (!inserted)
return false;
572 ct->set_enforcement_literal(new_size++, ref);
574 ct->mutable_enforcement_literal()->Truncate(new_size);
576 for (
const int ref : refs) {
578 if (literals_at_true->contains(ref))
continue;
579 if (literals_at_true->contains(
NegatedRef(ref)))
continue;
580 for (
const int to_test : {ref,
NegatedRef(ref)}) {
581 const Index index = IndexFromLiteral(to_test);
582 if (index < amo_indices_.size()) {
583 for (
const int a : amo_indices_[index]) {
584 if (triggered_amo_.contains(a)) {
587 if (literals_at_true->contains(to_test))
return false;
588 literals_at_true->insert(
NegatedRef(to_test));
600 absl::Span<
const std::pair<int, int64_t>> boolean_terms,
601 const Domain& other_terms,
const Domain& rhs, ConstraintProto* ct) {
603 for (
const int enf_lit : ct->enforcement_literal()) {
604 const Index negated_index = IndexFromLiteral(
NegatedRef(enf_lit));
605 if (negated_index >= amo_indices_.size())
continue;
606 if (amo_indices_[negated_index].empty())
continue;
608 triggered_amo_.clear();
609 triggered_amo_.insert(amo_indices_[negated_index].begin(),
610 amo_indices_[negated_index].end());
613 int64_t min_activity = 0;
614 int64_t max_activity = 0;
615 for (
const auto [ref, coeff] : boolean_terms) {
618 if (ref == enf_lit || ref ==
NegatedRef(enf_lit))
break;
620 const bool is_true = AppearInTriggeredAmo(
NegatedRef(ref));
621 const bool is_false = AppearInTriggeredAmo(ref);
624 if (is_true && is_false)
break;
626 if (is_false)
continue;
628 min_activity += coeff;
629 max_activity += coeff;
633 max_activity += coeff;
635 min_activity += coeff;
639 if (
Domain(min_activity, max_activity)
640 .AdditionWith(other_terms)
641 .IsIncludedIn(rhs)) {
642 tmp_set_.insert(enf_lit);
646 if (!tmp_set_.empty()) {
648 for (
const int ref : ct->enforcement_literal()) {
649 if (tmp_set_.contains(ref))
continue;
650 ct->set_enforcement_literal(new_size++, ref);
652 const int old_size = ct->enforcement_literal().size();
653 ct->mutable_enforcement_literal()->Truncate(new_size);
654 return old_size - new_size;
660 absl::Span<const int> clause) {
662 for (
const int ref : clause) {
663 const Index index = IndexFromLiteral(ref);
664 while (index >= literal_to_hash_.size()) {
666 literal_to_hash_.push_back(absl::Uniform<uint64_t>(random_));
668 hash ^= literal_to_hash_[index];
671 if (c >= clause_to_hash_.size()) clause_to_hash_.resize(c + 1, 0);
672 clause_to_hash_[c] = hash;
676 absl::Span<const int> literals) {
678 for (
const int ref : literals) {
679 const Index index = IndexFromLiteral(
NegatedRef(ref));
680 while (index >= literal_to_hash_.size()) {
682 literal_to_hash_.push_back(absl::Uniform<uint64_t>(random_));
684 hash ^= literal_to_hash_[index];
690 const LinearConstraintProto& lin2,
int* var1,
691 int64_t* coeff1,
int* var2, int64_t* coeff2) {
692 const int size = lin1.vars().size();
693 CHECK_EQ(size, lin2.vars().size());
698 while (
i < size || j < size) {
700 const int v1 =
i < size ? lin1.vars(
i) : std::numeric_limits<int>::max();
701 const int v2 = j < size ? lin2.vars(j) : std::numeric_limits<int>::max();
704 if (v1 == v2 && lin1.coeffs(
i) == lin2.coeffs(j)) {
713 if (*coeff1 != 0)
return false;
715 *coeff1 = lin1.coeffs(
i);
722 if (*coeff2 != 0)
return false;
724 *coeff2 = lin2.coeffs(j);
730 if (*coeff1 != 0 || *coeff2 != 0)
return false;
733 *coeff1 = lin1.coeffs(
i);
734 *coeff2 = lin2.coeffs(j);
739 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)
Similar algo as above for this simpler case.
bool PresolveEnforcement(absl::Span< const int > refs, ConstraintProto *ct, absl::flat_hash_set< int > *literals_at_true)
bool IsAmo(absl::Span< const int > literals)
Returns true iff the given literal are in at most one relationship.
void RegisterClause(int c, absl::Span< const int > clause)
We use the proto encoding of literals here.
uint64_t HashOfNegatedLiterals(absl::Span< const int > literals)
Returns a hash of the negation of all the given literals.
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
bool WorkLimitIsReached() const
~PresolveTimer()
Update dtime and log operation summary.
bool FindSingleLinearDifference(const LinearConstraintProto &lin1, const LinearConstraintProto &lin2, int *var1, int64_t *coeff1, int *var2, int64_t *coeff2)
Same as LinearsDifferAtOneTerm() below but also fills the differing terms.
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)
std::string FormatCounter(int64_t num)
Prints a positive number with separators for easier reading (ex: 1'348'065).
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
Serializes a Domain into the domain field of a proto.
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
Reads a Domain from the domain field of a proto.
int NegatedRef(int ref)
Small utility functions to deal with negative variable/literal references.
In SWIG mode, we don't want anything besides these top-level includes.
bool AtMinOrMaxInt64(int64_t x)
Checks if x is equal to the min or the max value of an int64_t.
int64_t CapAdd(int64_t x, int64_t y)
int64_t CapProd(int64_t x, int64_t y)
#define SOLVER_LOG(logger,...)