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/types/span.h"
41#include "ortools/sat/cp_model.pb.h"
58 num_vars_with_negation_ = 2 * num_variables;
60 std::make_unique<SimpleDynamicPartition>(num_vars_with_negation_);
62 can_freely_decrease_.assign(num_vars_with_negation_,
true);
64 shared_buffer_.clear();
65 has_initial_candidates_.assign(num_vars_with_negation_,
false);
66 initial_candidates_.assign(num_vars_with_negation_, IntegerVariableSpan());
69 dominating_vars_.assign(num_vars_with_negation_, IntegerVariableSpan());
71 ct_index_for_signature_ = 0;
72 block_down_signatures_.assign(num_vars_with_negation_, 0);
75void VarDomination::RefinePartition(std::vector<int>* vars) {
76 if (vars->empty())
return;
77 partition_->Refine(*vars);
78 for (
int& var : *vars) {
79 const IntegerVariable wrapped(var);
80 can_freely_decrease_[wrapped] =
false;
81 can_freely_decrease_[
NegationOf(wrapped)] =
false;
84 partition_->Refine(*vars);
88 if (phase_ != 0)
return;
90 for (
const int ref : refs) {
93 RefinePartition(&tmp_vars_);
98 absl::Span<const int64_t> coeffs) {
99 if (phase_ != 0)
return;
100 FillTempRanks(
false, {}, refs,
103 for (
int i = 0;
i < tmp_ranks_.size(); ++
i) {
104 if (
i > 0 && tmp_ranks_[
i].rank != tmp_ranks_[
i - 1].rank) {
105 RefinePartition(&tmp_vars_);
108 tmp_vars_.push_back(tmp_ranks_[
i].var.value());
110 RefinePartition(&tmp_vars_);
115void VarDomination::ProcessTempRanks() {
119 ++ct_index_for_signature_;
120 for (IntegerVariableWithRank& entry : tmp_ranks_) {
121 can_freely_decrease_[entry.var] =
false;
122 block_down_signatures_[entry.var] |= uint64_t{1}
123 << (ct_index_for_signature_ % 64);
124 entry.part = partition_->PartOf(entry.var.value());
127 tmp_ranks_.begin(), tmp_ranks_.end(),
128 [](
const IntegerVariableWithRank& a,
const IntegerVariableWithRank&
b) {
129 return a.part < b.part;
132 for (
int i = 1;
i < tmp_ranks_.size(); ++
i) {
133 if (tmp_ranks_[
i].part != tmp_ranks_[start].part) {
134 Initialize({&tmp_ranks_[start],
static_cast<size_t>(
i - start)});
138 if (start < tmp_ranks_.size()) {
139 Initialize({&tmp_ranks_[start], tmp_ranks_.size() - start});
141 }
else if (phase_ == 1) {
142 FilterUsingTempRanks();
145 CheckUsingTempRanks();
150 absl::Span<const int> enforcements, absl::Span<const int> refs,
151 absl::Span<const int64_t> coeffs) {
152 FillTempRanks(
false, enforcements, refs, coeffs);
157 absl::Span<const int> enforcements, absl::Span<const int> refs,
158 absl::Span<const int64_t> coeffs) {
159 FillTempRanks(
true, enforcements, refs, coeffs);
163void VarDomination::MakeRankEqualToStartOfPart(
164 absl::Span<IntegerVariableWithRank> span) {
165 const int size = span.size();
167 int previous_value = 0;
168 for (
int i = 0;
i < size; ++
i) {
169 const int64_t value = span[
i].rank;
170 if (value != previous_value) {
171 previous_value = value;
174 span[
i].rank = start;
178void VarDomination::Initialize(absl::Span<IntegerVariableWithRank> span) {
181 MakeRankEqualToStartOfPart(span);
185 if (span.empty())
return;
186 const int part = partition_->PartOf(span[0].var.value());
187 for (
int i = 1;
i < span.size(); ++
i) {
188 CHECK_EQ(part, partition_->PartOf(span[
i].var.value()));
192 const int future_start = shared_buffer_.size();
193 int first_start = -1;
194 const int size = span.size();
195 for (
int i = 0;
i < size; ++
i) {
196 const IntegerVariableWithRank entry = span[
i];
197 const int num_candidates = size - entry.rank;
200 if (has_initial_candidates_[entry.var]) {
201 if (num_candidates >= initial_candidates_[entry.var].size)
continue;
204 if (first_start == -1) first_start = entry.rank;
205 has_initial_candidates_[entry.var] =
true;
206 initial_candidates_[entry.var] = {
207 future_start - first_start +
static_cast<int>(entry.rank),
213 if (first_start == -1)
return;
214 for (
int i = first_start;
i < size; ++
i) {
215 shared_buffer_.push_back(span[
i].var);
231 const int kMaxInitialSize = 50;
232 absl::btree_set<IntegerVariable> cropped_vars;
234 num_vars_with_negation_,
false);
237 int non_cropped_size = 0;
238 std::vector<IntegerVariable> partition_data;
239 const std::vector<absl::Span<const IntegerVariable>> elements_by_part =
240 partition_->GetParts(&partition_data);
241 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
242 if (can_freely_decrease_[var])
continue;
244 const int part = partition_->PartOf(var.value());
245 const int start = buffer_.size();
246 const uint64_t var_sig = block_down_signatures_[var];
247 const uint64_t not_var_sig = block_down_signatures_[
NegationOf(var)];
248 absl::Span<const IntegerVariable> to_scan =
249 has_initial_candidates_[var] ? InitialDominatingCandidates(var)
250 : elements_by_part[part];
256 if (to_scan.size() <= 1'000) {
257 for (
const IntegerVariable x : to_scan) {
258 if (var_sig & ~block_down_signatures_[x])
continue;
259 if (block_down_signatures_[
NegationOf(x)] & ~not_var_sig)
continue;
261 if (can_freely_decrease_[
NegationOf(x)])
continue;
263 buffer_.push_back(x);
264 if (new_size >= kMaxInitialSize) {
265 is_cropped[var] =
true;
270 is_cropped[var] =
true;
272 for (
int i = 0;
i < 200; ++
i) {
273 const IntegerVariable x = to_scan[
i];
274 if (var_sig & ~block_down_signatures_[x])
continue;
275 if (block_down_signatures_[
NegationOf(x)] & ~not_var_sig)
continue;
277 if (can_freely_decrease_[
NegationOf(x)])
continue;
279 buffer_.push_back(x);
280 if (new_size >= kMaxInitialSize)
break;
284 if (!is_cropped[var]) non_cropped_size += new_size;
285 dominating_vars_[var] = {start, new_size};
295 int total_extra_space = 0;
297 num_vars_with_negation_, 0);
298 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
308 other_buffer_.resize(buffer_.size() + total_extra_space);
309 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
310 IntegerVariableSpan& s = dominating_vars_[var];
311 for (
int i = 0;
i < s.size; ++
i) {
312 other_buffer_[copy_index +
i] = buffer_[s.start +
i];
314 s.start = copy_index;
315 copy_index += s.size + extra_space[var];
316 extra_space[var] = s.size;
318 DCHECK_EQ(copy_index, other_buffer_.size());
319 std::swap(buffer_, other_buffer_);
324 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
325 const int start = dominating_vars_[var].start;
326 const int size = extra_space[var];
327 for (
int i = 0;
i < size; ++
i) {
328 const IntegerVariable dom = buffer_[start +
i];
330 IntegerVariableSpan& s = dominating_vars_[
NegationOf(dom)];
331 buffer_[s.start + s.size++] =
NegationOf(var);
339 for (
const IntegerVariable var : cropped_vars) {
340 DCHECK(is_cropped[var]);
341 IntegerVariableSpan& s = dominating_vars_[var];
342 if (s.size == 0)
continue;
343 IntegerVariable* pt = &buffer_[s.start];
344 std::sort(pt, pt + s.size);
345 const auto end = std::unique(pt, pt + s.size);
350 VLOG(1) <<
"Num initial list that where cropped: "
352 VLOG(1) <<
"Shared buffer size: " <<
FormatCounter(shared_buffer_.size());
353 VLOG(1) <<
"Non-cropped buffer size: " <<
FormatCounter(non_cropped_size);
360 return !buffer_.empty();
368 shared_buffer_.clear();
369 initial_candidates_.assign(num_vars_with_negation_, IntegerVariableSpan());
372 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
380 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
381 initial_candidates_[var].start = start;
382 start += initial_candidates_[var].size;
383 initial_candidates_[var].size = 0;
385 shared_buffer_.resize(start);
388 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
390 IntegerVariableSpan& span = initial_candidates_[
NegationOf(dom)];
391 shared_buffer_[span.start + span.size++] =
NegationOf(var);
397 tmp_var_to_rank_.resize(num_vars_with_negation_, -1);
398 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
399 for (
const IntegerVariable dom : InitialDominatingCandidates(var)) {
400 tmp_var_to_rank_[dom] = 1;
404 IntegerVariableSpan& span = dominating_vars_[var];
406 if (tmp_var_to_rank_[dom] != 1) {
410 buffer_[span.start + new_size++] = dom;
412 span.size = new_size;
414 for (
const IntegerVariable dom : InitialDominatingCandidates(var)) {
415 tmp_var_to_rank_[dom] = -1;
419 VLOG(1) <<
"Transpose removed " <<
FormatCounter(num_removed);
424void VarDomination::FillTempRanks(
bool reverse_references,
425 absl::Span<const int> enforcements,
426 absl::Span<const int> refs,
427 absl::Span<const int64_t> coeffs) {
429 if (coeffs.empty()) {
431 for (
const int ref : refs) {
432 const IntegerVariable var =
434 tmp_ranks_.push_back({var, 0, 0});
438 for (
int i = 0;
i < refs.size(); ++
i) {
439 if (coeffs[
i] == 0)
continue;
443 tmp_ranks_.push_back({var, 0, coeffs[
i]});
445 tmp_ranks_.push_back({
NegationOf(var), 0, -coeffs[
i]});
448 std::sort(tmp_ranks_.begin(), tmp_ranks_.end());
449 MakeRankEqualToStartOfPart({&tmp_ranks_[0], tmp_ranks_.size()});
455 const int enforcement_rank = tmp_ranks_.size();
456 for (
const int ref : enforcements) {
457 tmp_ranks_.push_back(
464void VarDomination::FilterUsingTempRanks() {
466 tmp_var_to_rank_.resize(num_vars_with_negation_, -1);
467 for (
const IntegerVariableWithRank entry : tmp_ranks_) {
468 tmp_var_to_rank_[entry.var] = entry.rank;
472 for (
const IntegerVariableWithRank entry : tmp_ranks_) {
480 IntegerVariableSpan& span = dominating_vars_[entry.var];
481 if (span.size == 0)
continue;
484 if (tmp_var_to_rank_[candidate] < entry.rank)
continue;
485 buffer_[span.start + new_size++] = candidate;
487 span.size = new_size;
492 for (
const IntegerVariableWithRank entry : tmp_ranks_) {
493 tmp_var_to_rank_[entry.var] = -1;
498void VarDomination::CheckUsingTempRanks() {
499 tmp_var_to_rank_.resize(num_vars_with_negation_, -1);
500 for (
const IntegerVariableWithRank entry : tmp_ranks_) {
501 tmp_var_to_rank_[entry.var] = entry.rank;
505 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
506 const int var_rank = tmp_var_to_rank_[var];
507 const int negated_var_rank = tmp_var_to_rank_[
NegationOf(var)];
509 CHECK(!can_freely_decrease_[
NegationOf(dom)]);
513 CHECK_LE(var_rank, tmp_var_to_rank_[dom]);
514 CHECK_LE(tmp_var_to_rank_[
NegationOf(dom)], negated_var_rank);
518 for (
const IntegerVariableWithRank entry : tmp_ranks_) {
519 tmp_var_to_rank_[entry.var] = -1;
528 return can_freely_decrease_[var];
531absl::Span<const IntegerVariable> VarDomination::InitialDominatingCandidates(
532 IntegerVariable var)
const {
533 const IntegerVariableSpan span = initial_candidates_[var];
534 if (span.size == 0)
return absl::Span<const IntegerVariable>();
535 return absl::Span<const IntegerVariable>(&shared_buffer_[span.start],
545 IntegerVariable var)
const {
546 const IntegerVariableSpan span = dominating_vars_[var];
547 if (span.size == 0)
return absl::Span<const IntegerVariable>();
548 return absl::Span<const IntegerVariable>(&buffer_[span.start], span.size);
567 IntegerValue* bounds = can_freely_decrease_until_.data();
568 int* locks = num_locks_.data();
569 int* locking_index = locking_ct_index_.data();
570 for (
const int ref : refs) {
571 const int var = RefToIntegerVariable(ref).value();
574 locking_index[var] = ct_index;
581 IntegerValue* bounds = can_freely_decrease_until_.data();
582 int* locks = num_locks_.data();
583 int* locking_index = locking_ct_index_.data();
584 for (
const int ref : refs) {
585 const int negated_var =
NegationOf(RefToIntegerVariable(ref)).value();
587 locks[negated_var]++;
588 locking_index[negated_var] = ct_index;
595 IntegerValue* bounds = can_freely_decrease_until_.data();
596 int* locks = num_locks_.data();
597 int* locking_index = locking_ct_index_.data();
598 for (
const int ref : refs) {
599 const IntegerVariable var = RefToIntegerVariable(ref);
600 const IntegerVariable minus_var =
NegationOf(var);
603 locks[var.value()]++;
604 locks[minus_var.value()]++;
605 locking_index[var.value()] = ct_index;
606 locking_index[minus_var.value()] = ct_index;
610template <
typename LinearProto>
613 const LinearProto& linear, int64_t min_activity, int64_t max_activity,
615 const int64_t lb_limit = linear.domain(linear.domain_size() - 2);
616 const int64_t ub_limit = linear.domain(1);
617 const int num_terms = linear.vars_size();
618 for (
int i = 0;
i < num_terms; ++
i) {
619 int ref = linear.vars(
i);
620 int64_t coeff = linear.coeffs(
i);
626 const int64_t min_term = coeff * context.
MinOf(ref);
627 const int64_t max_term = coeff * context.
MaxOf(ref);
628 const int64_t term_diff = max_term - min_term;
629 const IntegerVariable var = RefToIntegerVariable(ref);
632 if (min_activity < lb_limit) {
634 locking_ct_index_[var] = ct_index;
635 if (min_activity + term_diff < lb_limit) {
638 const IntegerValue slack(lb_limit - min_activity);
639 const IntegerValue var_diff =
640 CeilRatio(IntegerValue(slack), IntegerValue(coeff));
641 can_freely_decrease_until_[var] =
642 std::max(can_freely_decrease_until_[var],
643 IntegerValue(context.
MinOf(ref)) + var_diff);
656 if (max_activity > ub_limit) {
658 locking_ct_index_[
NegationOf(var)] = ct_index;
659 if (max_activity - term_diff > ub_limit) {
662 const IntegerValue slack(max_activity - ub_limit);
663 const IntegerValue var_diff =
664 CeilRatio(IntegerValue(slack), IntegerValue(coeff));
666 std::max(can_freely_decrease_until_[
NegationOf(var)],
667 -IntegerValue(context.
MaxOf(ref)) + var_diff);
678void TransformLinearWithSpecialBoolean(
const ConstraintProto& ct,
int ref,
679 std::vector<int64_t>* output) {
680 DCHECK_EQ(ct.constraint_case(), ConstraintProto::kLinear);
685 if (!ct.enforcement_literal().empty()) {
686 output->push_back(ct.enforcement_literal().size());
687 for (
const int literal : ct.enforcement_literal()) {
689 output->push_back(std::numeric_limits<int32_t>::max());
691 output->push_back(literal);
699 output->push_back(ct.linear().vars().size());
700 for (
int i = 0;
i < ct.linear().vars().size(); ++
i) {
701 const int v = ct.linear().vars(
i);
702 const int64_t
c = ct.linear().
coeffs(
i);
704 output->push_back(std::numeric_limits<int32_t>::max());
705 output->push_back(c);
708 output->push_back(std::numeric_limits<int32_t>::max());
709 output->push_back(-c);
712 output->push_back(v);
713 output->push_back(c);
718 for (
const int64_t value : ct.linear().domain()) {
719 output->push_back(
CapSub(value, offset));
727 num_deleted_constraints_ = 0;
729 const int num_vars = cp_model.variables_size();
730 int64_t num_fixed_vars = 0;
731 for (
int var = 0; var < num_vars; ++var) {
732 if (context->
IsFixed(var))
continue;
735 const int64_t lb = context->
MinOf(var);
737 if (ub_limit == lb) {
744 const int64_t ub = context->
MaxOf(var);
745 const int64_t lb_limit =
747 if (lb_limit == ub) {
755 if (lb_limit > ub_limit) {
759 int64_t value = domain.
Contains(0) ? 0 : domain.
Min();
762 if (std::abs(bound) < std::abs(value)) value = bound;
774 if (lb_limit > lb || ub_limit < ub) {
775 const int64_t new_ub =
779 Domain(ub_limit, std::numeric_limits<int64_t>::max()))
782 const int64_t new_lb =
786 Domain(std::numeric_limits<int64_t>::min(), lb_limit))
793 if (num_fixed_vars > 0) {
799 absl::flat_hash_set<int> equiv_ct_index_set;
800 absl::flat_hash_map<uint64_t, std::pair<int, int>> equiv_modified_constraints;
801 std::vector<int64_t> temp_data;
802 std::vector<int64_t> other_temp_data;
809 std::vector<bool> processed(num_vars,
false);
810 int64_t num_bool_in_near_duplicate_ct = 0;
811 for (IntegerVariable var(0); var < num_locks_.size(); ++var) {
814 if (processed[positive_ref])
continue;
815 if (context->
IsFixed(positive_ref))
continue;
819 if (num_locks_[var] != 1)
continue;
820 if (locking_ct_index_[var] == -1) {
822 "TODO dual: only one unspecified blocking constraint?");
826 const int ct_index = locking_ct_index_[var];
827 const ConstraintProto& ct = context->
working_model->constraints(ct_index);
828 if (ct.constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
832 if (ct.constraint_case() == ConstraintProto::kAtMostOne) {
837 if (ct.constraint_case() != ConstraintProto::kBoolAnd) {
844 if (ct.enforcement_literal().size() == 1 &&
845 PositiveRef(ct.enforcement_literal(0)) != positive_ref) {
846 const int enf = ct.enforcement_literal(0);
848 : context->
MaxOf(positive_ref);
876 processed[positive_ref] =
true;
887 positive_ref, enf, implied.
FixedValue() - bound, bound)) {
906 processed[positive_ref] =
true;
924 if (ct.constraint_case() == ConstraintProto::kLinear &&
925 ct.linear().vars().size() == 1 &&
926 ct.enforcement_literal().size() == 1 &&
927 ct.enforcement_literal(0) ==
NegatedRef(ref)) {
928 const int var = ct.linear().vars(0);
940 context->
working_model->mutable_constraints(ct_index)->Clear();
944 if (rhs == var_domain) {
948 context->
working_model->mutable_constraints(ct_index)->Clear();
956 const int64_t value =
963 if (encoding_lit ==
NegatedRef(ref))
continue;
974 if (encoding_lit == ref)
continue;
985 context->
working_model->mutable_constraints(ct_index)->Clear();
991 if (
PositiveRef(encoding_lit) < processed.size()) {
1005 ref,
false, {{var, 1}}, complement);
1006 ConstraintProto* new_ct = context->
working_model->add_constraints();
1007 new_ct->add_enforcement_literal(ref);
1008 new_ct->mutable_linear()->add_vars(var);
1009 new_ct->mutable_linear()->add_coeffs(1);
1016 }
else if (complement.
IsFixed()) {
1044 if (equiv_ct_index_set.insert(ct_index).second &&
1045 ct.constraint_case() == ConstraintProto::kLinear &&
1047 TransformLinearWithSpecialBoolean(ct, ref, &temp_data);
1048 const uint64_t hash =
1049 fasthash64(temp_data.data(), temp_data.size() *
sizeof(int64_t),
1050 uint64_t{0xa5b85c5e198ed849});
1051 const auto [it, inserted] =
1052 equiv_modified_constraints.insert({hash, {ct_index, ref}});
1055 const auto [other_c_with_same_hash, other_ref] = it->second;
1056 CHECK_NE(other_c_with_same_hash, ct_index);
1057 const auto& other_ct =
1058 context->
working_model->constraints(other_c_with_same_hash);
1059 TransformLinearWithSpecialBoolean(other_ct, other_ref,
1061 if (temp_data == other_temp_data) {
1068 "dual: detected l => ct and not(l) => ct with unused l!");
1077 ++num_bool_in_near_duplicate_ct;
1092 ++num_deleted_constraints_;
1093 context->
working_model->mutable_constraints(ct_index)->Clear();
1102 if (!ct.enforcement_literal().empty()) {
1103 if (ct.constraint_case() == ConstraintProto::kLinear &&
1104 ct.linear().vars().size() == 1 &&
1105 ct.enforcement_literal().size() == 1 &&
1106 ct.enforcement_literal(0) ==
NegatedRef(ref)) {
1110 "TODO dual: only one blocking enforced constraint?");
1117 if (ct.enforcement_literal().size() != 1)
continue;
1132 int a = ct.enforcement_literal(0);
1135 num_locks_[RefToIntegerVariable(
NegatedRef(a))] == 1) {
1138 if (ct.bool_and().literals().size() != 1)
continue;
1139 b = ct.bool_and().literals(0);
1143 for (
const int lhs : ct.bool_and().literals()) {
1145 num_locks_[RefToIntegerVariable(lhs)] == 1) {
1153 CHECK_EQ(num_locks_[RefToIntegerVariable(
NegatedRef(a))], 1);
1166 if (num_bool_in_near_duplicate_ct) {
1168 "dual: equivalent Boolean in near-duplicate constraints",
1169 num_bool_in_near_duplicate_ct);
1172 VLOG(2) <<
"Num deleted constraints: " << num_deleted_constraints_;
1181 const int num_vars = cp_model.variables().size();
1182 var_domination->
Reset(num_vars);
1184 for (
int var = 0; var < num_vars; ++var) {
1199 }
else if (r.
coeff == -1) {
1209 const int num_constraints = cp_model.constraints_size();
1210 std::vector<bool> c_is_free_to_increase(num_constraints);
1211 std::vector<bool> c_is_free_to_decrease(num_constraints);
1212 for (
int c = 0; c < num_constraints; ++c) {
1213 const ConstraintProto& ct = cp_model.constraints(c);
1214 switch (ct.constraint_case()) {
1215 case ConstraintProto::kBoolOr:
1217 case ConstraintProto::kBoolAnd:
1219 case ConstraintProto::kAtMostOne:
1221 case ConstraintProto::kExactlyOne:
1225 case ConstraintProto::kLinear: {
1227 const auto [min_activity, max_activity] =
1229 const bool domain_is_simple = ct.linear().domain().size() == 2;
1230 const bool free_to_increase =
1231 domain_is_simple && ct.linear().domain(1) >= max_activity;
1232 const bool free_to_decrease =
1233 domain_is_simple && ct.linear().domain(0) <= min_activity;
1234 c_is_free_to_increase[c] = free_to_increase;
1235 c_is_free_to_decrease[c] = free_to_decrease;
1236 if (free_to_decrease && free_to_increase)
break;
1237 if (!free_to_increase && !free_to_decrease) {
1239 ct.linear().coeffs());
1255 if (cp_model.has_objective()) {
1259 const auto [min_activity, max_activity] =
1261 const auto& domain = cp_model.objective().domain();
1262 if (domain.empty() || (domain.size() == 2 && domain[0] <= min_activity)) {
1266 cp_model.objective().coeffs());
1273 std::vector<int> tmp;
1274 for (
int phase = 0; phase < 2; phase++) {
1275 for (
int c = 0; c < num_constraints; ++c) {
1276 const ConstraintProto& ct = cp_model.constraints(c);
1277 switch (ct.constraint_case()) {
1278 case ConstraintProto::kBoolOr:
1280 ct.bool_or().literals(),
1283 case ConstraintProto::kBoolAnd:
1293 for (
const int ref : ct.enforcement_literal()) {
1296 for (
const int ref : ct.bool_and().literals()) {
1303 case ConstraintProto::kAtMostOne:
1305 ct.at_most_one().literals(),
1308 case ConstraintProto::kLinear: {
1309 const bool free_to_increase = c_is_free_to_increase[c];
1310 const bool free_to_decrease = c_is_free_to_decrease[c];
1311 if (free_to_decrease && free_to_increase)
break;
1312 if (free_to_increase) {
1315 ct.linear().coeffs());
1316 }
else if (free_to_decrease) {
1319 ct.linear().coeffs());
1321 if (!ct.enforcement_literal().empty()) {
1323 {}, ct.enforcement_literal(), {});
1335 if (cp_model.has_objective()) {
1336 const auto [min_activity, max_activity] =
1338 const auto& domain = cp_model.objective().domain();
1339 if (domain.empty() || (domain.size() == 2 && domain[0] <= min_activity)) {
1341 {}, cp_model.objective().vars(),
1342 cp_model.objective().coeffs());
1359 int64_t num_unconstrained_refs = 0;
1360 int64_t num_dominated_refs = 0;
1361 int64_t num_dominance_relations = 0;
1362 for (
int var = 0; var < num_vars; ++var) {
1363 if (context.
IsFixed(var))
continue;
1365 for (
const int ref : {var,
NegatedRef(var)}) {
1367 num_unconstrained_refs++;
1369 num_dominated_refs++;
1370 num_dominance_relations +=
1375 if (num_unconstrained_refs == 0 && num_dominated_refs == 0)
return;
1376 VLOG(1) <<
"Dominance:"
1377 <<
" num_unconstrained_refs=" << num_unconstrained_refs
1378 <<
" num_dominated_refs=" << num_dominated_refs
1379 <<
" num_dominance_relations=" << num_dominance_relations;
1387 const int num_vars = cp_model.variables().size();
1388 dual_bound_strengthening->
Reset(num_vars);
1390 for (
int var = 0; var < num_vars; ++var) {
1406 const int num_constraints = cp_model.constraints_size();
1407 for (
int c = 0; c < num_constraints; ++c) {
1408 const ConstraintProto& ct = cp_model.constraints(c);
1409 dual_bound_strengthening->
CannotIncrease(ct.enforcement_literal(), c);
1410 switch (ct.constraint_case()) {
1411 case ConstraintProto::kBoolOr:
1412 dual_bound_strengthening->
CannotDecrease(ct.bool_or().literals(), c);
1414 case ConstraintProto::kBoolAnd:
1415 dual_bound_strengthening->
CannotDecrease(ct.bool_and().literals(), c);
1417 case ConstraintProto::kAtMostOne:
1418 dual_bound_strengthening->
CannotIncrease(ct.at_most_one().literals(),
1421 case ConstraintProto::kExactlyOne:
1422 dual_bound_strengthening->
CannotMove(ct.exactly_one().literals(), c);
1424 case ConstraintProto::kLinear: {
1426 const auto [min_activity, max_activity] =
1429 false, context, ct.linear(), min_activity, max_activity, c);
1442 if (cp_model.has_objective()) {
1446 const auto [min_activity, max_activity] =
1449 true, context, cp_model.objective(), min_activity, max_activity);
1461void MaybeUpdateRefHintFromDominance(
1462 PresolveContext& context,
int ref,
const Domain& domain,
1463 absl::Span<const IntegerVariable> dominating_variables) {
1465 DCHECK_EQ(domain.
Min(), context.DomainOf(ref).Min());
1466 DCHECK(context.DomainOf(ref).Contains(domain.
Max()));
1467 std::vector<std::pair<int, Domain>> dominating_refs;
1468 dominating_refs.reserve(dominating_variables.size());
1469 for (
const IntegerVariable var : dominating_variables) {
1471 dominating_refs.push_back(
1472 {dominating_ref, context.DomainOf(dominating_ref)});
1474 context.solution_crush().UpdateRefsWithDominance(
1475 ref, domain.
Min(), domain.
Max(), dominating_refs);
1478bool ProcessAtMostOne(
1479 absl::Span<const int> literals,
const std::string& message,
1481 util_intops::StrongVector<IntegerVariable, bool>* in_constraints,
1483 for (
const int ref : literals) {
1486 for (
const int ref : literals) {
1487 if (context->IsFixed(ref))
continue;
1489 const auto dominating_ivars = var_domination.DominatingVariables(ref);
1490 for (
const IntegerVariable ivar : dominating_ivars) {
1492 if (!(*in_constraints)[ivar])
continue;
1493 if (context->IsFixed(iref)) {
1501 context->UpdateRuleStats(message);
1502 context->solution_crush().UpdateLiteralsWithDominance(ref, iref);
1503 if (!context->SetLiteralToFalse(ref))
return false;
1507 for (
const int ref : literals) {
1518 const int num_vars = cp_model.variables_size();
1521 bool work_to_do =
false;
1522 for (
int var = 0; var < num_vars; ++var) {
1523 if (context->
IsFixed(var))
continue;
1530 if (!work_to_do)
return true;
1541 num_vars * 2, std::numeric_limits<int64_t>::min());
1552 absl::flat_hash_set<std::pair<int, int>> implications;
1553 const int num_constraints = cp_model.constraints_size();
1554 for (
int c = 0; c < num_constraints; ++c) {
1555 const ConstraintProto& ct = cp_model.constraints(c);
1557 if (ct.constraint_case() == ConstraintProto::kBoolAnd) {
1558 if (ct.enforcement_literal().size() != 1)
continue;
1559 const int a = ct.enforcement_literal(0);
1560 if (context->
IsFixed(a))
continue;
1561 for (
const int b : ct.bool_and().literals()) {
1563 implications.insert({a,
b});
1570 for (
const IntegerVariable ivar :
1580 if (context->
IsFixed(a))
break;
1586 for (
const IntegerVariable ivar :
1600 if (!ct.enforcement_literal().empty())
continue;
1606 const auto add_implications = [&implications](absl::Span<const int> lits) {
1607 if (lits.size() > 3)
return;
1608 for (
int i = 0;
i < lits.size(); ++
i) {
1609 for (
int j = 0; j < lits.size(); ++j) {
1610 if (
i == j)
continue;
1611 implications.insert({lits[
i],
NegatedRef(lits[j])});
1615 if (ct.constraint_case() == ConstraintProto::kAtMostOne) {
1616 add_implications(ct.at_most_one().literals());
1617 if (!ProcessAtMostOne(ct.at_most_one().literals(),
1618 "domination: in at most one", var_domination,
1619 &in_constraints, context)) {
1622 }
else if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
1623 add_implications(ct.exactly_one().literals());
1624 if (!ProcessAtMostOne(ct.exactly_one().literals(),
1625 "domination: in exactly one", var_domination,
1626 &in_constraints, context)) {
1631 if (ct.constraint_case() != ConstraintProto::kLinear)
continue;
1633 int num_dominated = 0;
1640 if (num_dominated == 0)
continue;
1643 int64_t min_activity = 0;
1644 int64_t max_activity = 0;
1645 const int num_terms = ct.linear().vars_size();
1646 for (
int i = 0;
i < num_terms; ++
i) {
1647 int ref = ct.linear().vars(
i);
1648 int64_t coeff = ct.linear().coeffs(
i);
1653 const int64_t min_term = coeff * context->
MinOf(ref);
1654 const int64_t max_term = coeff * context->
MaxOf(ref);
1655 min_activity += min_term;
1656 max_activity += max_term;
1658 var_lb_to_ub_diff[ivar] = max_term - min_term;
1659 var_lb_to_ub_diff[
NegationOf(ivar)] = min_term - max_term;
1661 const int64_t rhs_lb = ct.linear().domain(0);
1662 const int64_t rhs_ub = ct.linear().domain(ct.linear().domain_size() - 1);
1663 if (max_activity < rhs_lb || min_activity > rhs_ub) {
1669 const auto get_delta = [context, &var_lb_to_ub_diff](
1671 absl::Span<const IntegerVariable> vars) {
1673 for (
const IntegerVariable var : vars) {
1681 delta += std::max(int64_t{0}, var_lb_to_ub_diff[var]);
1683 delta += std::max(int64_t{0}, -var_lb_to_ub_diff[var]);
1690 for (
int i = 0;
i < num_terms; ++
i) {
1691 const int ref = ct.linear().vars(
i);
1692 const int64_t coeff = ct.linear().coeffs(
i);
1693 const int64_t coeff_magnitude = std::abs(coeff);
1694 if (context->
IsFixed(ref))
continue;
1697 const bool only_lb = max_activity <= rhs_ub;
1698 const bool only_ub = min_activity >= rhs_lb;
1699 if (only_lb || only_ub) {
1701 const int current_ref = (coeff > 0) == only_lb ? ref :
NegatedRef(ref);
1702 const int64_t shifted_rhs =
1703 only_lb ? rhs_lb - min_activity : max_activity - rhs_ub;
1704 const IntegerVariable current_ivar =
1706 can_freely_decrease_count[
NegationOf(current_ivar)]++;
1708 const int64_t delta = get_delta(
1713 const int64_t slack = shifted_rhs - delta;
1714 const int64_t current_lb = context->
MinOf(current_ref);
1724 const int64_t min_delta =
1726 can_freely_decrease_until[current_ivar] = std::max(
1727 can_freely_decrease_until[current_ivar], current_lb + min_delta);
1728 can_freely_decrease_count[current_ivar]++;
1732 for (
const int current_ref : {ref,
NegatedRef(ref)}) {
1733 const absl::Span<const IntegerVariable> dominated_by =
1735 if (dominated_by.empty())
continue;
1737 const bool ub_side = (coeff > 0) == (current_ref == ref);
1739 if (max_activity <= rhs_ub)
continue;
1741 if (min_activity >= rhs_lb)
continue;
1743 const int64_t slack =
1744 ub_side ? rhs_ub - min_activity : max_activity - rhs_lb;
1748 const int64_t delta = get_delta(ub_side, dominated_by);
1749 const int64_t lb = context->
MinOf(current_ref);
1750 if (delta + coeff_magnitude > slack) {
1753 MaybeUpdateRefHintFromDominance(*context, current_ref, reduced_domain,
1760 const IntegerVariable current_var =
1763 CHECK_GE(var_lb_to_ub_diff[current_var], 0);
1764 max_activity -= var_lb_to_ub_diff[current_var];
1766 CHECK_LE(var_lb_to_ub_diff[current_var], 0);
1767 min_activity -= var_lb_to_ub_diff[current_var];
1769 var_lb_to_ub_diff[current_var] = 0;
1770 var_lb_to_ub_diff[
NegationOf(current_var)] = 0;
1775 const IntegerValue diff =
FloorRatio(IntegerValue(slack - delta),
1776 IntegerValue(coeff_magnitude));
1777 int64_t new_ub = lb + diff.value();
1778 if (new_ub < context->MaxOf(current_ref)) {
1784 if (new_ub < context->MaxOf(current_ref)) {
1787 MaybeUpdateRefHintFromDominance(*context, current_ref, reduced_domain,
1794 const IntegerVariable current_var =
1797 CHECK_GE(var_lb_to_ub_diff[current_var], 0);
1798 max_activity -= var_lb_to_ub_diff[current_var];
1800 CHECK_LE(var_lb_to_ub_diff[current_var], 0);
1801 min_activity -= var_lb_to_ub_diff[current_var];
1803 const int64_t new_diff = std::abs(coeff_magnitude * (new_ub - lb));
1805 var_lb_to_ub_diff[current_var] = new_diff;
1806 var_lb_to_ub_diff[
NegationOf(current_var)] = -new_diff;
1807 max_activity += new_diff;
1809 var_lb_to_ub_diff[current_var] = -new_diff;
1810 var_lb_to_ub_diff[
NegationOf(current_var)] = +new_diff;
1811 min_activity -= new_diff;
1818 for (
const int ref : ct.linear().vars()) {
1820 var_lb_to_ub_diff[ivar] = 0;
1841 2 * num_vars,
false);
1842 for (
int positive_ref = 0; positive_ref < num_vars; ++positive_ref) {
1843 if (context->
IsFixed(positive_ref))
continue;
1851 if (obj_coeff > 0) {
1854 }
else if (obj_coeff < 0) {
1860 for (
const int ref : {positive_ref,
NegatedRef(positive_ref)}) {
1862 if (increase_is_forbidden[
NegationOf(var)])
continue;
1863 if (can_freely_decrease_count[var] ==
1866 int64_t lb = can_freely_decrease_until[var];
1868 if (lb < context->MaxOf(ref)) {
1875 const absl::Span<const IntegerVariable> dominating_vars =
1877 for (
const IntegerVariable dom : dominating_vars) {
1883 if (increase_is_forbidden[dom] ||
1890 if (increase_is_forbidden[
NegationOf(var)]) {
1895 increase_is_forbidden[var] =
true;
1897 "domination: dual strenghtening using dominance");
1899 MaybeUpdateRefHintFromDominance(*context, ref, reduced_domain,
1915 for (
const IntegerVariable dom :
1917 if (increase_is_forbidden[dom])
continue;
1919 if (context->
IsFixed(dom_ref))
continue;
1923 if (implications.contains({ref, dom_ref}))
continue;
1933 implications.insert({ref, dom_ref});
1937 increase_is_forbidden[var] =
true;
1938 increase_is_forbidden[
NegationOf(dom)] =
true;
1943 if (num_added > 0) {
1944 VLOG(1) <<
"Added " << num_added <<
" domination implications.";
1945 context->
UpdateRuleStats(
"domination: added implications", num_added);
int64_t ValueAtOrAfter(int64_t input) const
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)
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)
All constraints should be mapped to one of more call to these functions.
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)
Most of the logic here deals with linear constraints.
DomainDeductions deductions
Advanced presolve. See this class comment.
Domain DomainOf(int ref) const
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
Each time a domain is modified this is set to true.
ABSL_MUST_USE_RESULT bool SetLiteralToTrue(int lit)
void AddImplication(int a, int b)
a => b.
ABSL_MUST_USE_RESULT bool SetLiteralToFalse(int lit)
Returns false if the 'lit' doesn't have the desired value in the domain.
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
Returns true if this ref no longer appears in the model.
bool VariableWasRemoved(int ref) const
void UpdateConstraintVariableUsage(int c)
void UpdateRuleStats(const std::string &name, int num_times=1)
AffineRelation::Relation GetAffineRelation(int ref) const
Returns the representative of ref under the affine relations.
const absl::flat_hash_set< int > & VarToConstraints(int var) const
void UpdateNewConstraintsVariableUsage()
Calls UpdateConstraintVariableUsage() on all newly created constraints.
bool CanBeUsedAsLiteral(int ref) const
int64_t MinOf(int ref) const
bool StoreLiteralImpliesVarNEqValue(int literal, int var, int64_t value)
int64_t MaxOf(int ref) const
bool ModelIsUnsat() const
SolutionCrush & solution_crush()
void SetVarToValueIf(int var, int64_t value, int condition_lit)
Sets the value of var to value if the value of condition_lit is true.
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)
Returns the vector of the negated variables.
void ScanModelForDualBoundStrengthening(const PresolveContext &context, DualBoundStrengthening *dual_bound_strengthening)
Scan the model so that dual_bound_strengthening.Strenghten() works.
IntegerVariable PositiveVariable(IntegerVariable i)
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.
void ScanModelForDominanceDetection(PresolveContext &context, VarDomination *var_domination)
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.
int64_t CapSub(int64_t x, int64_t y)
uint64_t fasthash64(const void *buf, size_t len, uint64_t seed)
std::unique_ptr< IntegerValue[]> coeffs