23 #include "absl/container/flat_hash_map.h"
24 #include "absl/meta/type_traits.h"
25 #include "absl/strings/str_join.h"
26 #include "absl/types/span.h"
29 #include "ortools/sat/cp_model.pb.h"
40 const Index index = IndexFromLiteral(literal_ref);
45 if (
var >= tmp_num_occurrences_.size()) {
46 tmp_num_occurrences_.resize(
var + 1, 0);
48 const auto insert = deductions_.insert({{
index,
var}, domain});
55 const Domain& old_domain = insert.first->second;
65 const Index index = IndexFromLiteral(literal_ref);
66 const auto it = deductions_.find({
index,
var});
72 absl::Span<const int> clause) {
73 std::vector<std::pair<int, Domain>> result;
77 for (
const int ref : clause) {
79 if (
index >= something_changed_.
size())
return result;
80 if (something_changed_[
index]) {
84 if (abort)
return result;
87 std::vector<int> to_process;
88 std::vector<int> to_clean;
89 for (
const int ref : clause) {
91 for (
const int var : enforcement_to_vars_[
index]) {
92 if (tmp_num_occurrences_[
var] == 0) {
93 to_clean.push_back(
var);
95 tmp_num_occurrences_[
var]++;
96 if (tmp_num_occurrences_[
var] == clause.size()) {
97 to_process.push_back(
var);
103 for (
const int var : to_clean) {
104 tmp_num_occurrences_[
var] = 0;
108 std::vector<Domain> domains(to_process.size());
109 for (
const int ref : clause) {
111 for (
int i = 0; i < to_process.size(); ++i) {
112 domains[i] = domains[i].UnionWith(deductions_.at({index, to_process[i]}));
116 for (
int i = 0; i < to_process.size(); ++i) {
117 result.push_back({to_process[i], std::move(domains[i])});
125 template <
typename ProtoWithVarsAndCoeffs>
126 int64_t GetVarCoeffAndCopyOtherTerms(
127 const int var,
const ProtoWithVarsAndCoeffs&
proto,
128 std::vector<std::pair<int, int64_t>>* terms) {
129 int64_t var_coeff = 0;
130 const int size =
proto.vars().size();
131 for (
int i = 0; i < size; ++i) {
132 int ref =
proto.vars(i);
133 int64_t coeff =
proto.coeffs(i);
144 terms->push_back({ref, coeff});
152 template <
typename ProtoWithVarsAndCoeffs>
153 void SortAndMergeTerms(std::vector<std::pair<int, int64_t>>* terms,
154 ProtoWithVarsAndCoeffs*
proto) {
156 proto->clear_coeffs();
157 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 += entry.second;
165 if (current_coeff != 0) {
166 proto->add_vars(current_var);
167 proto->add_coeffs(current_coeff);
169 current_var = entry.first;
170 current_coeff = entry.second;
173 if (current_coeff != 0) {
174 proto->add_vars(current_var);
175 proto->add_coeffs(current_coeff);
181 void AddTermsFromVarDefinition(
const int var,
const int64_t var_coeff,
182 const ConstraintProto& definition,
183 std::vector<std::pair<int, int64_t>>* terms) {
184 const int definition_size = definition.linear().vars().size();
185 for (
int i = 0; i < definition_size; ++i) {
186 int ref = definition.linear().vars(i);
187 int64_t coeff = definition.linear().coeffs(i);
196 terms->push_back({ref, -coeff * var_coeff});
203 const ConstraintProto& definition,
204 ConstraintProto*
ct) {
206 CHECK_EQ(std::abs(var_coeff_in_definition), 1);
209 std::vector<std::pair<int, int64_t>> terms;
210 int64_t var_coeff = GetVarCoeffAndCopyOtherTerms(
var,
ct->linear(), &terms);
211 if (var_coeff == 0)
return false;
213 if (var_coeff_in_definition < 0) var_coeff *= -1;
215 AddTermsFromVarDefinition(
var, var_coeff, definition, &terms);
227 SortAndMergeTerms(&terms,
ct->mutable_linear());
232 num_at_most_ones_ = 0;
233 amo_indices_.
clear();
238 const int complexity_limit = 50;
239 for (
const int literal : amo) {
241 if (i >= amo_indices_.
size()) amo_indices_.
resize(i + 1);
242 if (amo_indices_[i].size() >= complexity_limit) ++num_skipped;
244 if (num_skipped + 1 >= amo.size())
return;
247 const int unique_index = num_at_most_ones_++;
248 for (
const int literal : amo) {
250 if (amo_indices_[i].size() < complexity_limit) {
258 for (
const ConstraintProto&
ct :
proto.constraints()) {
259 const auto type =
ct.constraint_case();
260 if (type == ConstraintProto::kAtMostOne) {
262 }
else if (type == ConstraintProto::kExactlyOne) {
264 }
else if (type == ConstraintProto::kBoolAnd) {
265 if (
ct.enforcement_literal().size() == 1) {
266 const int a =
ct.enforcement_literal(0);
267 for (
const int b :
ct.bool_and().literals()) {
276 int64_t ActivityBoundHelper::ComputeActivity(
277 bool compute_min, absl::Span<
const std::pair<int, int64_t>> terms,
278 std::vector<std::array<int64_t, 2>>* conditional) {
280 tmp_terms_.reserve(terms.size());
282 for (
auto [lit, coeff] : terms) {
283 if (compute_min) coeff = -coeff;
285 tmp_terms_.push_back({lit, coeff});
288 tmp_terms_.push_back({
NegatedRef(lit), -coeff});
292 const int64_t internal_result =
293 ComputeMaxActivityInternal(tmp_terms_, conditional);
296 if (conditional !=
nullptr) {
297 const int num_terms = terms.size();
298 for (
int i = 0; i < num_terms; ++i) {
299 if (tmp_terms_[i].first != terms[i].first) {
301 std::swap((*conditional)[i][0], (*conditional)[i][1]);
303 (*conditional)[i][0] += offset;
304 (*conditional)[i][1] += offset;
306 (*conditional)[i][0] = -(*conditional)[i][0];
307 (*conditional)[i][1] = -(*conditional)[i][1];
311 if (compute_min)
return -(offset + internal_result);
312 return offset + internal_result;
319 void ActivityBoundHelper::PartitionIntoAmo(
320 absl::Span<
const std::pair<int, int64_t>> terms) {
323 const int num_terms = terms.size();
325 to_sort_.reserve(num_terms);
326 for (
int i = 0; i < num_terms; ++i) {
327 const Index index = IndexFromLiteral(terms[i].first);
328 const int64_t coeff = terms[i].second;
330 for (
const int a : amo_indices_[
index]) {
331 amo_sums_[
a] += coeff;
334 to_sort_.push_back({terms[i].second, i});
336 std::sort(to_sort_.begin(), to_sort_.end(), std::greater<>());
339 partition_.resize(num_terms);
340 used_amo_to_dense_index_.clear();
341 for (
int i = 0; i < num_terms; ++i) {
342 const int original_i = to_sort_[i].second;
343 const Index index = IndexFromLiteral(terms[original_i].first);
344 const int64_t coeff = terms[original_i].second;
346 int64_t best_sum = 0;
349 for (
const int a : amo_indices_[
index]) {
350 const auto it = used_amo_to_dense_index_.find(
a);
351 if (it != used_amo_to_dense_index_.end()) {
352 partition_[original_i] = it->second;
357 const int64_t sum_left = amo_sums_[
a];
358 amo_sums_[
a] -= coeff;
359 if (sum_left > best_sum) {
369 partition_[original_i] = num_parts++;
371 used_amo_to_dense_index_[best] = num_parts;
372 partition_[original_i] = num_parts;
376 for (
const int p : partition_) CHECK_LT(p, num_parts);
377 CHECK_LE(num_parts, num_terms);
381 std::vector<absl::Span<const int>>
384 for (
const int ref : literals) {
387 for (
const int a : amo_indices_[
index]) {
394 const int num_literals = literals.size();
395 partition_.resize(num_literals);
396 used_amo_to_dense_index_.clear();
397 for (
int i = 0; i < literals.size(); ++i) {
398 const Index index = IndexFromLiteral(literals[i]);
400 int64_t best_sum = 0;
403 for (
const int a : amo_indices_[
index]) {
404 const auto it = used_amo_to_dense_index_.find(
a);
405 if (it != used_amo_to_dense_index_.end()) {
406 partition_[i] = it->second;
411 const int64_t sum_left = amo_sums_[
a];
413 if (sum_left > best_sum) {
423 used_amo_to_dense_index_[best] = num_parts;
425 partition_[i] = num_parts++;
429 part_starts_.assign(num_parts, 0);
430 part_sizes_.assign(num_parts, 0);
431 part_ends_.assign(num_parts, 0);
432 for (
int i = 0; i < num_literals; ++i) {
433 DCHECK_GE(partition_[i], 0);
434 DCHECK_LT(partition_[i], num_parts);
435 part_sizes_[partition_[i]]++;
437 for (
int p = 1; p < num_parts; ++p) {
438 part_starts_[p] = part_ends_[p] = part_sizes_[p - 1] + part_starts_[p - 1];
440 reordered_literals_.resize(num_literals);
441 for (
int i = 0; i < num_literals; ++i) {
442 const int p = partition_[i];
443 reordered_literals_[part_ends_[p]++] = literals[i];
445 std::vector<absl::Span<const int>> result;
446 for (
int p = 0; p < num_parts; ++p) {
448 absl::MakeSpan(&reordered_literals_[part_starts_[p]], part_sizes_[p]));
455 for (
int i = 0; i < literals.size(); ++i) {
456 bool has_max_size =
false;
457 const Index index = IndexFromLiteral(literals[i]);
458 if (
index >= amo_indices_.
size())
return false;
459 for (
const int a : amo_indices_[
index]) {
460 if (amo_sums_[
a]++ == i) has_max_size =
true;
462 if (!has_max_size)
return false;
467 int64_t ActivityBoundHelper::ComputeMaxActivityInternal(
468 absl::Span<
const std::pair<int, int64_t>> terms,
469 std::vector<std::array<int64_t, 2>>* conditional) {
470 PartitionIntoAmo(terms);
473 const int num_terms = terms.size();
474 max_by_partition_.assign(num_terms, 0);
475 second_max_by_partition_.assign(num_terms, 0);
476 for (
int i = 0; i < num_terms; ++i) {
477 const int p = partition_[i];
478 const int64_t coeff = terms[i].second;
479 if (coeff >= max_by_partition_[p]) {
480 second_max_by_partition_[p] = max_by_partition_[p];
481 max_by_partition_[p] = coeff;
482 }
else if (coeff > second_max_by_partition_[p]) {
483 second_max_by_partition_[p] = coeff;
488 int64_t max_activity = 0;
489 for (
int p = 0; p < partition_.size(); ++p) {
490 max_activity += max_by_partition_[p];
492 if (conditional !=
nullptr) {
493 conditional->resize(num_terms);
494 for (
int i = 0; i < num_terms; ++i) {
495 const int64_t coeff = terms[i].second;
496 const int p = partition_[i];
497 const int64_t max_used = max_by_partition_[p];
501 if (coeff == max_used) {
503 (*conditional)[i][0] =
504 max_activity - max_used + second_max_by_partition_[p];
505 (*conditional)[i][1] = max_activity;
508 (*conditional)[i][0] = max_activity;
509 (*conditional)[i][1] = max_activity - max_used + coeff;
517 absl::Span<const int> refs, ConstraintProto*
ct,
518 absl::flat_hash_set<int>* literals_at_true) {
519 if (
ct->enforcement_literal().empty())
return true;
521 literals_at_true->clear();
522 triggered_amo_.clear();
524 for (
int i = 0; i <
ct->enforcement_literal().size(); ++i) {
525 const int ref =
ct->enforcement_literal(i);
526 if (literals_at_true->contains(ref))
continue;
527 if (literals_at_true->contains(
NegatedRef(ref)))
return false;
528 literals_at_true->insert(ref);
536 if (negated_index < amo_indices_.
size()) {
538 for (
const int a : amo_indices_[negated_index]) {
539 if (triggered_amo_.contains(
a)) {
549 for (
const int a : amo_indices_[
index]) {
552 const auto [_, inserted] = triggered_amo_.insert(
a);
553 if (!inserted)
return false;
558 ct->set_enforcement_literal(new_size++, ref);
560 ct->mutable_enforcement_literal()->Truncate(new_size);
562 for (
const int ref : refs) {
564 if (literals_at_true->contains(ref))
continue;
565 if (literals_at_true->contains(
NegatedRef(ref)))
continue;
566 for (
const int to_test : {ref,
NegatedRef(ref)}) {
567 const Index index = IndexFromLiteral(to_test);
569 for (
const int a : amo_indices_[
index]) {
570 if (triggered_amo_.contains(
a)) {
573 if (literals_at_true->contains(to_test))
return false;
574 literals_at_true->insert(
NegatedRef(to_test));
586 absl::Span<const int> clause) {
588 for (
const int ref : clause) {
590 while (
index >= literal_to_hash_.
size()) {
592 literal_to_hash_.
push_back(absl::Uniform<uint64_t>(random_));
597 if (c >= clause_to_hash_.size()) clause_to_hash_.resize(c + 1, 0);
598 clause_to_hash_[c] =
hash;
602 absl::Span<const int> literals) {
604 for (
const int ref : literals) {
606 while (
index >= literal_to_hash_.
size()) {
608 literal_to_hash_.
push_back(absl::Uniform<uint64_t>(random_));
void resize(size_type new_size)
void push_back(const value_type &x)
We call domain any subset of Int64 = [kint64min, kint64max].
static Domain AllValues()
Returns the full domain Int64.
bool IsIncludedIn(const Domain &domain) const
Returns true iff D is included in the given domain.
Domain AdditionWith(const Domain &domain) const
Returns {x ∈ Int64, ∃ a ∈ D, ∃ b ∈ domain, x = a + b}.
Domain MultiplicationBy(int64_t coeff, bool *exact=nullptr) const
Returns {x ∈ Int64, ∃ e ∈ D, x = e * coeff}.
Domain IntersectionWith(const Domain &domain) const
Returns the intersection of D and domain.
void Set(IntegerType index)
void Resize(IntegerType size)
std::vector< absl::Span< const int > > PartitionLiteralsIntoAmo(absl::Span< const int > literals)
bool IsAmo(absl::Span< const int > literals)
bool PresolveEnforcement(absl::Span< const int > refs, ConstraintProto *ct, absl::flat_hash_set< int > *literals_at_true)
void AddAtMostOne(absl::Span< const int > amo)
void AddAllAtMostOnes(const CpModelProto &proto)
uint64_t HashOfNegatedLiterals(absl::Span< const int > literals)
void RegisterClause(int c, absl::Span< const int > clause)
std::vector< std::pair< int, Domain > > ProcessClause(absl::Span< const int > clause)
Domain ImpliedDomain(int literal_ref, int var) const
void AddDeduction(int literal_ref, int var, Domain domain)
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
bool RefIsPositive(int ref)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
bool SubstituteVariable(int var, int64_t var_coeff_in_definition, const ConstraintProto &definition, ConstraintProto *ct)
Collection of objects used to extend the Constraint Solver library.