23 num_vars_with_negation_ = 2 * num_variables;
24 partition_ = absl::make_unique<DynamicPartition>(num_vars_with_negation_);
26 can_freely_decrease_.
assign(num_vars_with_negation_,
true);
28 shared_buffer_.clear();
29 initial_candidates_.
assign(num_vars_with_negation_, IntegerVariableSpan());
32 dominating_vars_.
assign(num_vars_with_negation_, IntegerVariableSpan());
34 ct_index_for_signature_ = 0;
35 block_down_signatures_.
assign(num_vars_with_negation_, 0);
38 void VarDomination::RefinePartition(std::vector<int>* vars) {
39 if (vars->empty())
return;
40 partition_->Refine(*vars);
41 for (
int&
var : *vars) {
42 const IntegerVariable wrapped(
var);
43 can_freely_decrease_[wrapped] =
false;
44 can_freely_decrease_[
NegationOf(wrapped)] =
false;
47 partition_->Refine(*vars);
51 if (phase_ != 0)
return;
53 for (
const int ref : refs) {
56 RefinePartition(&tmp_vars_);
61 absl::Span<const int64> coeffs) {
62 if (phase_ != 0)
return;
63 FillTempRanks(
false, {}, refs,
66 for (
int i = 0; i < tmp_ranks_.size(); ++i) {
67 if (i > 0 && tmp_ranks_[i].rank != tmp_ranks_[i - 1].rank) {
68 RefinePartition(&tmp_vars_);
71 tmp_vars_.push_back(tmp_ranks_[i].
var.value());
73 RefinePartition(&tmp_vars_);
78 void VarDomination::ProcessTempRanks() {
82 ++ct_index_for_signature_;
83 for (IntegerVariableWithRank& entry : tmp_ranks_) {
84 can_freely_decrease_[entry.var] =
false;
85 block_down_signatures_[entry.var] |=
uint64{1}
86 << (ct_index_for_signature_ % 64);
87 entry.part = partition_->PartOf(entry.var.value());
90 tmp_ranks_.begin(), tmp_ranks_.end(),
91 [](
const IntegerVariableWithRank&
a,
const IntegerVariableWithRank&
b) {
92 return a.part < b.part;
95 for (
int i = 1; i < tmp_ranks_.size(); ++i) {
96 if (tmp_ranks_[i].part != tmp_ranks_[start].part) {
97 Initialize({&tmp_ranks_[start],
static_cast<size_t>(i - start)});
101 if (start < tmp_ranks_.size()) {
102 Initialize({&tmp_ranks_[start], tmp_ranks_.size() - start});
104 }
else if (phase_ == 1) {
105 FilterUsingTempRanks();
108 CheckUsingTempRanks();
113 absl::Span<const int> enforcements, absl::Span<const int> refs,
114 absl::Span<const int64> coeffs) {
115 FillTempRanks(
false, enforcements, refs, coeffs);
120 absl::Span<const int> enforcements, absl::Span<const int> refs,
121 absl::Span<const int64> coeffs) {
122 FillTempRanks(
true, enforcements, refs, coeffs);
126 void VarDomination::MakeRankEqualToStartOfPart(
127 absl::Span<IntegerVariableWithRank> span) {
128 const int size = span.size();
130 int previous_value = 0;
131 for (
int i = 0; i < size; ++i) {
133 if (
value != previous_value) {
134 previous_value =
value;
137 span[i].rank = start;
141 void VarDomination::Initialize(absl::Span<IntegerVariableWithRank> span) {
144 MakeRankEqualToStartOfPart(span);
146 const int future_start = shared_buffer_.size();
147 int first_start = -1;
151 const int kSizeThreshold = 1000;
152 const int size = span.size();
153 for (
int i =
std::max(0, size - kSizeThreshold); i < size; ++i) {
154 const IntegerVariableWithRank entry = span[i];
155 const int num_candidates = size - entry.rank;
156 if (num_candidates >= kSizeThreshold)
continue;
159 int size_threshold = kSizeThreshold;
162 const int var_part = partition_->PartOf(entry.var.value());
163 const int part_size = partition_->SizeOfPart(var_part);
164 size_threshold =
std::min(size_threshold, part_size);
167 const int current_num_candidates = initial_candidates_[entry.var].
size;
168 if (current_num_candidates != 0) {
169 size_threshold =
std::min(size_threshold, current_num_candidates);
172 if (num_candidates < size_threshold) {
173 if (first_start == -1) first_start = entry.rank;
174 initial_candidates_[entry.var] = {
175 future_start - first_start +
static_cast<int>(entry.rank),
181 if (first_start == -1)
return;
182 for (
int i = first_start; i < size; ++i) {
183 shared_buffer_.push_back(span[i].
var);
199 const int kMaxInitialSize = 50;
200 std::vector<IntegerVariable> cropped_lists;
205 for (IntegerVariable
var(0);
var < num_vars_with_negation_; ++
var) {
206 if (can_freely_decrease_[
var])
continue;
207 const int part = partition_->PartOf(
var.value());
208 const int part_size = partition_->SizeOfPart(part);
210 const int start = buffer_.size();
213 const uint64 var_sig = block_down_signatures_[
var];
215 const int stored_size = initial_candidates_[
var].
size;
216 if (stored_size == 0 || part_size < stored_size) {
220 for (
const int value : partition_->ElementsInPart(part)) {
221 const IntegerVariable c = IntegerVariable(
value);
226 if (++num_tested > 1000) {
227 is_cropped[
var] =
true;
228 cropped_lists.push_back(
var);
229 int extra = new_size;
230 while (extra < kMaxInitialSize) {
237 if (can_freely_decrease_[
NegationOf(c)])
continue;
238 if (var_sig & ~block_down_signatures_[c])
continue;
239 if (block_down_signatures_[
NegationOf(c)] & ~not_var_sig)
continue;
241 buffer_.push_back(c);
245 if (new_size > kMaxInitialSize) {
246 is_cropped[
var] =
true;
247 cropped_lists.push_back(
var);
256 for (
const IntegerVariable c : InitialDominatingCandidates(
var)) {
258 if (can_freely_decrease_[
NegationOf(c)])
continue;
259 if (partition_->PartOf(c.value()) != part)
continue;
260 if (var_sig & ~block_down_signatures_[c])
continue;
261 if (block_down_signatures_[
NegationOf(c)] & ~not_var_sig)
continue;
263 buffer_.push_back(c);
267 if (new_size > kMaxInitialSize) {
268 is_cropped[
var] =
true;
269 cropped_lists.push_back(
var);
275 dominating_vars_[
var] = {start, new_size};
282 for (
const IntegerVariable
var : cropped_lists) {
283 if (kMaxInitialSize / 2 < dominating_vars_[
var].size) {
284 dominating_vars_[
var].
size = kMaxInitialSize / 2;
287 for (IntegerVariable
var(0);
var < num_vars_with_negation_; ++
var) {
290 IntegerVariableSpan& s = dominating_vars_[
NegationOf(dom)];
291 if (s.size >= kMaxInitialSize)
continue;
300 for (
const IntegerVariable
var : cropped_lists) {
301 if (!is_cropped[
var])
continue;
302 IntegerVariableSpan& s = dominating_vars_[
var];
303 std::sort(&buffer_[s.start], &buffer_[s.start + s.size]);
304 const auto p = std::unique(&buffer_[s.start], &buffer_[s.start + s.size]);
305 s.size = p - &buffer_[s.start];
309 VLOG(1) <<
"Num initial list that where cropped: " << cropped_lists.size();
310 VLOG(1) <<
"Shared buffer size: " << shared_buffer_.size();
311 VLOG(1) <<
"Buffer size: " << buffer_.size();
321 shared_buffer_.clear();
322 initial_candidates_.
assign(num_vars_with_negation_, IntegerVariableSpan());
325 for (IntegerVariable
var(0);
var < num_vars_with_negation_; ++
var) {
333 for (IntegerVariable
var(0);
var < num_vars_with_negation_; ++
var) {
334 initial_candidates_[
var].start = start;
335 start += initial_candidates_[
var].
size;
336 initial_candidates_[
var].
size = 0;
338 shared_buffer_.resize(start);
341 for (IntegerVariable
var(0);
var < num_vars_with_negation_; ++
var) {
343 IntegerVariableSpan& span = initial_candidates_[
NegationOf(dom)];
350 tmp_var_to_rank_.
resize(num_vars_with_negation_, -1);
351 for (IntegerVariable
var(0);
var < num_vars_with_negation_; ++
var) {
352 for (
const IntegerVariable dom : InitialDominatingCandidates(
var)) {
353 tmp_var_to_rank_[dom] = 1;
357 IntegerVariableSpan& span = dominating_vars_[
var];
359 if (tmp_var_to_rank_[dom] != 1) {
363 buffer_[span.start + new_size++] = dom;
365 span.size = new_size;
367 for (
const IntegerVariable dom : InitialDominatingCandidates(
var)) {
368 tmp_var_to_rank_[dom] = -1;
372 VLOG(1) <<
"Transpose removed " << num_removed;
377 void VarDomination::FillTempRanks(
bool reverse_references,
378 absl::Span<const int> enforcements,
379 absl::Span<const int> refs,
380 absl::Span<const int64> coeffs) {
382 if (coeffs.empty()) {
384 for (
const int ref : refs) {
385 const IntegerVariable
var =
387 tmp_ranks_.push_back({
var, 0, 0});
391 for (
int i = 0; i < refs.size(); ++i) {
392 if (coeffs[i] == 0)
continue;
394 reverse_references ?
NegatedRef(refs[i]) : refs[i]);
396 tmp_ranks_.push_back({
var, 0, coeffs[i]});
401 std::sort(tmp_ranks_.begin(), tmp_ranks_.end());
402 MakeRankEqualToStartOfPart({&tmp_ranks_[0], tmp_ranks_.size()});
408 const int enforcement_rank = tmp_ranks_.size();
409 for (
const int ref : enforcements) {
410 tmp_ranks_.push_back(
417 void VarDomination::FilterUsingTempRanks() {
419 tmp_var_to_rank_.
resize(num_vars_with_negation_, -1);
420 for (
const IntegerVariableWithRank entry : tmp_ranks_) {
421 tmp_var_to_rank_[entry.var] = entry.rank;
425 for (
const IntegerVariableWithRank entry : tmp_ranks_) {
433 IntegerVariableSpan& span = dominating_vars_[entry.var];
434 if (span.size == 0)
continue;
437 if (tmp_var_to_rank_[candidate] < entry.rank)
continue;
438 buffer_[span.start + new_size++] = candidate;
440 span.size = new_size;
445 for (
const IntegerVariableWithRank entry : tmp_ranks_) {
446 tmp_var_to_rank_[entry.var] = -1;
451 void VarDomination::CheckUsingTempRanks() {
452 tmp_var_to_rank_.
resize(num_vars_with_negation_, -1);
453 for (
const IntegerVariableWithRank entry : tmp_ranks_) {
454 tmp_var_to_rank_[entry.var] = entry.rank;
458 for (IntegerVariable
var(0);
var < num_vars_with_negation_; ++
var) {
459 const int var_rank = tmp_var_to_rank_[
var];
460 const int negated_var_rank = tmp_var_to_rank_[
NegationOf(
var)];
466 CHECK_LE(var_rank, tmp_var_to_rank_[dom]);
471 for (
const IntegerVariableWithRank entry : tmp_ranks_) {
472 tmp_var_to_rank_[entry.var] = -1;
481 return can_freely_decrease_[
var];
484 absl::Span<const IntegerVariable> VarDomination::InitialDominatingCandidates(
485 IntegerVariable
var)
const {
486 const IntegerVariableSpan span = initial_candidates_[
var];
487 if (span.size == 0)
return absl::Span<const IntegerVariable>();
488 return absl::Span<const IntegerVariable>(&shared_buffer_[span.start],
498 IntegerVariable
var)
const {
499 const IntegerVariableSpan span = dominating_vars_[
var];
500 if (span.size == 0)
return absl::Span<const IntegerVariable>();
501 return absl::Span<const IntegerVariable>(&buffer_[span.start], span.size);
517 for (
const int ref : refs) {
518 const IntegerVariable
var = RefToIntegerVariable(ref);
524 for (
const int ref : refs) {
525 const IntegerVariable
var = RefToIntegerVariable(ref);
531 for (
const int ref : refs) {
532 const IntegerVariable
var = RefToIntegerVariable(ref);
538 template <
typename LinearProto>
541 const LinearProto& linear,
int64 min_activity,
int64 max_activity) {
542 const int64 lb_limit = linear.domain(linear.domain_size() - 2);
543 const int64 ub_limit = linear.domain(1);
544 const int num_terms = linear.vars_size();
545 for (
int i = 0; i < num_terms; ++i) {
546 int ref = linear.vars(i);
547 int64 coeff = linear.coeffs(i);
555 const int64 term_diff = max_term - min_term;
556 const IntegerVariable
var = RefToIntegerVariable(ref);
559 if (min_activity < lb_limit) {
560 if (min_activity + term_diff < lb_limit) {
563 const IntegerValue slack(lb_limit - min_activity);
564 const IntegerValue var_diff =
565 CeilRatio(IntegerValue(slack), IntegerValue(coeff));
566 can_freely_decrease_until_[
var] =
568 IntegerValue(
context.MinOf(ref)) + var_diff);
579 if (max_activity > ub_limit) {
580 if (max_activity - term_diff > ub_limit) {
583 const IntegerValue slack(max_activity - ub_limit);
584 const IntegerValue var_diff =
585 CeilRatio(IntegerValue(slack), IntegerValue(coeff));
588 -IntegerValue(
context.MaxOf(ref)) + var_diff);
595 const CpModelProto& cp_model = *
context->working_model;
596 const int num_vars = cp_model.variables_size();
597 for (
int var = 0;
var < num_vars; ++
var) {
603 if (ub_limit == lb) {
604 context->UpdateRuleStats(
"dual: fix variable");
611 const int64 lb_limit =
613 if (lb_limit == ub) {
614 context->UpdateRuleStats(
"dual: fix variable");
621 if (lb_limit > ub_limit) {
631 context->UpdateRuleStats(
"dual: fix variable with multiple choices");
639 if (lb_limit > lb || ub_limit < ub) {
650 context->UpdateRuleStats(
"dual: reduced domain");
660 template <
typename LinearExprProto>
662 const LinearExprProto&
proto,
int64* min_activity,
663 int64* max_activity) {
666 const int num_vars =
proto.vars().size();
667 for (
int i = 0; i < num_vars; ++i) {
680 const CpModelProto& cp_model = *
context.working_model;
681 const int num_vars = cp_model.variables().size();
682 var_domination->
Reset(num_vars);
683 dual_bound_strengthening->
Reset(num_vars);
688 for (
int var = 0;
var < num_vars; ++
var) {
700 }
else if (r.
coeff == -1) {
720 std::vector<int> tmp;
721 const int num_constraints = cp_model.constraints_size();
723 for (
int c = 0; c < num_constraints; ++c) {
724 const ConstraintProto&
ct = cp_model.constraints(c);
728 switch (
ct.constraint_case()) {
729 case ConstraintProto::kBoolOr:
734 ct.bool_or().literals(),
737 case ConstraintProto::kBoolAnd:
751 for (
const int ref :
ct.enforcement_literal()) {
754 for (
const int ref :
ct.bool_and().literals()) {
761 case ConstraintProto::kAtMostOne:
764 ct.at_most_one().literals());
767 ct.at_most_one().literals(),
770 case ConstraintProto::kLinear: {
771 FillMinMaxActivity(
context,
ct.linear(), &min_activity,
775 false,
context,
ct.linear(), min_activity, max_activity);
777 const bool domain_is_simple =
ct.linear().domain().size() == 2;
778 const bool free_to_increase =
779 domain_is_simple &&
ct.linear().domain(1) >= max_activity;
780 const bool free_to_decrease =
781 domain_is_simple &&
ct.linear().domain(0) <= min_activity;
782 if (free_to_decrease && free_to_increase)
break;
783 if (free_to_increase) {
786 ct.linear().coeffs());
787 }
else if (free_to_decrease) {
790 ct.linear().coeffs());
793 if (!
ct.enforcement_literal().empty()) {
795 {},
ct.enforcement_literal(), {});
798 ct.linear().coeffs());
808 for (
const int var :
context.ConstraintToVars(c)) {
817 if (cp_model.has_objective()) {
821 FillMinMaxActivity(
context, cp_model.objective(), &min_activity,
824 true,
context, cp_model.objective(), min_activity, max_activity);
825 const auto& domain = cp_model.objective().domain();
826 if (domain.empty() || (domain.size() == 2 && domain[0] <= min_activity)) {
828 {}, cp_model.objective().vars(),
829 cp_model.objective().coeffs());
832 cp_model.objective().coeffs());
841 int64 num_unconstrained_refs = 0;
842 int64 num_dominated_refs = 0;
843 int64 num_dominance_relations = 0;
844 for (
int var = 0;
var < num_vars; ++
var) {
849 num_unconstrained_refs++;
851 num_dominated_refs++;
852 num_dominance_relations +=
857 if (num_unconstrained_refs == 0 && num_dominated_refs == 0)
return;
858 VLOG(1) <<
"Dominance:"
859 <<
" num_unconstrained_refs=" << num_unconstrained_refs
860 <<
" num_dominated_refs=" << num_dominated_refs
861 <<
" num_dominance_relations=" << num_dominance_relations;
866 const CpModelProto& cp_model = *
context->working_model;
867 const int num_vars = cp_model.variables_size();
870 bool work_to_do =
false;
871 for (
int var = 0;
var < num_vars; ++
var) {
879 if (!work_to_do)
return true;
884 const int num_constraints = cp_model.constraints_size();
885 for (
int c = 0; c < num_constraints; ++c) {
886 const ConstraintProto&
ct = cp_model.constraints(c);
888 if (
ct.constraint_case() == ConstraintProto::kBoolAnd) {
889 if (
ct.enforcement_literal().size() != 1)
continue;
890 const int a =
ct.enforcement_literal(0);
892 for (
const int b :
ct.bool_and().literals()) {
896 for (
const IntegerVariable ivar :
900 context->UpdateRuleStats(
"domination: in implication");
901 if (!
context->SetLiteralToFalse(
a))
return false;
908 for (
const IntegerVariable ivar :
912 context->UpdateRuleStats(
"domination: in implication");
913 if (!
context->SetLiteralToTrue(
b))
return false;
921 if (!
ct.enforcement_literal().empty())
continue;
923 if (
ct.constraint_case() == ConstraintProto::kAtMostOne) {
924 for (
const int ref :
ct.at_most_one().literals()) {
927 for (
const int ref :
ct.at_most_one().literals()) {
928 if (
context->IsFixed(ref))
continue;
931 if (dominating_ivars.empty())
continue;
932 for (
const IntegerVariable ivar : dominating_ivars) {
933 if (!in_constraints[ivar])
continue;
939 context->UpdateRuleStats(
"domination: in at most one");
940 if (!
context->SetLiteralToFalse(ref))
return false;
944 for (
const int ref :
ct.at_most_one().literals()) {
949 if (
ct.constraint_case() != ConstraintProto::kLinear)
continue;
951 int num_dominated = 0;
952 for (
const int var :
context->ConstraintToVars(c)) {
958 if (num_dominated == 0)
continue;
961 int64 min_activity = 0;
962 int64 max_activity = 0;
963 const int num_terms =
ct.linear().vars_size();
964 for (
int i = 0; i < num_terms; ++i) {
965 int ref =
ct.linear().vars(i);
966 int64 coeff =
ct.linear().coeffs(i);
973 min_activity += min_term;
974 max_activity += max_term;
976 var_lb_to_ub_diff[ivar] = max_term - min_term;
977 var_lb_to_ub_diff[
NegationOf(ivar)] = min_term - max_term;
979 const int64 rhs_lb =
ct.linear().domain(0);
980 const int64 rhs_ub =
ct.linear().domain(
ct.linear().domain_size() - 1);
981 if (max_activity < rhs_lb || min_activity > rhs_ub) {
982 return context->NotifyThatModelIsUnsat(
"linear equation unsat.");
986 for (
int i = 0; i < num_terms; ++i) {
987 const int ref =
ct.linear().vars(i);
988 const int64 coeff =
ct.linear().coeffs(i);
990 if (
context->IsFixed(ref))
continue;
992 for (
const int current_ref : {ref,
NegatedRef(ref)}) {
993 const absl::Span<const IntegerVariable> dominated_by =
995 if (dominated_by.empty())
continue;
997 const bool ub_side = (coeff > 0) == (current_ref == ref);
999 if (max_activity <= rhs_ub)
continue;
1001 if (min_activity >= rhs_lb)
continue;
1004 ub_side ? rhs_ub - min_activity : max_activity - rhs_lb;
1009 for (
const IntegerVariable ivar : dominated_by) {
1019 context->UpdateRuleStats(
"domination: fixed to lb.");
1020 if (!
context->IntersectDomainWith(current_ref,
Domain(lb))) {
1025 const IntegerVariable current_var =
1028 CHECK_GE(var_lb_to_ub_diff[current_var], 0);
1029 max_activity -= var_lb_to_ub_diff[current_var];
1031 CHECK_LE(var_lb_to_ub_diff[current_var], 0);
1032 min_activity -= var_lb_to_ub_diff[current_var];
1034 var_lb_to_ub_diff[current_var] = 0;
1035 var_lb_to_ub_diff[
NegationOf(current_var)] = 0;
1042 const int64 new_ub = lb + diff.value();
1043 if (new_ub < context->MaxOf(current_ref)) {
1044 context->UpdateRuleStats(
"domination: reduced ub.");
1045 if (!
context->IntersectDomainWith(current_ref,
Domain(lb, new_ub))) {
1050 const IntegerVariable current_var =
1053 CHECK_GE(var_lb_to_ub_diff[current_var], 0);
1054 max_activity -= var_lb_to_ub_diff[current_var];
1056 CHECK_LE(var_lb_to_ub_diff[current_var], 0);
1057 min_activity -= var_lb_to_ub_diff[current_var];
1061 var_lb_to_ub_diff[current_var] = new_diff;
1062 var_lb_to_ub_diff[
NegationOf(current_var)] = -new_diff;
1063 max_activity += new_diff;
1065 var_lb_to_ub_diff[current_var] = -new_diff;
1066 var_lb_to_ub_diff[
NegationOf(current_var)] = +new_diff;
1067 min_activity -= new_diff;
1074 for (
const int ref :
ct.linear().vars()) {
1076 var_lb_to_ub_diff[ivar] = 0;
1094 for (
int positive_ref = 0; positive_ref < num_vars; ++positive_ref) {
1095 if (
context->IsFixed(positive_ref))
continue;
1096 if (!
context->CanBeUsedAsLiteral(positive_ref))
continue;
1097 for (
const int ref : {positive_ref,
NegatedRef(positive_ref)}) {
1100 for (
const IntegerVariable dom :
1102 if (increase_is_forbidden[dom])
continue;
1104 if (
context->IsFixed(dom_ref))
continue;
1105 if (!
context->CanBeUsedAsLiteral(dom_ref))
continue;
1107 context->AddImplication(ref, dom_ref);
1110 increase_is_forbidden[
var] =
true;
1111 increase_is_forbidden[
NegationOf(dom)] =
true;
1115 if (num_added > 0) {
1116 VLOG(1) <<
"Added " << num_added <<
" domination implications.";
1117 context->UpdateNewConstraintsVariableUsage();
1118 context->UpdateRuleStats(
"domination: added implications", num_added);