27#include "absl/container/flat_hash_map.h"
28#include "absl/container/flat_hash_set.h"
29#include "absl/log/check.h"
30#include "absl/meta/type_traits.h"
31#include "absl/random/distributions.h"
32#include "absl/strings/str_cat.h"
33#include "absl/strings/str_format.h"
34#include "absl/strings/str_join.h"
35#include "absl/types/span.h"
37#include "ortools/sat/cp_model.pb.h"
52 std::string counter_string;
53 for (
const auto& [counter_name, count] : counters_) {
54 absl::StrAppend(&counter_string,
" #", counter_name,
"=",
60 absl::StrFormat(
" %.2ed", work_),
62 absl::StrCat(
"[", name_,
"]"), counter_string,
" ",
63 absl::StrJoin(extra_infos_,
" "));
68 const Index
index = IndexFromLiteral(literal_ref);
73 if (
var >= tmp_num_occurrences_.size()) {
74 tmp_num_occurrences_.resize(
var + 1, 0);
76 const auto insert = deductions_.insert({{
index,
var}, domain});
83 const Domain& old_domain = insert.first->second;
93 const Index
index = IndexFromLiteral(literal_ref);
94 const auto it = deductions_.find({
index,
var});
100 absl::Span<const int> clause) {
101 std::vector<std::pair<int, Domain>> result;
105 for (
const int ref : clause) {
106 const Index
index = IndexFromLiteral(ref);
107 if (
index >= something_changed_.
size())
return result;
108 if (something_changed_[
index]) {
112 if (abort)
return result;
115 std::vector<int> to_process;
116 std::vector<int> to_clean;
117 for (
const int ref : clause) {
118 const Index
index = IndexFromLiteral(ref);
119 for (
const int var : enforcement_to_vars_[
index]) {
120 if (tmp_num_occurrences_[
var] == 0) {
121 to_clean.push_back(
var);
123 tmp_num_occurrences_[
var]++;
124 if (tmp_num_occurrences_[
var] == clause.size()) {
125 to_process.push_back(
var);
131 for (
const int var : to_clean) {
132 tmp_num_occurrences_[
var] = 0;
136 std::vector<Domain> domains(to_process.size());
137 for (
const int ref : clause) {
138 const Index
index = IndexFromLiteral(ref);
139 for (
int i = 0;
i < to_process.size(); ++
i) {
140 domains[
i] = domains[
i].UnionWith(deductions_.at({index, to_process[i]}));
144 for (
int i = 0;
i < to_process.size(); ++
i) {
145 result.push_back({to_process[
i], std::move(domains[
i])});
155bool SortAndMergeTerms(std::vector<std::pair<int, int64_t>>* terms) {
156 std::sort(terms->begin(), terms->end());
160 int64_t current_coeff = 0;
161 for (
const auto& entry : *terms) {
163 if (entry.first == current_var) {
164 current_coeff =
CapAdd(current_coeff, entry.second);
167 if (current_coeff != 0) {
168 (*terms)[new_size++] = {current_var, current_coeff};
170 current_var = entry.first;
171 current_coeff = entry.second;
174 if (current_coeff != 0) {
175 (*terms)[new_size++] = {current_var, current_coeff};
177 terms->resize(new_size);
184 ConstraintProto* to_modify) {
185 if (factor == 0)
return true;
186 DCHECK_EQ(to_add.constraint_case(), ConstraintProto::kLinear);
187 DCHECK_EQ(to_modify->constraint_case(), ConstraintProto::kLinear);
190 std::vector<std::pair<int, int64_t>> terms;
191 LinearConstraintProto* out = to_modify->mutable_linear();
192 const int initial_size = out->vars().size();
193 for (
int i = 0;
i < initial_size; ++
i) {
194 const int var = out->vars(
i);
195 const int64_t coeff = out->coeffs(
i);
197 terms.push_back({
var, coeff});
201 const int to_add_size = to_add.linear().vars().size();
202 for (
int i = 0;
i < to_add_size; ++
i) {
203 const int var = to_add.linear().vars(
i);
204 const int64_t coeff = to_add.linear().coeffs(
i);
206 terms.push_back({
var,
CapProd(coeff, factor)});
211 if (!SortAndMergeTerms(&terms))
return false;
216 for (
const auto [
var, coeff] : terms) {
218 out->add_coeffs(coeff);
234 const ConstraintProto& definition,
235 ConstraintProto*
ct) {
241 int64_t var_coeff = 0;
242 const int initial_ct_size =
ct->linear().vars().size();
243 for (
int i = 0;
i < initial_ct_size; ++
i) {
244 const int ref =
ct->linear().vars(
i);
248 var_coeff +=
ct->linear().coeffs(
i);
251 if (var_coeff == 0)
return false;
253 CHECK_EQ(std::abs(var_coeff_in_definition), 1);
254 const int64_t factor = var_coeff_in_definition > 0 ? -var_coeff : var_coeff;
259 num_at_most_ones_ = 0;
260 amo_indices_.clear();
265 const int complexity_limit = 50;
266 for (
const int literal : amo) {
267 const Index
i = IndexFromLiteral(
literal);
268 if (
i >= amo_indices_.size()) amo_indices_.
resize(
i + 1);
269 if (amo_indices_[
i].
size() >= complexity_limit) ++num_skipped;
271 if (num_skipped + 1 >= amo.size())
return;
274 const int unique_index = num_at_most_ones_++;
275 for (
const int literal : amo) {
276 const Index
i = IndexFromLiteral(
literal);
277 if (amo_indices_[
i].
size() < complexity_limit) {
285 for (
const ConstraintProto&
ct :
proto.constraints()) {
286 const auto type =
ct.constraint_case();
287 if (type == ConstraintProto::kAtMostOne) {
289 }
else if (type == ConstraintProto::kExactlyOne) {
291 }
else if (type == ConstraintProto::kBoolAnd) {
292 if (
ct.enforcement_literal().size() == 1) {
293 const int a =
ct.enforcement_literal(0);
294 for (
const int b :
ct.bool_and().literals()) {
303int64_t ActivityBoundHelper::ComputeActivity(
304 bool compute_min, absl::Span<
const std::pair<int, int64_t>> terms,
305 std::vector<std::array<int64_t, 2>>* conditional) {
306 tmp_terms_for_compute_activity_.clear();
307 tmp_terms_for_compute_activity_.reserve(terms.size());
309 for (
auto [
lit, coeff] : terms) {
310 if (compute_min) coeff = -coeff;
312 tmp_terms_for_compute_activity_.push_back({
lit, coeff});
315 tmp_terms_for_compute_activity_.push_back({
NegatedRef(
lit), -coeff});
319 const int64_t internal_result =
320 ComputeMaxActivityInternal(tmp_terms_for_compute_activity_, conditional);
323 if (conditional !=
nullptr) {
324 const int num_terms = terms.size();
325 for (
int i = 0;
i < num_terms; ++
i) {
326 if (tmp_terms_for_compute_activity_[
i].first != terms[
i].first) {
328 std::swap((*conditional)[
i][0], (*conditional)[
i][1]);
330 (*conditional)[
i][0] += offset;
331 (*conditional)[
i][1] += offset;
333 (*conditional)[
i][0] = -(*conditional)[
i][0];
334 (*conditional)[
i][1] = -(*conditional)[
i][1];
338 if (compute_min)
return -(offset + internal_result);
339 return offset + internal_result;
346void ActivityBoundHelper::PartitionIntoAmo(
347 absl::Span<
const std::pair<int, int64_t>> terms) {
350 const int num_terms = terms.size();
352 to_sort_.reserve(num_terms);
353 for (
int i = 0;
i < num_terms; ++
i) {
354 const Index index = IndexFromLiteral(terms[
i].first);
355 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 used_amo_to_dense_index_.clear();
374 for (
const TermWithIndex& term : to_sort_) {
375 const int original_i = term.span_index;
377 const int64_t coeff = term.coeff;
379 int64_t best_sum = 0;
381 if (
index < amo_indices_.size()) {
382 for (
const int a : amo_indices_[
index]) {
383 const auto it = used_amo_to_dense_index_.find(
a);
384 if (it != used_amo_to_dense_index_.end()) {
385 partition_[original_i] = it->second;
390 const int64_t sum_left = amo_sums_[
a];
391 amo_sums_[
a] -= coeff;
392 if (sum_left > best_sum) {
402 partition_[original_i] = num_parts++;
404 used_amo_to_dense_index_[best] = num_parts;
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>>
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 used_amo_to_dense_index_.clear();
430 for (
int i = 0;
i < literals.size(); ++
i) {
431 const Index
index = IndexFromLiteral(literals[
i]);
433 int64_t best_sum = 0;
435 if (
index < amo_indices_.size()) {
436 for (
const int a : amo_indices_[
index]) {
437 const auto it = used_amo_to_dense_index_.find(
a);
438 if (it != used_amo_to_dense_index_.end()) {
439 partition_[
i] = it->second;
444 const int64_t sum_left = amo_sums_[
a];
446 if (sum_left > best_sum) {
456 used_amo_to_dense_index_[best] = num_parts;
458 partition_[
i] = num_parts++;
463 DCHECK_EQ(part_to_literals_.
size(), num_parts);
469 for (
int i = 0;
i < literals.size(); ++
i) {
470 bool has_max_size =
false;
471 const Index
index = IndexFromLiteral(literals[
i]);
472 if (
index >= amo_indices_.size())
return false;
473 for (
const int a : amo_indices_[
index]) {
474 if (amo_sums_[
a]++ ==
i) has_max_size =
true;
476 if (!has_max_size)
return false;
481int64_t ActivityBoundHelper::ComputeMaxActivityInternal(
482 absl::Span<
const std::pair<int, int64_t>> terms,
483 std::vector<std::array<int64_t, 2>>* conditional) {
484 PartitionIntoAmo(terms);
487 const int num_terms = terms.size();
488 max_by_partition_.assign(num_terms, 0);
489 second_max_by_partition_.assign(num_terms, 0);
490 for (
int i = 0;
i < num_terms; ++
i) {
491 const int p = partition_[
i];
492 const int64_t coeff = terms[
i].second;
493 if (coeff >= max_by_partition_[p]) {
494 second_max_by_partition_[p] = max_by_partition_[p];
495 max_by_partition_[p] = coeff;
496 }
else if (coeff > second_max_by_partition_[p]) {
497 second_max_by_partition_[p] = coeff;
502 int64_t max_activity = 0;
503 for (
int p = 0; p < partition_.size(); ++p) {
504 max_activity += max_by_partition_[p];
506 if (conditional !=
nullptr) {
507 conditional->resize(num_terms);
508 for (
int i = 0;
i < num_terms; ++
i) {
509 const int64_t coeff = terms[
i].second;
510 const int p = partition_[
i];
511 const int64_t max_used = max_by_partition_[p];
515 if (coeff == max_used) {
517 (*conditional)[
i][0] =
518 max_activity - max_used + second_max_by_partition_[p];
519 (*conditional)[
i][1] = max_activity;
522 (*conditional)[
i][0] = max_activity;
523 (*conditional)[
i][1] = max_activity - max_used + coeff;
530bool ActivityBoundHelper::AppearInTriggeredAmo(
int literal) {
532 if (
index >= amo_indices_.size())
return false;
533 for (
const int amo : amo_indices_[
index]) {
534 if (triggered_amo_.contains(amo))
return true;
540 absl::Span<const int> refs, ConstraintProto*
ct,
541 absl::flat_hash_set<int>* literals_at_true) {
542 if (
ct->enforcement_literal().empty())
return true;
544 literals_at_true->clear();
545 triggered_amo_.clear();
547 for (
int i = 0;
i <
ct->enforcement_literal().
size(); ++
i) {
548 const int ref =
ct->enforcement_literal(
i);
549 if (literals_at_true->contains(ref))
continue;
550 if (literals_at_true->contains(
NegatedRef(ref)))
return false;
551 literals_at_true->insert(ref);
558 if (AppearInTriggeredAmo(
NegatedRef(ref)))
continue;
560 const Index
index = IndexFromLiteral(ref);
561 if (
index < amo_indices_.size()) {
562 for (
const int a : amo_indices_[
index]) {
565 const auto [_, inserted] = triggered_amo_.insert(
a);
566 if (!inserted)
return false;
571 ct->set_enforcement_literal(new_size++, ref);
573 ct->mutable_enforcement_literal()->Truncate(new_size);
575 for (
const int ref : refs) {
577 if (literals_at_true->contains(ref))
continue;
578 if (literals_at_true->contains(
NegatedRef(ref)))
continue;
579 for (
const int to_test : {ref,
NegatedRef(ref)}) {
580 const Index
index = IndexFromLiteral(to_test);
581 if (
index < amo_indices_.size()) {
582 for (
const int a : amo_indices_[
index]) {
583 if (triggered_amo_.contains(
a)) {
586 if (literals_at_true->contains(to_test))
return false;
587 literals_at_true->insert(
NegatedRef(to_test));
599 absl::Span<
const std::pair<int, int64_t>> boolean_terms,
600 const Domain& other_terms,
const Domain& rhs, ConstraintProto*
ct) {
602 for (
const int enf_lit :
ct->enforcement_literal()) {
603 const Index negated_index = IndexFromLiteral(
NegatedRef(enf_lit));
604 if (negated_index >= amo_indices_.size())
continue;
605 if (amo_indices_[negated_index].empty())
continue;
607 triggered_amo_.clear();
608 triggered_amo_.insert(amo_indices_[negated_index].begin(),
609 amo_indices_[negated_index].
end());
612 int64_t min_activity = 0;
613 int64_t max_activity = 0;
614 for (
const auto [ref, coeff] : boolean_terms) {
617 if (ref == enf_lit || ref ==
NegatedRef(enf_lit))
break;
619 const bool is_true = AppearInTriggeredAmo(
NegatedRef(ref));
620 const bool is_false = AppearInTriggeredAmo(ref);
623 if (is_true && is_false)
break;
625 if (is_false)
continue;
627 min_activity += coeff;
628 max_activity += coeff;
632 max_activity += coeff;
634 min_activity += coeff;
638 if (
Domain(min_activity, max_activity)
639 .AdditionWith(other_terms)
640 .IsIncludedIn(rhs)) {
641 tmp_set_.insert(enf_lit);
645 if (!tmp_set_.empty()) {
647 for (
const int ref :
ct->enforcement_literal()) {
648 if (tmp_set_.contains(ref))
continue;
649 ct->set_enforcement_literal(new_size++, ref);
651 const int old_size =
ct->enforcement_literal().size();
652 ct->mutable_enforcement_literal()->Truncate(new_size);
653 return old_size - new_size;
659 absl::Span<const int> clause) {
661 for (
const int ref : clause) {
662 const Index
index = IndexFromLiteral(ref);
663 while (
index >= literal_to_hash_.size()) {
665 literal_to_hash_.
push_back(absl::Uniform<uint64_t>(random_));
670 if (c >= clause_to_hash_.size()) clause_to_hash_.resize(c + 1, 0);
671 clause_to_hash_[c] =
hash;
675 absl::Span<const int> literals) {
677 for (
const int ref : literals) {
679 while (
index >= literal_to_hash_.size()) {
681 literal_to_hash_.
push_back(absl::Uniform<uint64_t>(random_));
689 const LinearConstraintProto& lin2,
int* var1,
690 int64_t* coeff1,
int* var2, int64_t* coeff2) {
691 const int size = lin1.vars().size();
692 CHECK_EQ(
size, lin2.vars().size());
699 const int v1 =
i <
size ? lin1.vars(
i) : std::numeric_limits<int>::max();
700 const int v2 = j <
size ? lin2.vars(j) : std::numeric_limits<int>::max();
703 if (v1 == v2 && lin1.coeffs(
i) == lin2.coeffs(j)) {
712 if (*coeff1 != 0)
return false;
714 *coeff1 = lin1.coeffs(
i);
721 if (*coeff2 != 0)
return false;
723 *coeff2 = lin2.coeffs(j);
729 if (*coeff1 != 0 || *coeff2 != 0)
return false;
732 *coeff1 = lin1.coeffs(
i);
733 *coeff2 = lin2.coeffs(j);
738 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 Set(IntegerType index)
void Resize(IntegerType size)
void AdvanceDeterministicTime(double deterministic_duration)
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.
void ResetFromFlatMapping(Keys keys, Values values)
std::vector< absl::Span< const V > > AsVectorOfSpan() const
size_t size() const
Size of the "key" space, always in [0, size()).
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.
void push_back(const value_type &val)
void resize(size_type new_size)
CpModelProto proto
The output proto.
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)
std::optional< int64_t > end
#define SOLVER_LOG(logger,...)