28#include "absl/container/btree_set.h"
29#include "absl/container/flat_hash_map.h"
30#include "absl/container/flat_hash_set.h"
31#include "absl/log/check.h"
32#include "absl/meta/type_traits.h"
33#include "absl/strings/str_cat.h"
34#include "absl/strings/string_view.h"
35#include "absl/types/span.h"
59 num_vars_with_negation_ = 2 * num_variables;
61 std::make_unique<SimpleDynamicPartition>(num_vars_with_negation_);
63 can_freely_decrease_.assign(num_vars_with_negation_,
true);
65 shared_buffer_.clear();
66 has_initial_candidates_.assign(num_vars_with_negation_,
false);
67 initial_candidates_.assign(num_vars_with_negation_, IntegerVariableSpan());
70 dominating_vars_.assign(num_vars_with_negation_, IntegerVariableSpan());
72 ct_index_for_signature_ = 0;
73 block_down_signatures_.assign(num_vars_with_negation_, 0);
76void VarDomination::RefinePartition(std::vector<int>* vars) {
77 if (vars->empty())
return;
78 partition_->Refine(*vars);
79 for (
int& var : *vars) {
80 const IntegerVariable wrapped(var);
81 can_freely_decrease_[wrapped] =
false;
82 can_freely_decrease_[
NegationOf(wrapped)] =
false;
85 partition_->Refine(*vars);
89 if (phase_ != 0)
return;
91 for (
const int ref : refs) {
94 RefinePartition(&tmp_vars_);
99 absl::Span<const int64_t> coeffs) {
100 if (phase_ != 0)
return;
101 FillTempRanks(
false, {}, refs,
104 for (
int i = 0;
i < tmp_ranks_.size(); ++
i) {
105 if (
i > 0 && tmp_ranks_[
i].rank != tmp_ranks_[
i - 1].rank) {
106 RefinePartition(&tmp_vars_);
109 tmp_vars_.push_back(tmp_ranks_[
i].var.value());
111 RefinePartition(&tmp_vars_);
116void VarDomination::ProcessTempRanks() {
120 ++ct_index_for_signature_;
121 for (IntegerVariableWithRank& entry : tmp_ranks_) {
122 can_freely_decrease_[entry.var] =
false;
123 block_down_signatures_[entry.var] |= uint64_t{1}
124 << (ct_index_for_signature_ % 64);
125 entry.part = partition_->PartOf(entry.var.value());
128 tmp_ranks_.begin(), tmp_ranks_.end(),
129 [](
const IntegerVariableWithRank& a,
const IntegerVariableWithRank&
b) {
130 return a.part < b.part;
133 for (
int i = 1;
i < tmp_ranks_.size(); ++
i) {
134 if (tmp_ranks_[
i].part != tmp_ranks_[start].part) {
135 Initialize({&tmp_ranks_[start],
static_cast<size_t>(
i - start)});
139 if (start < tmp_ranks_.size()) {
140 Initialize({&tmp_ranks_[start], tmp_ranks_.size() - start});
142 }
else if (phase_ == 1) {
143 FilterUsingTempRanks();
146 CheckUsingTempRanks();
151 absl::Span<const int> enforcements, absl::Span<const int> refs,
152 absl::Span<const int64_t> coeffs) {
153 FillTempRanks(
false, enforcements, refs, coeffs);
158 absl::Span<const int> enforcements, absl::Span<const int> refs,
159 absl::Span<const int64_t> coeffs) {
160 FillTempRanks(
true, enforcements, refs, coeffs);
164void VarDomination::MakeRankEqualToStartOfPart(
165 absl::Span<IntegerVariableWithRank> span) {
166 const int size = span.size();
168 int previous_value = 0;
169 for (
int i = 0;
i < size; ++
i) {
170 const int64_t value = span[
i].rank;
171 if (value != previous_value) {
172 previous_value = value;
175 span[
i].rank = start;
179void VarDomination::Initialize(absl::Span<IntegerVariableWithRank> span) {
182 MakeRankEqualToStartOfPart(span);
186 if (span.empty())
return;
187 const int part = partition_->PartOf(span[0].var.value());
188 for (
int i = 1;
i < span.size(); ++
i) {
189 CHECK_EQ(part, partition_->PartOf(span[
i].var.value()));
193 const int future_start = shared_buffer_.size();
194 int first_start = -1;
195 const int size = span.size();
196 for (
int i = 0;
i < size; ++
i) {
197 const IntegerVariableWithRank entry = span[
i];
198 const int num_candidates = size - entry.rank;
201 if (has_initial_candidates_[entry.var]) {
202 if (num_candidates >= initial_candidates_[entry.var].size)
continue;
205 if (first_start == -1) first_start = entry.rank;
206 has_initial_candidates_[entry.var] =
true;
207 initial_candidates_[entry.var] = {
208 future_start - first_start +
static_cast<int>(entry.rank),
214 if (first_start == -1)
return;
215 for (
int i = first_start;
i < size; ++
i) {
216 shared_buffer_.push_back(span[
i].var);
232 const int kMaxInitialSize = 50;
233 absl::btree_set<IntegerVariable> cropped_vars;
235 num_vars_with_negation_,
false);
238 int non_cropped_size = 0;
239 std::vector<IntegerVariable> partition_data;
240 const std::vector<absl::Span<const IntegerVariable>> elements_by_part =
241 partition_->GetParts(&partition_data);
242 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
243 if (can_freely_decrease_[var])
continue;
245 const int part = partition_->PartOf(var.value());
246 const int start = buffer_.size();
247 const uint64_t var_sig = block_down_signatures_[var];
248 const uint64_t not_var_sig = block_down_signatures_[
NegationOf(var)];
249 absl::Span<const IntegerVariable> to_scan =
250 has_initial_candidates_[var] ? InitialDominatingCandidates(var)
251 : elements_by_part[part];
257 if (to_scan.size() <= 1'000) {
258 for (
const IntegerVariable x : to_scan) {
259 if (var_sig & ~block_down_signatures_[x])
continue;
260 if (block_down_signatures_[
NegationOf(x)] & ~not_var_sig)
continue;
262 if (can_freely_decrease_[
NegationOf(x)])
continue;
264 buffer_.push_back(x);
265 if (new_size >= kMaxInitialSize) {
266 is_cropped[var] =
true;
271 is_cropped[var] =
true;
273 for (
int i = 0;
i < 200; ++
i) {
274 const IntegerVariable x = to_scan[
i];
275 if (var_sig & ~block_down_signatures_[x])
continue;
276 if (block_down_signatures_[
NegationOf(x)] & ~not_var_sig)
continue;
278 if (can_freely_decrease_[
NegationOf(x)])
continue;
280 buffer_.push_back(x);
281 if (new_size >= kMaxInitialSize)
break;
285 if (!is_cropped[var]) non_cropped_size += new_size;
286 dominating_vars_[var] = {start, new_size};
296 int total_extra_space = 0;
298 num_vars_with_negation_, 0);
299 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
309 other_buffer_.resize(buffer_.size() + total_extra_space);
310 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
311 IntegerVariableSpan& s = dominating_vars_[var];
312 for (
int i = 0;
i < s.size; ++
i) {
313 other_buffer_[copy_index +
i] = buffer_[s.start +
i];
315 s.start = copy_index;
316 copy_index += s.size + extra_space[var];
317 extra_space[var] = s.size;
319 DCHECK_EQ(copy_index, other_buffer_.size());
320 std::swap(buffer_, other_buffer_);
325 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
326 const int start = dominating_vars_[var].start;
327 const int size = extra_space[var];
328 for (
int i = 0;
i < size; ++
i) {
329 const IntegerVariable dom = buffer_[start +
i];
331 IntegerVariableSpan& s = dominating_vars_[
NegationOf(dom)];
332 buffer_[s.start + s.size++] =
NegationOf(var);
340 for (
const IntegerVariable var : cropped_vars) {
341 DCHECK(is_cropped[var]);
342 IntegerVariableSpan& s = dominating_vars_[var];
343 if (s.size == 0)
continue;
344 IntegerVariable* pt = &buffer_[s.start];
345 std::sort(pt, pt + s.size);
346 const auto end = std::unique(pt, pt + s.size);
351 VLOG(1) <<
"Num initial list that where cropped: "
353 VLOG(1) <<
"Shared buffer size: " <<
FormatCounter(shared_buffer_.size());
354 VLOG(1) <<
"Non-cropped buffer size: " <<
FormatCounter(non_cropped_size);
361 return !buffer_.empty();
369 shared_buffer_.clear();
370 initial_candidates_.assign(num_vars_with_negation_, IntegerVariableSpan());
373 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
381 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
382 initial_candidates_[var].start = start;
383 start += initial_candidates_[var].size;
384 initial_candidates_[var].size = 0;
386 shared_buffer_.resize(start);
389 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
391 IntegerVariableSpan& span = initial_candidates_[
NegationOf(dom)];
392 shared_buffer_[span.start + span.size++] =
NegationOf(var);
398 tmp_var_to_rank_.resize(num_vars_with_negation_, -1);
399 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
400 for (
const IntegerVariable dom : InitialDominatingCandidates(var)) {
401 tmp_var_to_rank_[dom] = 1;
405 IntegerVariableSpan& span = dominating_vars_[var];
407 if (tmp_var_to_rank_[dom] != 1) {
411 buffer_[span.start + new_size++] = dom;
413 span.size = new_size;
415 for (
const IntegerVariable dom : InitialDominatingCandidates(var)) {
416 tmp_var_to_rank_[dom] = -1;
420 VLOG(1) <<
"Transpose removed " <<
FormatCounter(num_removed);
425void VarDomination::FillTempRanks(
bool reverse_references,
426 absl::Span<const int> enforcements,
427 absl::Span<const int> refs,
428 absl::Span<const int64_t> coeffs) {
430 if (coeffs.empty()) {
432 for (
const int ref : refs) {
433 const IntegerVariable var =
435 tmp_ranks_.push_back({var, 0, 0});
439 for (
int i = 0;
i < refs.size(); ++
i) {
440 if (coeffs[
i] == 0)
continue;
444 tmp_ranks_.push_back({var, 0, coeffs[
i]});
446 tmp_ranks_.push_back({
NegationOf(var), 0, -coeffs[
i]});
449 std::sort(tmp_ranks_.begin(), tmp_ranks_.end());
450 MakeRankEqualToStartOfPart({&tmp_ranks_[0], tmp_ranks_.size()});
456 const int enforcement_rank = tmp_ranks_.size();
457 for (
const int ref : enforcements) {
458 tmp_ranks_.push_back(
465void VarDomination::FilterUsingTempRanks() {
467 tmp_var_to_rank_.resize(num_vars_with_negation_, -1);
468 for (
const IntegerVariableWithRank entry : tmp_ranks_) {
469 tmp_var_to_rank_[entry.var] = entry.rank;
473 for (
const IntegerVariableWithRank entry : tmp_ranks_) {
481 IntegerVariableSpan& span = dominating_vars_[entry.var];
482 if (span.size == 0)
continue;
485 if (tmp_var_to_rank_[candidate] < entry.rank)
continue;
486 buffer_[span.start + new_size++] = candidate;
488 span.size = new_size;
493 for (
const IntegerVariableWithRank entry : tmp_ranks_) {
494 tmp_var_to_rank_[entry.var] = -1;
499void VarDomination::CheckUsingTempRanks() {
500 tmp_var_to_rank_.resize(num_vars_with_negation_, -1);
501 for (
const IntegerVariableWithRank entry : tmp_ranks_) {
502 tmp_var_to_rank_[entry.var] = entry.rank;
506 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
507 const int var_rank = tmp_var_to_rank_[var];
508 const int negated_var_rank = tmp_var_to_rank_[
NegationOf(var)];
510 CHECK(!can_freely_decrease_[
NegationOf(dom)]);
514 CHECK_LE(var_rank, tmp_var_to_rank_[dom]);
515 CHECK_LE(tmp_var_to_rank_[
NegationOf(dom)], negated_var_rank);
519 for (
const IntegerVariableWithRank entry : tmp_ranks_) {
520 tmp_var_to_rank_[entry.var] = -1;
529 return can_freely_decrease_[var];
532absl::Span<const IntegerVariable> VarDomination::InitialDominatingCandidates(
533 IntegerVariable var)
const {
534 const IntegerVariableSpan span = initial_candidates_[var];
535 if (span.size == 0)
return absl::Span<const IntegerVariable>();
536 return absl::Span<const IntegerVariable>(&shared_buffer_[span.start],
546 IntegerVariable var)
const {
547 const IntegerVariableSpan span = dominating_vars_[var];
548 if (span.size == 0)
return absl::Span<const IntegerVariable>();
549 return absl::Span<const IntegerVariable>(&buffer_[span.start], span.size);
568 IntegerValue* bounds = can_freely_decrease_until_.data();
569 int* locks = num_locks_.data();
570 int* locking_index = locking_ct_index_.data();
571 for (
const int ref : refs) {
572 const int var = RefToIntegerVariable(ref).value();
575 locking_index[var] = ct_index;
582 IntegerValue* bounds = can_freely_decrease_until_.data();
583 int* locks = num_locks_.data();
584 int* locking_index = locking_ct_index_.data();
585 for (
const int ref : refs) {
586 const int negated_var =
NegationOf(RefToIntegerVariable(ref)).value();
588 locks[negated_var]++;
589 locking_index[negated_var] = ct_index;
596 IntegerValue* bounds = can_freely_decrease_until_.data();
597 int* locks = num_locks_.data();
598 int* locking_index = locking_ct_index_.data();
599 for (
const int ref : refs) {
600 const IntegerVariable var = RefToIntegerVariable(ref);
601 const IntegerVariable minus_var =
NegationOf(var);
604 locks[var.value()]++;
605 locks[minus_var.value()]++;
606 locking_index[var.value()] = ct_index;
607 locking_index[minus_var.value()] = ct_index;
611template <
typename LinearProto>
614 const LinearProto& linear, int64_t min_activity, int64_t max_activity,
616 const int64_t lb_limit = linear.domain(linear.domain_size() - 2);
617 const int64_t ub_limit = linear.domain(1);
618 const int num_terms = linear.vars_size();
619 for (
int i = 0;
i < num_terms; ++
i) {
620 int ref = linear.vars(
i);
621 int64_t coeff = linear.coeffs(
i);
627 const int64_t min_term = coeff * context.
MinOf(ref);
628 const int64_t max_term = coeff * context.
MaxOf(ref);
629 const int64_t term_diff = max_term - min_term;
630 const IntegerVariable var = RefToIntegerVariable(ref);
633 if (min_activity < lb_limit) {
635 locking_ct_index_[var] = ct_index;
636 if (min_activity + term_diff < lb_limit) {
639 const IntegerValue slack(lb_limit - min_activity);
640 const IntegerValue var_diff =
641 CeilRatio(IntegerValue(slack), IntegerValue(coeff));
642 can_freely_decrease_until_[var] =
643 std::max(can_freely_decrease_until_[var],
644 IntegerValue(context.
MinOf(ref)) + var_diff);
657 if (max_activity > ub_limit) {
659 locking_ct_index_[
NegationOf(var)] = ct_index;
660 if (max_activity - term_diff > ub_limit) {
663 const IntegerValue slack(max_activity - ub_limit);
664 const IntegerValue var_diff =
665 CeilRatio(IntegerValue(slack), IntegerValue(coeff));
667 std::max(can_freely_decrease_until_[
NegationOf(var)],
668 -IntegerValue(context.
MaxOf(ref)) + var_diff);
679void TransformLinearWithSpecialBoolean(
const ConstraintProto& ct,
int ref,
680 std::vector<int64_t>* output) {
690 output->push_back(std::numeric_limits<int32_t>::max());
692 output->push_back(literal);
701 for (
int i = 0;
i < ct.
linear().vars().size(); ++
i) {
705 output->push_back(std::numeric_limits<int32_t>::max());
706 output->push_back(c);
709 output->push_back(std::numeric_limits<int32_t>::max());
710 output->push_back(-c);
713 output->push_back(v);
714 output->push_back(c);
720 output->push_back(
CapSub(value, offset));
728 num_deleted_constraints_ = 0;
731 int64_t num_fixed_vars = 0;
732 for (
int var = 0; var < num_vars; ++var) {
733 if (context->
IsFixed(var))
continue;
736 const int64_t lb = context->
MinOf(var);
738 if (ub_limit == lb) {
745 const int64_t ub = context->
MaxOf(var);
746 const int64_t lb_limit =
748 if (lb_limit == ub) {
756 if (lb_limit > ub_limit) {
760 int64_t value = domain.
Contains(0) ? 0 : domain.
Min();
763 if (std::abs(bound) < std::abs(value)) value = bound;
775 if (lb_limit > lb || ub_limit < ub) {
776 const int64_t new_ub =
780 Domain(ub_limit, std::numeric_limits<int64_t>::max()))
783 const int64_t new_lb =
787 Domain(std::numeric_limits<int64_t>::min(), lb_limit))
794 if (num_fixed_vars > 0) {
800 absl::flat_hash_set<int> equiv_ct_index_set;
801 absl::flat_hash_map<uint64_t, std::pair<int, int>> equiv_modified_constraints;
802 std::vector<int64_t> temp_data;
803 std::vector<int64_t> other_temp_data;
810 std::vector<bool> processed(num_vars,
false);
811 int64_t num_bool_in_near_duplicate_ct = 0;
812 for (IntegerVariable var(0); var < num_locks_.size(); ++var) {
815 if (processed[positive_ref])
continue;
816 if (context->
IsFixed(positive_ref))
continue;
820 if (num_locks_[var] != 1)
continue;
821 if (locking_ct_index_[var] == -1) {
823 "TODO dual: only one unspecified blocking constraint?");
827 const int ct_index = locking_ct_index_[var];
849 : context->
MaxOf(positive_ref);
877 processed[positive_ref] =
true;
888 positive_ref, enf, implied.
FixedValue() - bound, bound)) {
907 processed[positive_ref] =
true;
945 if (rhs == var_domain) {
957 const int64_t value =
961 bool has_encoding =
false;
974 if (encoding_lit ==
NegatedRef(ref))
continue;
985 if (encoding_lit == ref)
continue;
1002 if (
PositiveRef(encoding_lit) < processed.size()) {
1016 ref,
false, {{var, 1}}, complement);
1027 }
else if (complement.
IsFixed()) {
1055 if (equiv_ct_index_set.insert(ct_index).second &&
1058 TransformLinearWithSpecialBoolean(ct, ref, &temp_data);
1059 const uint64_t hash =
1060 fasthash64(temp_data.data(), temp_data.size() *
sizeof(int64_t),
1061 uint64_t{0xa5b85c5e198ed849});
1062 const auto [it, inserted] =
1063 equiv_modified_constraints.insert({hash, {ct_index, ref}});
1066 const auto [other_c_with_same_hash, other_ref] = it->second;
1067 CHECK_NE(other_c_with_same_hash, ct_index);
1068 const auto& other_ct =
1070 TransformLinearWithSpecialBoolean(other_ct, other_ref,
1072 if (temp_data == other_temp_data) {
1079 "dual: detected l => ct and not(l) => ct with unused l!");
1088 ++num_bool_in_near_duplicate_ct;
1103 ++num_deleted_constraints_;
1121 "TODO dual: only one blocking enforced constraint?");
1146 num_locks_[RefToIntegerVariable(
NegatedRef(a))] == 1) {
1156 num_locks_[RefToIntegerVariable(lhs)] == 1) {
1164 CHECK_EQ(num_locks_[RefToIntegerVariable(
NegatedRef(a))], 1);
1177 if (num_bool_in_near_duplicate_ct) {
1179 "dual: equivalent Boolean in near-duplicate constraints",
1180 num_bool_in_near_duplicate_ct);
1183 VLOG(2) <<
"Num deleted constraints: " << num_deleted_constraints_;
1192 const int num_vars = cp_model.
variables().size();
1193 var_domination->
Reset(num_vars);
1195 for (
int var = 0; var < num_vars; ++var) {
1210 }
else if (r.
coeff == -1) {
1221 std::vector<bool> c_is_free_to_increase(num_constraints);
1222 std::vector<bool> c_is_free_to_decrease(num_constraints);
1223 for (
int c = 0; c < num_constraints; ++c) {
1238 const auto [min_activity, max_activity] =
1240 const bool domain_is_simple = ct.
linear().
domain().size() == 2;
1241 const bool free_to_increase =
1242 domain_is_simple && ct.
linear().
domain(1) >= max_activity;
1243 const bool free_to_decrease =
1244 domain_is_simple && ct.
linear().
domain(0) <= min_activity;
1245 c_is_free_to_increase[c] = free_to_increase;
1246 c_is_free_to_decrease[c] = free_to_decrease;
1247 if (free_to_decrease && free_to_increase)
break;
1248 if (!free_to_increase && !free_to_decrease) {
1270 const auto [min_activity, max_activity] =
1273 if (domain.empty() || (domain.size() == 2 && domain[0] <= min_activity)) {
1284 std::vector<int> tmp;
1285 for (
int phase = 0; phase < 2; phase++) {
1286 for (
int c = 0; c < num_constraints; ++c) {
1320 const bool free_to_increase = c_is_free_to_increase[c];
1321 const bool free_to_decrease = c_is_free_to_decrease[c];
1322 if (free_to_decrease && free_to_increase)
break;
1323 if (free_to_increase) {
1327 }
else if (free_to_decrease) {
1347 const auto [min_activity, max_activity] =
1350 if (domain.empty() || (domain.size() == 2 && domain[0] <= min_activity)) {
1370 int64_t num_unconstrained_refs = 0;
1371 int64_t num_dominated_refs = 0;
1372 int64_t num_dominance_relations = 0;
1373 for (
int var = 0; var < num_vars; ++var) {
1374 if (context.
IsFixed(var))
continue;
1376 for (
const int ref : {var,
NegatedRef(var)}) {
1378 num_unconstrained_refs++;
1380 num_dominated_refs++;
1381 num_dominance_relations +=
1386 if (num_unconstrained_refs == 0 && num_dominated_refs == 0)
return;
1387 VLOG(1) <<
"Dominance:"
1388 <<
" num_unconstrained_refs=" << num_unconstrained_refs
1389 <<
" num_dominated_refs=" << num_dominated_refs
1390 <<
" num_dominance_relations=" << num_dominance_relations;
1398 const int num_vars = cp_model.
variables().size();
1399 dual_bound_strengthening->
Reset(num_vars);
1401 for (
int var = 0; var < num_vars; ++var) {
1418 for (
int c = 0; c < num_constraints; ++c) {
1437 const auto [min_activity, max_activity] =
1440 false, context, ct.
linear(), min_activity, max_activity, c);
1457 const auto [min_activity, max_activity] =
1460 true, context, cp_model.
objective(), min_activity, max_activity);
1465Domain DomainOfRef(
const PresolveContext& context,
int ref) {
1477void MaybeUpdateRefHintFromDominance(
1478 PresolveContext& context,
int ref,
const Domain& domain,
1479 absl::Span<const IntegerVariable> dominating_variables) {
1480 DCHECK_EQ(domain.NumIntervals(), 1);
1481 DCHECK_EQ(domain.
Min(), DomainOfRef(context, ref).Min());
1482 DCHECK(DomainOfRef(context, ref).Contains(domain.
Max()));
1483 std::vector<std::pair<int, Domain>> dominating_refs;
1484 dominating_refs.reserve(dominating_variables.size());
1485 for (
const IntegerVariable var : dominating_variables) {
1487 dominating_refs.push_back(
1488 {dominating_ref, DomainOfRef(context, dominating_ref)});
1490 context.solution_crush().UpdateRefsWithDominance(
1491 ref, domain.
Min(), domain.
Max(), dominating_refs);
1494bool ProcessAtMostOne(
1495 absl::Span<const int> literals, absl::string_view message,
1497 util_intops::StrongVector<IntegerVariable, bool>* in_constraints,
1499 for (
const int ref : literals) {
1502 for (
const int ref : literals) {
1503 if (context->IsFixed(ref))
continue;
1505 const auto dominating_ivars = var_domination.DominatingVariables(ref);
1506 for (
const IntegerVariable ivar : dominating_ivars) {
1508 if (!(*in_constraints)[ivar])
continue;
1509 if (context->IsFixed(iref)) {
1517 context->UpdateRuleStats(message);
1518 context->solution_crush().UpdateLiteralsWithDominance(ref, iref);
1519 if (!context->SetLiteralToFalse(ref))
return false;
1523 for (
const int ref : literals) {
1537 bool work_to_do =
false;
1538 for (
int var = 0; var < num_vars; ++var) {
1539 if (context->
IsFixed(var))
continue;
1546 if (!work_to_do)
return true;
1557 num_vars * 2, std::numeric_limits<int64_t>::min());
1568 absl::flat_hash_set<std::pair<int, int>> implications;
1570 for (
int c = 0; c < num_constraints; ++c) {
1576 if (context->
IsFixed(a))
continue;
1579 implications.insert({a,
b});
1586 for (
const IntegerVariable ivar :
1596 if (context->
IsFixed(a))
break;
1602 for (
const IntegerVariable ivar :
1622 const auto add_implications = [&implications](absl::Span<const int> lits) {
1623 if (lits.size() > 3)
return;
1624 for (
int i = 0;
i < lits.size(); ++
i) {
1625 for (
int j = 0; j < lits.size(); ++j) {
1626 if (
i == j)
continue;
1627 implications.insert({lits[
i],
NegatedRef(lits[j])});
1634 "domination: in at most one", var_domination,
1635 &in_constraints, context)) {
1641 "domination: in exactly one", var_domination,
1642 &in_constraints, context)) {
1649 int num_dominated = 0;
1656 if (num_dominated == 0)
continue;
1659 int64_t min_activity = 0;
1660 int64_t max_activity = 0;
1662 for (
int i = 0;
i < num_terms; ++
i) {
1669 const int64_t min_term = coeff * context->
MinOf(ref);
1670 const int64_t max_term = coeff * context->
MaxOf(ref);
1671 min_activity += min_term;
1672 max_activity += max_term;
1674 var_lb_to_ub_diff[ivar] = max_term - min_term;
1675 var_lb_to_ub_diff[
NegationOf(ivar)] = min_term - max_term;
1679 if (max_activity < rhs_lb || min_activity > rhs_ub) {
1685 const auto get_delta = [context, &var_lb_to_ub_diff](
1687 absl::Span<const IntegerVariable> vars) {
1689 for (
const IntegerVariable var : vars) {
1693 .NumIntervals() != 1) {
1697 delta += std::max(int64_t{0}, var_lb_to_ub_diff[var]);
1699 delta += std::max(int64_t{0}, -var_lb_to_ub_diff[var]);
1706 for (
int i = 0;
i < num_terms; ++
i) {
1709 const int64_t coeff_magnitude = std::abs(coeff);
1710 if (context->
IsFixed(ref))
continue;
1713 const bool only_lb = max_activity <= rhs_ub;
1714 const bool only_ub = min_activity >= rhs_lb;
1715 if (only_lb || only_ub) {
1717 const int current_ref = (coeff > 0) == only_lb ? ref :
NegatedRef(ref);
1718 const int64_t shifted_rhs =
1719 only_lb ? rhs_lb - min_activity : max_activity - rhs_ub;
1720 const IntegerVariable current_ivar =
1722 can_freely_decrease_count[
NegationOf(current_ivar)]++;
1724 const int64_t delta = get_delta(
1729 const int64_t slack = shifted_rhs - delta;
1730 const int64_t current_lb = context->
MinOf(current_ref);
1740 const int64_t min_delta =
1742 can_freely_decrease_until[current_ivar] = std::max(
1743 can_freely_decrease_until[current_ivar], current_lb + min_delta);
1744 can_freely_decrease_count[current_ivar]++;
1748 for (
const int current_ref : {ref,
NegatedRef(ref)}) {
1749 const absl::Span<const IntegerVariable> dominated_by =
1751 if (dominated_by.empty())
continue;
1753 const bool ub_side = (coeff > 0) == (current_ref == ref);
1755 if (max_activity <= rhs_ub)
continue;
1757 if (min_activity >= rhs_lb)
continue;
1759 const int64_t slack =
1760 ub_side ? rhs_ub - min_activity : max_activity - rhs_lb;
1764 const int64_t delta = get_delta(ub_side, dominated_by);
1765 const int64_t lb = context->
MinOf(current_ref);
1766 if (delta + coeff_magnitude > slack) {
1769 MaybeUpdateRefHintFromDominance(*context, current_ref, reduced_domain,
1776 const IntegerVariable current_var =
1779 CHECK_GE(var_lb_to_ub_diff[current_var], 0);
1780 max_activity -= var_lb_to_ub_diff[current_var];
1782 CHECK_LE(var_lb_to_ub_diff[current_var], 0);
1783 min_activity -= var_lb_to_ub_diff[current_var];
1785 var_lb_to_ub_diff[current_var] = 0;
1786 var_lb_to_ub_diff[
NegationOf(current_var)] = 0;
1791 const IntegerValue diff =
FloorRatio(IntegerValue(slack - delta),
1792 IntegerValue(coeff_magnitude));
1793 int64_t new_ub = lb + diff.value();
1794 if (new_ub < context->MaxOf(current_ref)) {
1798 new_ub = DomainOfRef(*context, current_ref).ValueAtOrAfter(new_ub);
1800 if (new_ub < context->MaxOf(current_ref)) {
1803 MaybeUpdateRefHintFromDominance(*context, current_ref, reduced_domain,
1810 const IntegerVariable current_var =
1813 CHECK_GE(var_lb_to_ub_diff[current_var], 0);
1814 max_activity -= var_lb_to_ub_diff[current_var];
1816 CHECK_LE(var_lb_to_ub_diff[current_var], 0);
1817 min_activity -= var_lb_to_ub_diff[current_var];
1819 const int64_t new_diff = std::abs(coeff_magnitude * (new_ub - lb));
1821 var_lb_to_ub_diff[current_var] = new_diff;
1822 var_lb_to_ub_diff[
NegationOf(current_var)] = -new_diff;
1823 max_activity += new_diff;
1825 var_lb_to_ub_diff[current_var] = -new_diff;
1826 var_lb_to_ub_diff[
NegationOf(current_var)] = +new_diff;
1827 min_activity -= new_diff;
1836 var_lb_to_ub_diff[ivar] = 0;
1857 2 * num_vars,
false);
1858 for (
int positive_ref = 0; positive_ref < num_vars; ++positive_ref) {
1859 if (context->
IsFixed(positive_ref))
continue;
1867 if (obj_coeff > 0) {
1870 }
else if (obj_coeff < 0) {
1876 for (
const int ref : {positive_ref,
NegatedRef(positive_ref)}) {
1878 if (increase_is_forbidden[
NegationOf(var)])
continue;
1879 if (can_freely_decrease_count[var] ==
1882 int64_t lb = can_freely_decrease_until[var];
1883 lb = DomainOfRef(*context, ref).ValueAtOrAfter(lb);
1884 if (lb < context->MaxOf(ref)) {
1891 const absl::Span<const IntegerVariable> dominating_vars =
1893 for (
const IntegerVariable dom : dominating_vars) {
1899 if (increase_is_forbidden[dom] ||
1906 if (increase_is_forbidden[
NegationOf(var)]) {
1911 increase_is_forbidden[var] =
true;
1913 "domination: dual strenghtening using dominance");
1915 MaybeUpdateRefHintFromDominance(*context, ref, reduced_domain,
1931 for (
const IntegerVariable dom :
1933 if (increase_is_forbidden[dom])
continue;
1935 if (context->
IsFixed(dom_ref))
continue;
1939 if (implications.contains({ref, dom_ref}))
continue;
1949 implications.insert({ref, dom_ref});
1953 increase_is_forbidden[var] =
true;
1954 increase_is_forbidden[
NegationOf(dom)] =
true;
1959 if (num_added > 0) {
1960 VLOG(1) <<
"Added " << num_added <<
" domination implications.";
1961 context->
UpdateRuleStats(
"domination: added implications", num_added);
std::vector< int64_t > FlattenedIntervals() const
Domain IntersectionWith(const Domain &domain) const
bool Contains(int64_t value) const
int64_t FixedValue() const
Domain Complement() const
Domain InverseMultiplicationBy(int64_t coeff) const
static IntegralType CeilOfRatio(IntegralType numerator, IntegralType denominator)
::int32_t literals(int index) const
ConstraintCase constraint_case() const
::int32_t enforcement_literal(int index) const
const ::operations_research::sat::BoolArgumentProto & exactly_one() const
const ::operations_research::sat::LinearConstraintProto & linear() const
const ::operations_research::sat::BoolArgumentProto & at_most_one() const
const ::operations_research::sat::BoolArgumentProto & bool_and() const
void add_enforcement_literal(::int32_t value)
ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL
const ::operations_research::sat::BoolArgumentProto & bool_or() const
::operations_research::sat::LinearConstraintProto *PROTOBUF_NONNULL mutable_linear()
::operations_research::sat::ConstraintProto *PROTOBUF_NONNULL mutable_constraints(int index)
const ::operations_research::sat::IntegerVariableProto & variables(int index) const
const ::operations_research::sat::ConstraintProto & constraints(int index) const
bool has_objective() const
int constraints_size() const
::operations_research::sat::ConstraintProto *PROTOBUF_NONNULL add_constraints()
int variables_size() const
const ::operations_research::sat::CpObjectiveProto & objective() const
::int64_t domain(int index) const
::int32_t vars(int index) const
::int64_t coeffs(int index) const
Domain ImpliedDomain(int literal_ref, int var) const
bool Strengthen(PresolveContext *context)
void Reset(int num_variables)
int64_t CanFreelyDecreaseUntil(int ref) const
void CannotIncrease(absl::Span< const int > refs, int ct_index=-1)
void CannotDecrease(absl::Span< const int > refs, int ct_index=-1)
void CannotMove(absl::Span< const int > refs, int ct_index=-1)
void ProcessLinearConstraint(bool is_objective, const PresolveContext &context, const LinearProto &linear, int64_t min_activity, int64_t max_activity, int ct_index=-1)
::int32_t vars(int index) const
void add_vars(::int32_t value)
void add_coeffs(::int64_t value)
::int64_t coeffs(int index) const
::int64_t domain(int index) const
DomainDeductions deductions
bool ObjectiveDomainIsConstraining() const
ABSL_MUST_USE_RESULT bool IntersectDomainWith(int ref, const Domain &domain, bool *domain_modified=nullptr)
CpModelProto * working_model
bool StoreLiteralImpliesVarEqValue(int literal, int var, int64_t value)
bool StoreAffineRelation(int var_x, int var_y, int64_t coeff, int64_t offset, bool debug_no_recursion=false)
SparseBitset< int > modified_domains
ABSL_MUST_USE_RESULT bool SetLiteralToTrue(int lit)
void AddImplication(int a, int b)
ABSL_MUST_USE_RESULT bool SetLiteralToFalse(int lit)
absl::Span< const int > ConstraintToVars(int c) const
int64_t ObjectiveCoeff(int var) const
ABSL_MUST_USE_RESULT bool NotifyThatModelIsUnsat(absl::string_view message="")
int64_t num_presolve_operations
void WriteObjectiveToProto() const
bool IsFixed(int ref) const
bool StoreBooleanEqualityRelation(int ref_a, int ref_b)
std::pair< int64_t, int64_t > ComputeMinMaxActivity(const ProtoWithVarsAndCoeffs &proto) const
bool HasVarValueEncoding(int ref, int64_t value, int *literal=nullptr)
bool VariableIsNotUsedAnymore(int ref) const
bool VariableWasRemoved(int ref) const
void UpdateConstraintVariableUsage(int c)
AffineRelation::Relation GetAffineRelation(int ref) const
const absl::flat_hash_set< int > & VarToConstraints(int var) const
void UpdateNewConstraintsVariableUsage()
bool CanBeUsedAsLiteral(int ref) const
int64_t MinOf(int ref) const
int64_t MaxOf(int ref) const
bool VarCanTakeValue(int var, int64_t value) const
const Domain & DomainOf(int var) const
bool StoreLiteralImpliesVarNeValue(int literal, int var, int64_t value)
void UpdateRuleStats(std::string_view name, int num_times=1)
bool ModelIsUnsat() const
SolutionCrush & solution_crush()
void SetVarToValueIf(int var, int64_t value, int condition_lit)
void MakeLiteralsEqual(int lit1, int lit2)
void UpdateLiteralsWithDominance(int lit, int dominating_lit)
void SetLiteralToValueIfLinearConstraintViolated(int literal, bool value, absl::Span< const std::pair< int, int64_t > > linear, const Domain &domain)
void UpdateLiteralsToFalseIfDifferent(int lit1, int lit2)
void SetLiteralToValueIf(int literal, bool value, int condition_lit)
static int IntegerVariableToRef(IntegerVariable var)
absl::Span< const IntegerVariable > DominatingVariables(int ref) const
void Reset(int num_variables)
static IntegerVariable RefToIntegerVariable(int ref)
std::string DominationDebugString(IntegerVariable var) const
void CanOnlyDominateEachOther(absl::Span< const int > refs)
bool CanFreelyDecrease(int ref) const
void ActivityShouldNotIncrease(absl::Span< const int > enforcements, absl::Span< const int > refs, absl::Span< const int64_t > coeffs)
void ActivityShouldNotChange(absl::Span< const int > refs, absl::Span< const int64_t > coeffs)
void ActivityShouldNotDecrease(absl::Span< const int > enforcements, absl::Span< const int > refs, absl::Span< const int64_t > coeffs)
iterator insert(const_iterator pos, const value_type &x)
void STLClearObject(T *obj)
IntegerValue FloorRatio(IntegerValue dividend, IntegerValue positive_divisor)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
bool RefIsPositive(int ref)
bool ExploitDominanceRelations(const VarDomination &var_domination, PresolveContext *context)
IntegerValue CeilRatio(IntegerValue dividend, IntegerValue positive_divisor)
std::vector< IntegerVariable > NegationOf(absl::Span< const IntegerVariable > vars)
void ScanModelForDualBoundStrengthening(const PresolveContext &context, DualBoundStrengthening *dual_bound_strengthening)
IntegerVariable PositiveVariable(IntegerVariable i)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
void ScanModelForDominanceDetection(PresolveContext &context, VarDomination *var_domination)
int64_t CapSub(int64_t x, int64_t y)
ClosedInterval::Iterator end(ClosedInterval interval)
uint64_t fasthash64(const void *buf, size_t len, uint64_t seed)
std::string FormatCounter(int64_t num)