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/strings/str_cat.h"
32#include "absl/types/span.h"
38#include "ortools/sat/cp_model.pb.h"
54 num_vars_with_negation_ = 2 * num_variables;
56 std::make_unique<SimpleDynamicPartition>(num_vars_with_negation_);
58 can_freely_decrease_.
assign(num_vars_with_negation_,
true);
60 shared_buffer_.clear();
61 has_initial_candidates_.
assign(num_vars_with_negation_,
false);
62 initial_candidates_.
assign(num_vars_with_negation_, IntegerVariableSpan());
65 dominating_vars_.
assign(num_vars_with_negation_, IntegerVariableSpan());
67 ct_index_for_signature_ = 0;
68 block_down_signatures_.
assign(num_vars_with_negation_, 0);
71void VarDomination::RefinePartition(std::vector<int>* vars) {
72 if (vars->empty())
return;
73 partition_->Refine(*vars);
74 for (
int&
var : *vars) {
75 const IntegerVariable wrapped(
var);
76 can_freely_decrease_[wrapped] =
false;
77 can_freely_decrease_[
NegationOf(wrapped)] =
false;
80 partition_->Refine(*vars);
84 if (phase_ != 0)
return;
86 for (
const int ref : refs) {
89 RefinePartition(&tmp_vars_);
94 absl::Span<const int64_t> coeffs) {
95 if (phase_ != 0)
return;
96 FillTempRanks(
false, {}, refs,
99 for (
int i = 0;
i < tmp_ranks_.size(); ++
i) {
100 if (
i > 0 && tmp_ranks_[
i].rank != tmp_ranks_[
i - 1].rank) {
101 RefinePartition(&tmp_vars_);
104 tmp_vars_.push_back(tmp_ranks_[
i].
var.value());
106 RefinePartition(&tmp_vars_);
111void VarDomination::ProcessTempRanks() {
115 ++ct_index_for_signature_;
116 for (IntegerVariableWithRank& entry : tmp_ranks_) {
117 can_freely_decrease_[entry.var] =
false;
118 block_down_signatures_[entry.var] |= uint64_t{1}
119 << (ct_index_for_signature_ % 64);
120 entry.part = partition_->PartOf(entry.var.value());
123 tmp_ranks_.begin(), tmp_ranks_.end(),
124 [](
const IntegerVariableWithRank&
a,
const IntegerVariableWithRank&
b) {
125 return a.part < b.part;
128 for (
int i = 1;
i < tmp_ranks_.size(); ++
i) {
129 if (tmp_ranks_[
i].part != tmp_ranks_[
start].part) {
130 Initialize({&tmp_ranks_[
start],
static_cast<size_t>(
i -
start)});
134 if (
start < tmp_ranks_.size()) {
135 Initialize({&tmp_ranks_[
start], tmp_ranks_.size() -
start});
137 }
else if (phase_ == 1) {
138 FilterUsingTempRanks();
141 CheckUsingTempRanks();
146 absl::Span<const int> enforcements, absl::Span<const int> refs,
147 absl::Span<const int64_t> coeffs) {
148 FillTempRanks(
false, enforcements, refs, coeffs);
153 absl::Span<const int> enforcements, absl::Span<const int> refs,
154 absl::Span<const int64_t> coeffs) {
155 FillTempRanks(
true, enforcements, refs, coeffs);
159void VarDomination::MakeRankEqualToStartOfPart(
160 absl::Span<IntegerVariableWithRank> span) {
161 const int size = span.size();
163 int previous_value = 0;
164 for (
int i = 0;
i <
size; ++
i) {
165 const int64_t
value = span[
i].rank;
166 if (
value != previous_value) {
167 previous_value =
value;
174void VarDomination::Initialize(absl::Span<IntegerVariableWithRank> span) {
177 MakeRankEqualToStartOfPart(span);
181 if (span.empty())
return;
182 const int part = partition_->PartOf(span[0].
var.value());
183 for (
int i = 1;
i < span.size(); ++
i) {
184 CHECK_EQ(part, partition_->PartOf(span[
i].var.value()));
188 const int future_start = shared_buffer_.size();
189 int first_start = -1;
190 const int size = span.size();
191 for (
int i = 0;
i <
size; ++
i) {
192 const IntegerVariableWithRank entry = span[
i];
193 const int num_candidates =
size - entry.rank;
196 if (has_initial_candidates_[entry.var]) {
197 if (num_candidates >= initial_candidates_[entry.var].size)
continue;
200 if (first_start == -1) first_start = entry.rank;
201 has_initial_candidates_[entry.var] =
true;
202 initial_candidates_[entry.var] = {
203 future_start - first_start +
static_cast<int>(entry.rank),
209 if (first_start == -1)
return;
210 for (
int i = first_start;
i <
size; ++
i) {
211 shared_buffer_.push_back(span[
i].
var);
227 const int kMaxInitialSize = 50;
228 std::vector<IntegerVariable> cropped_vars;
230 num_vars_with_negation_,
false);
233 int non_cropped_size = 0;
234 std::vector<IntegerVariable> partition_data;
235 const std::vector<absl::Span<const IntegerVariable>> elements_by_part =
236 partition_->GetParts(&partition_data);
237 for (IntegerVariable
var(0);
var < num_vars_with_negation_; ++
var) {
238 if (can_freely_decrease_[
var])
continue;
240 const int part = partition_->PartOf(
var.value());
241 const int start = buffer_.size();
242 const uint64_t var_sig = block_down_signatures_[
var];
243 const uint64_t not_var_sig = block_down_signatures_[
NegationOf(
var)];
244 absl::Span<const IntegerVariable> to_scan =
245 has_initial_candidates_[
var] ? InitialDominatingCandidates(
var)
246 : elements_by_part[part];
252 if (to_scan.size() <= 1'000) {
253 for (
const IntegerVariable
x : to_scan) {
254 if (var_sig & ~block_down_signatures_[
x])
continue;
255 if (block_down_signatures_[
NegationOf(
x)] & ~not_var_sig)
continue;
259 buffer_.push_back(
x);
260 if (new_size >= kMaxInitialSize) {
261 is_cropped[
var] =
true;
262 cropped_vars.push_back(
var);
266 is_cropped[
var] =
true;
267 cropped_vars.push_back(
var);
268 for (
int i = 0;
i < 200; ++
i) {
269 const IntegerVariable
x = to_scan[
i];
270 if (var_sig & ~block_down_signatures_[
x])
continue;
271 if (block_down_signatures_[
NegationOf(
x)] & ~not_var_sig)
continue;
275 buffer_.push_back(
x);
276 if (new_size >= kMaxInitialSize)
break;
280 if (!is_cropped[
var]) non_cropped_size += new_size;
281 dominating_vars_[
var] = {
start, new_size};
291 int total_extra_space = 0;
293 num_vars_with_negation_, 0);
294 for (IntegerVariable
var(0);
var < num_vars_with_negation_; ++
var) {
304 other_buffer_.resize(buffer_.size() + total_extra_space);
305 for (IntegerVariable
var(0);
var < num_vars_with_negation_; ++
var) {
306 IntegerVariableSpan& s = dominating_vars_[
var];
307 for (
int i = 0;
i < s.size; ++
i) {
308 other_buffer_[copy_index +
i] = buffer_[s.start +
i];
310 s.start = copy_index;
311 copy_index += s.size + extra_space[
var];
312 extra_space[
var] = s.size;
314 DCHECK_EQ(copy_index, other_buffer_.size());
315 std::swap(buffer_, other_buffer_);
320 for (IntegerVariable
var(0);
var < num_vars_with_negation_; ++
var) {
321 const int start = dominating_vars_[
var].start;
322 const int size = extra_space[
var];
323 for (
int i = 0;
i <
size; ++
i) {
324 const IntegerVariable dom = buffer_[
start +
i];
326 IntegerVariableSpan& s = dominating_vars_[
NegationOf(dom)];
335 for (
const IntegerVariable
var : cropped_vars) {
336 DCHECK(is_cropped[
var]);
337 IntegerVariableSpan& s = dominating_vars_[
var];
338 if (s.size == 0)
continue;
339 IntegerVariable* pt = &buffer_[s.start];
340 std::sort(pt, pt + s.size);
341 const auto end = std::unique(pt, pt + s.size);
346 VLOG(1) <<
"Num initial list that where cropped: "
348 VLOG(1) <<
"Shared buffer size: " <<
FormatCounter(shared_buffer_.size());
349 VLOG(1) <<
"Non-cropped buffer size: " <<
FormatCounter(non_cropped_size);
356 return !buffer_.empty();
364 shared_buffer_.clear();
365 initial_candidates_.
assign(num_vars_with_negation_, IntegerVariableSpan());
368 for (IntegerVariable
var(0);
var < num_vars_with_negation_; ++
var) {
376 for (IntegerVariable
var(0);
var < num_vars_with_negation_; ++
var) {
377 initial_candidates_[
var].start =
start;
378 start += initial_candidates_[
var].size;
379 initial_candidates_[
var].size = 0;
381 shared_buffer_.resize(
start);
384 for (IntegerVariable
var(0);
var < num_vars_with_negation_; ++
var) {
386 IntegerVariableSpan& span = initial_candidates_[
NegationOf(dom)];
393 tmp_var_to_rank_.
resize(num_vars_with_negation_, -1);
394 for (IntegerVariable
var(0);
var < num_vars_with_negation_; ++
var) {
395 for (
const IntegerVariable dom : InitialDominatingCandidates(
var)) {
396 tmp_var_to_rank_[dom] = 1;
400 IntegerVariableSpan& span = dominating_vars_[
var];
402 if (tmp_var_to_rank_[dom] != 1) {
406 buffer_[span.start + new_size++] = dom;
408 span.size = new_size;
410 for (
const IntegerVariable dom : InitialDominatingCandidates(
var)) {
411 tmp_var_to_rank_[dom] = -1;
415 VLOG(1) <<
"Transpose removed " <<
FormatCounter(num_removed);
420void VarDomination::FillTempRanks(
bool reverse_references,
421 absl::Span<const int> enforcements,
422 absl::Span<const int> refs,
423 absl::Span<const int64_t> coeffs) {
425 if (coeffs.empty()) {
427 for (
const int ref : refs) {
428 const IntegerVariable
var =
430 tmp_ranks_.push_back({
var, 0, 0});
434 for (
int i = 0;
i < refs.size(); ++
i) {
435 if (coeffs[
i] == 0)
continue;
439 tmp_ranks_.push_back({
var, 0, coeffs[
i]});
444 std::sort(tmp_ranks_.begin(), tmp_ranks_.end());
445 MakeRankEqualToStartOfPart({&tmp_ranks_[0], tmp_ranks_.size()});
451 const int enforcement_rank = tmp_ranks_.size();
452 for (
const int ref : enforcements) {
453 tmp_ranks_.push_back(
460void VarDomination::FilterUsingTempRanks() {
462 tmp_var_to_rank_.
resize(num_vars_with_negation_, -1);
463 for (
const IntegerVariableWithRank entry : tmp_ranks_) {
464 tmp_var_to_rank_[entry.var] = entry.rank;
468 for (
const IntegerVariableWithRank entry : tmp_ranks_) {
476 IntegerVariableSpan& span = dominating_vars_[entry.var];
477 if (span.size == 0)
continue;
480 if (tmp_var_to_rank_[candidate] < entry.rank)
continue;
481 buffer_[span.start + new_size++] = candidate;
483 span.size = new_size;
488 for (
const IntegerVariableWithRank entry : tmp_ranks_) {
489 tmp_var_to_rank_[entry.var] = -1;
494void VarDomination::CheckUsingTempRanks() {
495 tmp_var_to_rank_.
resize(num_vars_with_negation_, -1);
496 for (
const IntegerVariableWithRank entry : tmp_ranks_) {
497 tmp_var_to_rank_[entry.var] = entry.rank;
501 for (IntegerVariable
var(0);
var < num_vars_with_negation_; ++
var) {
502 const int var_rank = tmp_var_to_rank_[
var];
503 const int negated_var_rank = tmp_var_to_rank_[
NegationOf(
var)];
505 CHECK(!can_freely_decrease_[
NegationOf(dom)]);
509 CHECK_LE(var_rank, tmp_var_to_rank_[dom]);
510 CHECK_LE(tmp_var_to_rank_[
NegationOf(dom)], negated_var_rank);
514 for (
const IntegerVariableWithRank entry : tmp_ranks_) {
515 tmp_var_to_rank_[entry.var] = -1;
524 return can_freely_decrease_[
var];
527absl::Span<const IntegerVariable> VarDomination::InitialDominatingCandidates(
528 IntegerVariable
var)
const {
529 const IntegerVariableSpan span = initial_candidates_[
var];
530 if (span.size == 0)
return absl::Span<const IntegerVariable>();
531 return absl::Span<const IntegerVariable>(&shared_buffer_[span.start],
541 IntegerVariable
var)
const {
542 const IntegerVariableSpan span = dominating_vars_[
var];
543 if (span.size == 0)
return absl::Span<const IntegerVariable>();
544 return absl::Span<const IntegerVariable>(&buffer_[span.start], span.size);
563 IntegerValue* bounds = can_freely_decrease_until_.
data();
564 int* locks = num_locks_.
data();
565 int* locking_index = locking_ct_index_.
data();
566 for (
const int ref : refs) {
567 const int var = RefToIntegerVariable(ref).value();
577 IntegerValue* bounds = can_freely_decrease_until_.
data();
578 int* locks = num_locks_.
data();
579 int* locking_index = locking_ct_index_.
data();
580 for (
const int ref : refs) {
581 const int negated_var =
NegationOf(RefToIntegerVariable(ref)).value();
583 locks[negated_var]++;
584 locking_index[negated_var] =
ct_index;
591 IntegerValue* bounds = can_freely_decrease_until_.
data();
592 int* locks = num_locks_.
data();
593 int* locking_index = locking_ct_index_.
data();
594 for (
const int ref : refs) {
595 const IntegerVariable
var = RefToIntegerVariable(ref);
599 locks[
var.value()]++;
600 locks[minus_var.value()]++;
602 locking_index[minus_var.value()] =
ct_index;
606template <
typename LinearProto>
609 const LinearProto& linear, int64_t min_activity, int64_t max_activity,
611 const int64_t lb_limit = linear.domain(linear.domain_size() - 2);
612 const int64_t ub_limit = linear.domain(1);
613 const int num_terms = linear.vars_size();
614 for (
int i = 0;
i < num_terms; ++
i) {
615 int ref = linear.vars(
i);
616 int64_t coeff = linear.coeffs(
i);
622 const int64_t min_term = coeff *
context.MinOf(ref);
623 const int64_t max_term = coeff *
context.MaxOf(ref);
624 const int64_t term_diff = max_term - min_term;
625 const IntegerVariable
var = RefToIntegerVariable(ref);
628 if (min_activity < lb_limit) {
631 if (min_activity + term_diff < lb_limit) {
634 const IntegerValue slack(lb_limit - min_activity);
635 const IntegerValue var_diff =
636 CeilRatio(IntegerValue(slack), IntegerValue(coeff));
637 can_freely_decrease_until_[
var] =
638 std::max(can_freely_decrease_until_[
var],
639 IntegerValue(
context.MinOf(ref)) + var_diff);
652 if (max_activity > ub_limit) {
655 if (max_activity - term_diff > ub_limit) {
658 const IntegerValue slack(max_activity - ub_limit);
659 const IntegerValue var_diff =
660 CeilRatio(IntegerValue(slack), IntegerValue(coeff));
663 -IntegerValue(
context.MaxOf(ref)) + var_diff);
674void TransformLinearWithSpecialBoolean(
const ConstraintProto&
ct,
int ref,
675 std::vector<int64_t>* output) {
676 DCHECK_EQ(
ct.constraint_case(), ConstraintProto::kLinear);
681 if (!
ct.enforcement_literal().empty()) {
682 output->push_back(
ct.enforcement_literal().size());
683 for (
const int literal :
ct.enforcement_literal()) {
685 output->push_back(std::numeric_limits<int32_t>::max());
695 output->push_back(
ct.linear().vars().size());
696 for (
int i = 0;
i <
ct.linear().vars().
size(); ++
i) {
697 const int v =
ct.linear().vars(
i);
698 const int64_t
c =
ct.linear().coeffs(
i);
700 output->push_back(std::numeric_limits<int32_t>::max());
701 output->push_back(c);
704 output->push_back(std::numeric_limits<int32_t>::max());
705 output->push_back(-c);
708 output->push_back(v);
709 output->push_back(c);
714 for (
const int64_t
value :
ct.linear().domain()) {
722 num_deleted_constraints_ = 0;
723 const CpModelProto& cp_model = *
context->working_model;
724 const int num_vars = cp_model.variables_size();
725 int64_t num_fixed_vars = 0;
726 for (
int var = 0;
var < num_vars; ++
var) {
732 if (ub_limit == lb) {
740 const int64_t lb_limit =
742 if (lb_limit == ub) {
750 if (lb_limit > ub_limit) {
761 context->UpdateRuleStats(
"dual: fix variable with multiple choices");
769 if (lb_limit > lb || ub_limit < ub) {
770 const int64_t new_ub =
774 Domain(ub_limit, std::numeric_limits<int64_t>::max()))
777 const int64_t new_lb =
781 Domain(std::numeric_limits<int64_t>::min(), lb_limit))
784 context->UpdateRuleStats(
"dual: reduced domain");
788 if (num_fixed_vars > 0) {
789 context->UpdateRuleStats(
"dual: fix variable", num_fixed_vars);
794 absl::flat_hash_set<int> equiv_ct_index_set;
795 absl::flat_hash_map<uint64_t, std::pair<int, int>> equiv_modified_constraints;
796 std::vector<int64_t> temp_data;
797 std::vector<int64_t> other_temp_data;
804 std::vector<bool> processed(num_vars,
false);
805 int64_t num_bool_in_near_duplicate_ct = 0;
806 for (IntegerVariable
var(0);
var < num_locks_.size(); ++
var) {
809 if (processed[positive_ref])
continue;
810 if (
context->IsFixed(positive_ref))
continue;
811 if (
context->VariableIsNotUsedAnymore(positive_ref))
continue;
812 if (
context->VariableWasRemoved(positive_ref))
continue;
814 if (num_locks_[
var] != 1)
continue;
815 if (locking_ct_index_[
var] == -1) {
817 "TODO dual: only one unspecified blocking constraint?");
823 if (
ct.constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
827 if (
ct.constraint_case() == ConstraintProto::kAtMostOne) {
828 context->UpdateRuleStats(
"TODO dual: tighten at most one");
832 if (
ct.constraint_case() != ConstraintProto::kBoolAnd) {
839 if (
ct.enforcement_literal().size() == 1 &&
841 const int enf =
ct.enforcement_literal(0);
843 :
context->MaxOf(positive_ref);
845 context->DomainOf(positive_ref)
847 context->deductions.ImpliedDomain(enf, positive_ref));
849 context->UpdateRuleStats(
"dual: fix variable");
850 if (!
context->SetLiteralToFalse(enf))
return false;
859 context->UpdateRuleStats(
"dual: fix variable");
860 if (!
context->IntersectDomainWith(positive_ref, implied)) {
871 processed[positive_ref] =
true;
872 context->UpdateRuleStats(
"dual: affine relation");
875 if (!
context->StoreAffineRelation(
890 if (
context->CanBeUsedAsLiteral(positive_ref)) {
894 processed[positive_ref] =
true;
895 context->UpdateRuleStats(
"dual: add implication");
897 context->UpdateNewConstraintsVariableUsage();
902 context->UpdateRuleStats(
"TODO dual: add implied bound");
907 if (
ct.constraint_case() == ConstraintProto::kLinear &&
908 ct.linear().vars().size() == 1 &&
909 ct.enforcement_literal().size() == 1 &&
911 const int var =
ct.linear().vars(0);
917 context->UpdateRuleStats(
"linear1: infeasible");
918 if (!
context->SetLiteralToFalse(
ct.enforcement_literal(0))) {
927 if (rhs == var_domain) {
928 context->UpdateRuleStats(
"linear1: always true");
938 context->UpdateRuleStats(
"dual: make encoding equiv");
939 const int64_t
value =
946 if (encoding_lit ==
NegatedRef(ref))
continue;
947 context->StoreBooleanEqualityRelation(encoding_lit,
950 if (encoding_lit == ref)
continue;
951 context->StoreBooleanEqualityRelation(encoding_lit, ref);
963 ConstraintProto* new_ct =
context->working_model->add_constraints();
964 new_ct->add_enforcement_literal(ref);
965 new_ct->mutable_linear()->add_vars(
var);
966 new_ct->mutable_linear()->add_coeffs(1);
968 context->UpdateNewConstraintsVariableUsage();
973 }
else if (complement.
IsFixed()) {
1001 if (equiv_ct_index_set.insert(
ct_index).second &&
1002 ct.constraint_case() == ConstraintProto::kLinear &&
1003 context->CanBeUsedAsLiteral(ref)) {
1004 TransformLinearWithSpecialBoolean(
ct, ref, &temp_data);
1005 const uint64_t
hash =
1006 fasthash64(temp_data.data(), temp_data.size() *
sizeof(int64_t),
1007 uint64_t{0xa5b85c5e198ed849});
1008 const auto [it, inserted] =
1009 equiv_modified_constraints.insert({
hash, {
ct_index, ref}});
1012 const auto [other_c_with_same_hash, other_ref] = it->second;
1013 CHECK_NE(other_c_with_same_hash,
ct_index);
1014 const auto& other_ct =
1015 context->working_model->constraints(other_c_with_same_hash);
1016 TransformLinearWithSpecialBoolean(other_ct, other_ref,
1018 if (temp_data == other_temp_data) {
1021 ++num_bool_in_near_duplicate_ct;
1024 context->StoreBooleanEqualityRelation(ref, other_ref);
1028 ++num_deleted_constraints_;
1038 if (!
ct.enforcement_literal().empty()) {
1039 if (
ct.constraint_case() == ConstraintProto::kLinear &&
1040 ct.linear().vars().size() == 1 &&
1041 ct.enforcement_literal().size() == 1 &&
1043 context->UpdateRuleStats(
"TODO dual: make linear1 equiv");
1046 "TODO dual: only one blocking enforced constraint?");
1049 context->UpdateRuleStats(
"TODO dual: only one blocking constraint?");
1053 if (
ct.enforcement_literal().size() != 1)
continue;
1068 int a =
ct.enforcement_literal(0);
1071 num_locks_[RefToIntegerVariable(
NegatedRef(
a))] == 1) {
1074 if (
ct.bool_and().literals().size() != 1)
continue;
1075 b =
ct.bool_and().literals(0);
1079 for (
const int lhs :
ct.bool_and().literals()) {
1081 num_locks_[RefToIntegerVariable(lhs)] == 1) {
1089 CHECK_EQ(num_locks_[RefToIntegerVariable(
NegatedRef(
a))], 1);
1093 context->StoreBooleanEqualityRelation(
a,
b);
1094 context->UpdateRuleStats(
"dual: enforced equivalence");
1097 if (num_bool_in_near_duplicate_ct) {
1099 "dual: equivalent Boolean in near-duplicate constraints",
1100 num_bool_in_near_duplicate_ct);
1103 VLOG(2) <<
"Num deleted constraints: " << num_deleted_constraints_;
1109 if (
context.ModelIsUnsat())
return;
1111 const CpModelProto& cp_model = *
context.working_model;
1112 const int num_vars = cp_model.variables().size();
1113 var_domination->
Reset(num_vars);
1115 for (
int var = 0;
var < num_vars; ++
var) {
1130 }
else if (r.
coeff == -1) {
1140 const int num_constraints = cp_model.constraints_size();
1141 std::vector<bool> c_is_free_to_increase(num_constraints);
1142 std::vector<bool> c_is_free_to_decrease(num_constraints);
1143 for (
int c = 0; c < num_constraints; ++c) {
1144 const ConstraintProto&
ct = cp_model.constraints(c);
1145 switch (
ct.constraint_case()) {
1146 case ConstraintProto::kBoolOr:
1148 case ConstraintProto::kBoolAnd:
1150 case ConstraintProto::kAtMostOne:
1152 case ConstraintProto::kExactlyOne:
1156 case ConstraintProto::kLinear: {
1158 const auto [min_activity, max_activity] =
1159 context.ComputeMinMaxActivity(
ct.linear());
1160 const bool domain_is_simple =
ct.linear().domain().size() == 2;
1161 const bool free_to_increase =
1162 domain_is_simple &&
ct.linear().domain(1) >= max_activity;
1163 const bool free_to_decrease =
1164 domain_is_simple &&
ct.linear().domain(0) <= min_activity;
1165 c_is_free_to_increase[c] = free_to_increase;
1166 c_is_free_to_decrease[c] = free_to_decrease;
1167 if (free_to_decrease && free_to_increase)
break;
1168 if (!free_to_increase && !free_to_decrease) {
1170 ct.linear().coeffs());
1177 for (
const int var :
context.ConstraintToVars(c)) {
1186 if (cp_model.has_objective()) {
1189 context.WriteObjectiveToProto();
1190 const auto [min_activity, max_activity] =
1191 context.ComputeMinMaxActivity(cp_model.objective());
1192 const auto& domain = cp_model.objective().domain();
1193 if (domain.empty() || (domain.size() == 2 && domain[0] <= min_activity)) {
1197 cp_model.objective().coeffs());
1204 std::vector<int> tmp;
1206 for (
int c = 0; c < num_constraints; ++c) {
1207 const ConstraintProto&
ct = cp_model.constraints(c);
1208 switch (
ct.constraint_case()) {
1209 case ConstraintProto::kBoolOr:
1211 ct.bool_or().literals(),
1214 case ConstraintProto::kBoolAnd:
1224 for (
const int ref :
ct.enforcement_literal()) {
1227 for (
const int ref :
ct.bool_and().literals()) {
1234 case ConstraintProto::kAtMostOne:
1236 ct.at_most_one().literals(),
1239 case ConstraintProto::kLinear: {
1240 const bool free_to_increase = c_is_free_to_increase[c];
1241 const bool free_to_decrease = c_is_free_to_decrease[c];
1242 if (free_to_decrease && free_to_increase)
break;
1243 if (free_to_increase) {
1246 ct.linear().coeffs());
1247 }
else if (free_to_decrease) {
1250 ct.linear().coeffs());
1252 if (!
ct.enforcement_literal().empty()) {
1254 {},
ct.enforcement_literal(), {});
1266 if (cp_model.has_objective()) {
1267 const auto [min_activity, max_activity] =
1268 context.ComputeMinMaxActivity(cp_model.objective());
1269 const auto& domain = cp_model.objective().domain();
1270 if (domain.empty() || (domain.size() == 2 && domain[0] <= min_activity)) {
1272 {}, cp_model.objective().vars(),
1273 cp_model.objective().coeffs());
1290 int64_t num_unconstrained_refs = 0;
1291 int64_t num_dominated_refs = 0;
1292 int64_t num_dominance_relations = 0;
1293 for (
int var = 0;
var < num_vars; ++
var) {
1298 num_unconstrained_refs++;
1300 num_dominated_refs++;
1301 num_dominance_relations +=
1306 if (num_unconstrained_refs == 0 && num_dominated_refs == 0)
return;
1307 VLOG(1) <<
"Dominance:" <<
" num_unconstrained_refs="
1308 << num_unconstrained_refs
1309 <<
" num_dominated_refs=" << num_dominated_refs
1310 <<
" num_dominance_relations=" << num_dominance_relations;
1316 if (
context.ModelIsUnsat())
return;
1317 const CpModelProto& cp_model = *
context.working_model;
1318 const int num_vars = cp_model.variables().size();
1319 dual_bound_strengthening->
Reset(num_vars);
1321 for (
int var = 0;
var < num_vars; ++
var) {
1337 const int num_constraints = cp_model.constraints_size();
1338 for (
int c = 0; c < num_constraints; ++c) {
1339 const ConstraintProto&
ct = cp_model.constraints(c);
1341 switch (
ct.constraint_case()) {
1342 case ConstraintProto::kBoolOr:
1345 case ConstraintProto::kBoolAnd:
1348 case ConstraintProto::kAtMostOne:
1352 case ConstraintProto::kExactlyOne:
1353 dual_bound_strengthening->
CannotMove(
ct.exactly_one().literals(), c);
1355 case ConstraintProto::kLinear: {
1357 const auto [min_activity, max_activity] =
1358 context.ComputeMinMaxActivity(
ct.linear());
1360 false,
context,
ct.linear(), min_activity, max_activity, c);
1373 if (cp_model.has_objective()) {
1376 context.WriteObjectiveToProto();
1377 const auto [min_activity, max_activity] =
1378 context.ComputeMinMaxActivity(cp_model.objective());
1380 true,
context, cp_model.objective(), min_activity, max_activity);
1386bool ProcessAtMostOne(
1387 absl::Span<const int> literals,
const std::string&
message,
1388 const VarDomination& var_domination,
1391 for (
const int ref : literals) {
1394 for (
const int ref : literals) {
1395 if (
context->IsFixed(ref))
continue;
1397 const auto dominating_ivars = var_domination.DominatingVariables(ref);
1398 for (
const IntegerVariable ivar : dominating_ivars) {
1399 if (!(*in_constraints)[ivar])
continue;
1406 if (!
context->SetLiteralToFalse(ref))
return false;
1410 for (
const int ref : literals) {
1420 const CpModelProto& cp_model = *
context->working_model;
1421 const int num_vars = cp_model.variables_size();
1424 bool work_to_do =
false;
1425 for (
int var = 0;
var < num_vars; ++
var) {
1433 if (!work_to_do)
return true;
1435 const int64_t saved_num_operations =
context->num_presolve_operations;
1444 num_vars * 2, std::numeric_limits<int64_t>::min());
1454 absl::flat_hash_set<std::pair<int, int>> implications;
1455 const int num_constraints = cp_model.constraints_size();
1456 for (
int c = 0; c < num_constraints; ++c) {
1457 const ConstraintProto&
ct = cp_model.constraints(c);
1459 if (
ct.constraint_case() == ConstraintProto::kBoolAnd) {
1460 if (
ct.enforcement_literal().size() != 1)
continue;
1461 const int a =
ct.enforcement_literal(0);
1463 for (
const int b :
ct.bool_and().literals()) {
1465 implications.insert({
a,
b});
1469 for (
const IntegerVariable ivar :
1473 context->UpdateRuleStats(
"domination: in implication");
1474 if (!
context->SetLiteralToFalse(
a))
return false;
1481 for (
const IntegerVariable ivar :
1485 context->UpdateRuleStats(
"domination: in implication");
1486 if (!
context->SetLiteralToTrue(
b))
return false;
1494 if (!
ct.enforcement_literal().empty())
continue;
1500 const auto add_implications = [&implications](absl::Span<const int> lits) {
1501 if (lits.size() > 3)
return;
1502 for (
int i = 0;
i < lits.size(); ++
i) {
1503 for (
int j = 0; j < lits.size(); ++j) {
1504 if (
i == j)
continue;
1505 implications.insert({lits[
i],
NegatedRef(lits[j])});
1509 if (
ct.constraint_case() == ConstraintProto::kAtMostOne) {
1510 add_implications(
ct.at_most_one().literals());
1511 if (!ProcessAtMostOne(
ct.at_most_one().literals(),
1512 "domination: in at most one", var_domination,
1516 }
else if (
ct.constraint_case() == ConstraintProto::kExactlyOne) {
1517 add_implications(
ct.exactly_one().literals());
1518 if (!ProcessAtMostOne(
ct.exactly_one().literals(),
1519 "domination: in exactly one", var_domination,
1525 if (
ct.constraint_case() != ConstraintProto::kLinear)
continue;
1527 int num_dominated = 0;
1528 for (
const int var :
context->ConstraintToVars(c)) {
1534 if (num_dominated == 0)
continue;
1537 int64_t min_activity = 0;
1538 int64_t max_activity = 0;
1539 const int num_terms =
ct.linear().vars_size();
1540 for (
int i = 0;
i < num_terms; ++
i) {
1541 int ref =
ct.linear().vars(
i);
1542 int64_t coeff =
ct.linear().coeffs(
i);
1547 const int64_t min_term = coeff *
context->MinOf(ref);
1548 const int64_t max_term = coeff *
context->MaxOf(ref);
1549 min_activity += min_term;
1550 max_activity += max_term;
1552 var_lb_to_ub_diff[ivar] = max_term - min_term;
1553 var_lb_to_ub_diff[
NegationOf(ivar)] = min_term - max_term;
1555 const int64_t rhs_lb =
ct.linear().domain(0);
1556 const int64_t rhs_ub =
ct.linear().domain(
ct.linear().domain_size() - 1);
1557 if (max_activity < rhs_lb || min_activity > rhs_ub) {
1558 return context->NotifyThatModelIsUnsat(
"linear equation unsat.");
1563 const auto get_delta = [
context, &var_lb_to_ub_diff](
1565 absl::Span<const IntegerVariable> vars) {
1567 for (
const IntegerVariable
var : vars) {
1571 .NumIntervals() != 1) {
1575 delta += std::max(int64_t{0}, var_lb_to_ub_diff[
var]);
1577 delta += std::max(int64_t{0}, -var_lb_to_ub_diff[
var]);
1584 for (
int i = 0;
i < num_terms; ++
i) {
1585 const int ref =
ct.linear().vars(
i);
1586 const int64_t coeff =
ct.linear().coeffs(
i);
1588 if (
context->IsFixed(ref))
continue;
1591 const bool only_lb = max_activity <= rhs_ub;
1592 const bool only_ub = min_activity >= rhs_lb;
1593 if (only_lb || only_ub) {
1595 const int current_ref = (coeff > 0) == only_lb ? ref :
NegatedRef(ref);
1596 const int64_t shifted_rhs =
1597 only_lb ? rhs_lb - min_activity : max_activity - rhs_ub;
1598 const IntegerVariable current_ivar =
1600 can_freely_decrease_count[
NegationOf(current_ivar)]++;
1602 const int64_t
delta = get_delta(
1607 const int64_t slack = shifted_rhs -
delta;
1608 const int64_t current_lb =
context->MinOf(current_ref);
1618 const int64_t min_delta =
1620 can_freely_decrease_until[current_ivar] = std::max(
1621 can_freely_decrease_until[current_ivar], current_lb + min_delta);
1622 can_freely_decrease_count[current_ivar]++;
1626 for (
const int current_ref : {ref,
NegatedRef(ref)}) {
1627 const absl::Span<const IntegerVariable> dominated_by =
1629 if (dominated_by.empty())
continue;
1631 const bool ub_side = (coeff > 0) == (current_ref == ref);
1633 if (max_activity <= rhs_ub)
continue;
1635 if (min_activity >= rhs_lb)
continue;
1637 const int64_t slack =
1638 ub_side ? rhs_ub - min_activity : max_activity - rhs_lb;
1642 const int64_t
delta = get_delta(ub_side, dominated_by);
1643 const int64_t lb =
context->MinOf(current_ref);
1645 context->UpdateRuleStats(
"domination: fixed to lb.");
1646 if (!
context->IntersectDomainWith(current_ref,
Domain(lb))) {
1651 const IntegerVariable current_var =
1654 CHECK_GE(var_lb_to_ub_diff[current_var], 0);
1655 max_activity -= var_lb_to_ub_diff[current_var];
1657 CHECK_LE(var_lb_to_ub_diff[current_var], 0);
1658 min_activity -= var_lb_to_ub_diff[current_var];
1660 var_lb_to_ub_diff[current_var] = 0;
1661 var_lb_to_ub_diff[
NegationOf(current_var)] = 0;
1668 int64_t new_ub = lb + diff.value();
1669 if (new_ub < context->MaxOf(current_ref)) {
1673 new_ub =
context->DomainOf(current_ref).ValueAtOrAfter(new_ub);
1675 if (new_ub < context->MaxOf(current_ref)) {
1676 context->UpdateRuleStats(
"domination: reduced ub.");
1677 if (!
context->IntersectDomainWith(current_ref,
Domain(lb, new_ub))) {
1682 const IntegerVariable current_var =
1685 CHECK_GE(var_lb_to_ub_diff[current_var], 0);
1686 max_activity -= var_lb_to_ub_diff[current_var];
1688 CHECK_LE(var_lb_to_ub_diff[current_var], 0);
1689 min_activity -= var_lb_to_ub_diff[current_var];
1693 var_lb_to_ub_diff[current_var] = new_diff;
1694 var_lb_to_ub_diff[
NegationOf(current_var)] = -new_diff;
1695 max_activity += new_diff;
1697 var_lb_to_ub_diff[current_var] = -new_diff;
1698 var_lb_to_ub_diff[
NegationOf(current_var)] = +new_diff;
1699 min_activity -= new_diff;
1706 for (
const int ref :
ct.linear().vars()) {
1708 var_lb_to_ub_diff[ivar] = 0;
1729 2 * num_vars,
false);
1730 for (
int positive_ref = 0; positive_ref < num_vars; ++positive_ref) {
1731 if (
context->IsFixed(positive_ref))
continue;
1732 if (
context->VariableIsNotUsedAnymore(positive_ref))
continue;
1733 if (
context->VariableWasRemoved(positive_ref))
continue;
1737 if (!
context->ObjectiveDomainIsConstraining()) {
1738 const int64_t obj_coeff =
context->ObjectiveCoeff(positive_ref);
1739 if (obj_coeff > 0) {
1742 }
else if (obj_coeff < 0) {
1748 for (
const int ref : {positive_ref,
NegatedRef(positive_ref)}) {
1751 if (can_freely_decrease_count[
var] ==
1752 context->VarToConstraints(positive_ref).size()) {
1754 int64_t lb = can_freely_decrease_until[
var];
1755 lb =
context->DomainOf(ref).ValueAtOrAfter(lb);
1756 if (lb < context->MaxOf(ref)) {
1763 for (
const IntegerVariable dom :
1770 if (increase_is_forbidden[dom] ||
1782 increase_is_forbidden[
var] =
true;
1784 "domination: dual strenghtening using dominance");
1785 if (!
context->IntersectDomainWith(
1799 if (!
context->CanBeUsedAsLiteral(positive_ref))
continue;
1800 for (
const IntegerVariable dom :
1802 if (increase_is_forbidden[dom])
continue;
1804 if (
context->IsFixed(dom_ref))
continue;
1805 if (
context->VariableIsNotUsedAnymore(dom_ref))
continue;
1806 if (
context->VariableWasRemoved(dom_ref))
continue;
1807 if (!
context->CanBeUsedAsLiteral(dom_ref))
continue;
1808 if (implications.contains({ref, dom_ref}))
continue;
1811 context->AddImplication(ref, dom_ref);
1812 context->UpdateNewConstraintsVariableUsage();
1813 implications.insert({ref, dom_ref});
1817 increase_is_forbidden[
var] =
true;
1818 increase_is_forbidden[
NegationOf(dom)] =
true;
1823 if (num_added > 0) {
1824 VLOG(1) <<
"Added " << num_added <<
" domination implications.";
1825 context->UpdateRuleStats(
"domination: added implications", num_added);
1829 if (saved_num_operations ==
context->num_presolve_operations) {
1830 context->UpdateRuleStats(
"TODO domination: unexploited dominations");
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
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.
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)
void assign(size_type n, const value_type &val)
value_type * data()
– Pass-through methods to STL vector ----------------------------------—
void resize(size_type new_size)
DecisionBuilder *const phase
The decision builder we are going to use in this dive.
GurobiMPCallbackContext * context
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)
IntType CeilOfRatio(IntType numerator, IntType denominator)
std::vector< IntegerVariable > NegationOf(const std::vector< 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)
Fractional coeff_magnitude
std::optional< int64_t > end