27 #include "absl/strings/str_cat.h"
28 #include "absl/types/span.h"
34 #include "ortools/sat/cp_model.pb.h"
47 num_vars_with_negation_ = 2 * num_variables;
49 std::make_unique<SimpleDynamicPartition>(num_vars_with_negation_);
51 can_freely_decrease_.
assign(num_vars_with_negation_,
true);
53 shared_buffer_.clear();
54 initial_candidates_.
assign(num_vars_with_negation_, IntegerVariableSpan());
57 dominating_vars_.
assign(num_vars_with_negation_, IntegerVariableSpan());
59 ct_index_for_signature_ = 0;
60 block_down_signatures_.
assign(num_vars_with_negation_, 0);
63 void VarDomination::RefinePartition(std::vector<int>* vars) {
64 if (vars->empty())
return;
65 partition_->Refine(*vars);
66 for (
int&
var : *vars) {
67 const IntegerVariable wrapped(
var);
68 can_freely_decrease_[wrapped] =
false;
69 can_freely_decrease_[
NegationOf(wrapped)] =
false;
72 partition_->Refine(*vars);
76 if (phase_ != 0)
return;
78 for (
const int ref : refs) {
81 RefinePartition(&tmp_vars_);
86 absl::Span<const int64_t> coeffs) {
87 if (phase_ != 0)
return;
88 FillTempRanks(
false, {}, refs,
91 for (
int i = 0; i < tmp_ranks_.size(); ++i) {
92 if (i > 0 && tmp_ranks_[i].rank != tmp_ranks_[i - 1].rank) {
93 RefinePartition(&tmp_vars_);
96 tmp_vars_.push_back(tmp_ranks_[i].
var.value());
98 RefinePartition(&tmp_vars_);
103 void VarDomination::ProcessTempRanks() {
107 ++ct_index_for_signature_;
108 for (IntegerVariableWithRank& entry : tmp_ranks_) {
109 can_freely_decrease_[entry.var] =
false;
110 block_down_signatures_[entry.var] |= uint64_t{1}
111 << (ct_index_for_signature_ % 64);
112 entry.part = partition_->PartOf(entry.var.value());
115 tmp_ranks_.begin(), tmp_ranks_.end(),
116 [](
const IntegerVariableWithRank&
a,
const IntegerVariableWithRank&
b) {
117 return a.part < b.part;
120 for (
int i = 1; i < tmp_ranks_.size(); ++i) {
121 if (tmp_ranks_[i].part != tmp_ranks_[
start].part) {
122 Initialize({&tmp_ranks_[
start],
static_cast<size_t>(i -
start)});
126 if (
start < tmp_ranks_.size()) {
127 Initialize({&tmp_ranks_[
start], tmp_ranks_.size() -
start});
129 }
else if (phase_ == 1) {
130 FilterUsingTempRanks();
133 CheckUsingTempRanks();
138 absl::Span<const int> enforcements, absl::Span<const int> refs,
139 absl::Span<const int64_t> coeffs) {
140 FillTempRanks(
false, enforcements, refs, coeffs);
145 absl::Span<const int> enforcements, absl::Span<const int> refs,
146 absl::Span<const int64_t> coeffs) {
147 FillTempRanks(
true, enforcements, refs, coeffs);
151 void VarDomination::MakeRankEqualToStartOfPart(
152 absl::Span<IntegerVariableWithRank> span) {
153 const int size = span.size();
155 int previous_value = 0;
156 for (
int i = 0; i < size; ++i) {
157 const int64_t
value = span[i].rank;
158 if (
value != previous_value) {
159 previous_value =
value;
162 span[i].rank =
start;
166 void VarDomination::Initialize(absl::Span<IntegerVariableWithRank> span) {
169 MakeRankEqualToStartOfPart(span);
171 const int future_start = shared_buffer_.size();
172 int first_start = -1;
176 const int kSizeThreshold = 1000;
177 const int size = span.size();
178 for (
int i =
std::max(0, size - kSizeThreshold); i < size; ++i) {
179 const IntegerVariableWithRank entry = span[i];
180 const int num_candidates = size - entry.rank;
181 if (num_candidates >= kSizeThreshold)
continue;
184 int size_threshold = kSizeThreshold;
187 const int var_part = partition_->PartOf(entry.var.value());
188 const int part_size = partition_->SizeOfPart(var_part);
189 size_threshold =
std::min(size_threshold, part_size);
192 const int current_num_candidates = initial_candidates_[entry.var].
size;
193 if (current_num_candidates != 0) {
194 size_threshold =
std::min(size_threshold, current_num_candidates);
197 if (num_candidates < size_threshold) {
198 if (first_start == -1) first_start = entry.rank;
199 initial_candidates_[entry.var] = {
200 future_start - first_start +
static_cast<int>(entry.rank),
206 if (first_start == -1)
return;
207 for (
int i = first_start; i < size; ++i) {
208 shared_buffer_.push_back(span[i].
var);
224 const int kMaxInitialSize = 50;
225 std::vector<IntegerVariable> cropped_lists;
230 std::vector<int> buffer;
231 const auto elements_by_part = partition_->GetParts(&buffer);
232 for (IntegerVariable
var(0);
var < num_vars_with_negation_; ++
var) {
233 if (can_freely_decrease_[
var])
continue;
234 const int part = partition_->PartOf(
var.value());
235 const int part_size = partition_->SizeOfPart(part);
237 const int start = buffer_.size();
240 const uint64_t var_sig = block_down_signatures_[
var];
241 const uint64_t not_var_sig = block_down_signatures_[
NegationOf(
var)];
242 const int stored_size = initial_candidates_[
var].
size;
243 if (stored_size == 0 || part_size < stored_size) {
247 for (
const int value : elements_by_part[part]) {
248 const IntegerVariable c = IntegerVariable(
value);
253 if (++num_tested > 1000) {
254 is_cropped[
var] =
true;
256 int extra = new_size;
257 while (extra < kMaxInitialSize) {
264 if (can_freely_decrease_[
NegationOf(c)])
continue;
265 if (var_sig & ~block_down_signatures_[c])
continue;
266 if (block_down_signatures_[
NegationOf(c)] & ~not_var_sig)
continue;
268 buffer_.push_back(c);
272 if (new_size > kMaxInitialSize) {
273 is_cropped[
var] =
true;
283 for (
const IntegerVariable c : InitialDominatingCandidates(
var)) {
285 if (can_freely_decrease_[
NegationOf(c)])
continue;
286 if (partition_->PartOf(c.value()) != part)
continue;
287 if (var_sig & ~block_down_signatures_[c])
continue;
288 if (block_down_signatures_[
NegationOf(c)] & ~not_var_sig)
continue;
290 buffer_.push_back(c);
294 if (new_size > kMaxInitialSize) {
295 is_cropped[
var] =
true;
302 dominating_vars_[
var] = {
start, new_size};
309 for (
const IntegerVariable
var : cropped_lists) {
310 if (kMaxInitialSize / 2 < dominating_vars_[
var].size) {
311 dominating_vars_[
var].
size = kMaxInitialSize / 2;
314 for (IntegerVariable
var(0);
var < num_vars_with_negation_; ++
var) {
317 IntegerVariableSpan& s = dominating_vars_[
NegationOf(dom)];
318 if (s.size >= kMaxInitialSize)
continue;
327 for (
const IntegerVariable
var : cropped_lists) {
328 if (!is_cropped[
var])
continue;
329 IntegerVariableSpan& s = dominating_vars_[
var];
330 std::sort(&buffer_[s.start], &buffer_[s.start + s.size]);
331 const auto p = std::unique(&buffer_[s.start], &buffer_[s.start + s.size]);
332 s.size = p - &buffer_[s.start];
336 VLOG(1) <<
"Num initial list that where cropped: " << cropped_lists.size();
337 VLOG(1) <<
"Shared buffer size: " << shared_buffer_.size();
338 VLOG(1) <<
"Buffer size: " << buffer_.size();
344 return !buffer_.empty();
352 shared_buffer_.clear();
353 initial_candidates_.
assign(num_vars_with_negation_, IntegerVariableSpan());
356 for (IntegerVariable
var(0);
var < num_vars_with_negation_; ++
var) {
364 for (IntegerVariable
var(0);
var < num_vars_with_negation_; ++
var) {
365 initial_candidates_[
var].start =
start;
367 initial_candidates_[
var].
size = 0;
369 shared_buffer_.resize(
start);
372 for (IntegerVariable
var(0);
var < num_vars_with_negation_; ++
var) {
374 IntegerVariableSpan& span = initial_candidates_[
NegationOf(dom)];
381 tmp_var_to_rank_.
resize(num_vars_with_negation_, -1);
382 for (IntegerVariable
var(0);
var < num_vars_with_negation_; ++
var) {
383 for (
const IntegerVariable dom : InitialDominatingCandidates(
var)) {
384 tmp_var_to_rank_[dom] = 1;
388 IntegerVariableSpan& span = dominating_vars_[
var];
390 if (tmp_var_to_rank_[dom] != 1) {
394 buffer_[span.start + new_size++] = dom;
396 span.size = new_size;
398 for (
const IntegerVariable dom : InitialDominatingCandidates(
var)) {
399 tmp_var_to_rank_[dom] = -1;
403 VLOG(1) <<
"Transpose removed " << num_removed;
408 void VarDomination::FillTempRanks(
bool reverse_references,
409 absl::Span<const int> enforcements,
410 absl::Span<const int> refs,
411 absl::Span<const int64_t> coeffs) {
413 if (coeffs.empty()) {
415 for (
const int ref : refs) {
416 const IntegerVariable
var =
418 tmp_ranks_.push_back({
var, 0, 0});
422 for (
int i = 0; i < refs.size(); ++i) {
423 if (coeffs[i] == 0)
continue;
425 reverse_references ?
NegatedRef(refs[i]) : refs[i]);
427 tmp_ranks_.push_back({
var, 0, coeffs[i]});
432 std::sort(tmp_ranks_.begin(), tmp_ranks_.end());
433 MakeRankEqualToStartOfPart({&tmp_ranks_[0], tmp_ranks_.size()});
439 const int enforcement_rank = tmp_ranks_.size();
440 for (
const int ref : enforcements) {
441 tmp_ranks_.push_back(
448 void VarDomination::FilterUsingTempRanks() {
450 tmp_var_to_rank_.
resize(num_vars_with_negation_, -1);
451 for (
const IntegerVariableWithRank entry : tmp_ranks_) {
452 tmp_var_to_rank_[entry.var] = entry.rank;
456 for (
const IntegerVariableWithRank entry : tmp_ranks_) {
464 IntegerVariableSpan& span = dominating_vars_[entry.var];
465 if (span.size == 0)
continue;
468 if (tmp_var_to_rank_[candidate] < entry.rank)
continue;
469 buffer_[span.start + new_size++] = candidate;
471 span.size = new_size;
476 for (
const IntegerVariableWithRank entry : tmp_ranks_) {
477 tmp_var_to_rank_[entry.var] = -1;
482 void VarDomination::CheckUsingTempRanks() {
483 tmp_var_to_rank_.
resize(num_vars_with_negation_, -1);
484 for (
const IntegerVariableWithRank entry : tmp_ranks_) {
485 tmp_var_to_rank_[entry.var] = entry.rank;
489 for (IntegerVariable
var(0);
var < num_vars_with_negation_; ++
var) {
490 const int var_rank = tmp_var_to_rank_[
var];
491 const int negated_var_rank = tmp_var_to_rank_[
NegationOf(
var)];
493 CHECK(!can_freely_decrease_[
NegationOf(dom)]);
497 CHECK_LE(var_rank, tmp_var_to_rank_[dom]);
498 CHECK_LE(tmp_var_to_rank_[
NegationOf(dom)], negated_var_rank);
502 for (
const IntegerVariableWithRank entry : tmp_ranks_) {
503 tmp_var_to_rank_[entry.var] = -1;
512 return can_freely_decrease_[
var];
515 absl::Span<const IntegerVariable> VarDomination::InitialDominatingCandidates(
516 IntegerVariable
var)
const {
517 const IntegerVariableSpan span = initial_candidates_[
var];
518 if (span.size == 0)
return absl::Span<const IntegerVariable>();
519 return absl::Span<const IntegerVariable>(&shared_buffer_[span.start],
529 IntegerVariable
var)
const {
530 const IntegerVariableSpan span = dominating_vars_[
var];
531 if (span.size == 0)
return absl::Span<const IntegerVariable>();
532 return absl::Span<const IntegerVariable>(&buffer_[span.start], span.size);
550 for (
const int ref : refs) {
551 const IntegerVariable
var = RefToIntegerVariable(ref);
554 locking_ct_index_[
var] = ct_index;
560 for (
const int ref : refs) {
561 const IntegerVariable
var = RefToIntegerVariable(ref);
570 for (
const int ref : refs) {
571 const IntegerVariable
var = RefToIntegerVariable(ref);
576 locking_ct_index_[
var] = ct_index;
581 template <
typename LinearProto>
584 const LinearProto& linear, int64_t min_activity, int64_t max_activity,
586 const int64_t lb_limit = linear.domain(linear.domain_size() - 2);
587 const int64_t ub_limit = linear.domain(1);
588 const int num_terms = linear.vars_size();
589 for (
int i = 0; i < num_terms; ++i) {
590 int ref = linear.vars(i);
591 int64_t coeff = linear.coeffs(i);
597 const int64_t min_term = coeff *
context.MinOf(ref);
598 const int64_t max_term = coeff *
context.MaxOf(ref);
599 const int64_t term_diff = max_term - min_term;
600 const IntegerVariable
var = RefToIntegerVariable(ref);
603 if (min_activity < lb_limit) {
605 locking_ct_index_[
var] = ct_index;
606 if (min_activity + term_diff < lb_limit) {
609 const IntegerValue slack(lb_limit - min_activity);
610 const IntegerValue var_diff =
611 CeilRatio(IntegerValue(slack), IntegerValue(coeff));
612 can_freely_decrease_until_[
var] =
614 IntegerValue(
context.MinOf(ref)) + var_diff);
627 if (max_activity > ub_limit) {
630 if (max_activity - term_diff > ub_limit) {
633 const IntegerValue slack(max_activity - ub_limit);
634 const IntegerValue var_diff =
635 CeilRatio(IntegerValue(slack), IntegerValue(coeff));
638 -IntegerValue(
context.MaxOf(ref)) + var_diff);
649 void TransformLinearWithSpecialBoolean(
const ConstraintProto&
ct,
int ref,
650 std::vector<int64_t>* output) {
651 DCHECK_EQ(
ct.constraint_case(), ConstraintProto::kLinear);
656 if (!
ct.enforcement_literal().empty()) {
657 output->push_back(
ct.enforcement_literal().size());
658 for (
const int literal :
ct.enforcement_literal()) {
670 output->push_back(
ct.linear().vars().size());
671 for (
int i = 0; i <
ct.linear().vars().size(); ++i) {
672 const int v =
ct.linear().vars(i);
673 const int64_t c =
ct.linear().coeffs(i);
676 output->push_back(c);
680 output->push_back(-c);
683 output->push_back(v);
684 output->push_back(c);
689 for (
const int64_t
value :
ct.linear().domain()) {
690 output->push_back(
value - offset);
697 num_deleted_constraints_ = 0;
698 const CpModelProto& cp_model = *
context->working_model;
699 const int num_vars = cp_model.variables_size();
700 int64_t num_fixed_vars = 0;
701 for (
int var = 0;
var < num_vars; ++
var) {
707 if (ub_limit == lb) {
715 const int64_t lb_limit =
717 if (lb_limit == ub) {
725 if (lb_limit > ub_limit) {
735 context->UpdateRuleStats(
"dual: fix variable with multiple choices");
743 if (lb_limit > lb || ub_limit < ub) {
744 const int64_t new_ub =
751 const int64_t new_lb =
758 context->UpdateRuleStats(
"dual: reduced domain");
762 if (num_fixed_vars > 0) {
763 context->UpdateRuleStats(
"dual: fix variable", num_fixed_vars);
768 absl::flat_hash_map<uint64_t, std::pair<int, int>> equiv_modified_constraints;
769 std::vector<int64_t> temp_data;
770 std::vector<int64_t> other_temp_data;
777 int64_t work_done = 0;
778 const int64_t work_limit =
static_cast<int64_t
>(1e9);
779 std::vector<bool> processed(num_vars,
false);
780 int64_t num_bool_in_near_duplicate_ct = 0;
781 for (IntegerVariable
var(0);
var < num_locks_.
size(); ++
var) {
784 if (processed[positive_ref])
continue;
785 if (
context->IsFixed(positive_ref))
continue;
786 if (
context->VariableIsNotUsedAnymore(positive_ref))
continue;
787 if (
context->VariableWasRemoved(positive_ref))
continue;
789 if (num_locks_[
var] != 1)
continue;
790 if (locking_ct_index_[
var] == -1) {
792 "TODO dual: only one unspecified blocking constraint?");
796 const int ct_index = locking_ct_index_[
var];
797 const ConstraintProto&
ct =
context->working_model->constraints(ct_index);
798 if (
ct.constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
802 if (
ct.constraint_case() == ConstraintProto::kAtMostOne) {
803 context->UpdateRuleStats(
"TODO dual: tighten at most one");
807 if (
ct.constraint_case() != ConstraintProto::kBoolAnd) {
814 if (
ct.enforcement_literal().size() == 1 &&
816 const int enf =
ct.enforcement_literal(0);
818 :
context->MaxOf(positive_ref);
820 context->DomainOf(positive_ref)
822 context->deductions.ImpliedDomain(enf, positive_ref));
824 context->UpdateRuleStats(
"dual: fix variable");
825 if (!
context->SetLiteralToFalse(enf))
return false;
834 context->UpdateRuleStats(
"dual: fix variable");
835 if (!
context->IntersectDomainWith(positive_ref, implied)) {
846 processed[positive_ref] =
true;
847 context->UpdateRuleStats(
"dual: affine relation");
850 if (!
context->StoreAffineRelation(
865 if (
context->CanBeUsedAsLiteral(positive_ref)) {
869 processed[positive_ref] =
true;
870 context->UpdateRuleStats(
"dual: add implication");
872 context->UpdateNewConstraintsVariableUsage();
877 context->UpdateRuleStats(
"TODO dual: add implied bound");
882 if (
ct.constraint_case() == ConstraintProto::kLinear &&
883 ct.linear().vars().size() == 1 &&
884 ct.enforcement_literal().size() == 1 &&
886 const int var =
ct.linear().vars(0);
892 context->UpdateRuleStats(
"linear1: infeasible");
893 if (!
context->SetLiteralToFalse(
ct.enforcement_literal(0))) {
898 context->working_model->mutable_constraints(ct_index)->Clear();
899 context->UpdateConstraintVariableUsage(ct_index);
902 if (rhs == var_domain) {
903 context->UpdateRuleStats(
"linear1: always true");
906 context->working_model->mutable_constraints(ct_index)->Clear();
907 context->UpdateConstraintVariableUsage(ct_index);
913 context->UpdateRuleStats(
"dual: make encoding equiv");
914 const int64_t
value =
921 if (encoding_lit ==
NegatedRef(ref))
continue;
922 context->StoreBooleanEqualityRelation(encoding_lit,
925 if (encoding_lit == ref)
continue;
926 context->StoreBooleanEqualityRelation(encoding_lit, ref);
928 context->working_model->mutable_constraints(ct_index)->Clear();
929 context->UpdateConstraintVariableUsage(ct_index);
938 ConstraintProto* new_ct =
context->working_model->add_constraints();
939 new_ct->add_enforcement_literal(ref);
940 new_ct->mutable_linear()->add_vars(
var);
941 new_ct->mutable_linear()->add_coeffs(1);
943 context->UpdateNewConstraintsVariableUsage();
948 }
else if (complement.
IsFixed()) {
971 if (
ct.constraint_case() == ConstraintProto::kLinear &&
972 context->CanBeUsedAsLiteral(ref) && work_done < work_limit) {
973 work_done +=
ct.linear().vars().size();
974 TransformLinearWithSpecialBoolean(
ct, ref, &temp_data);
975 const uint64_t
hash =
976 fasthash64(temp_data.data(), temp_data.size() *
sizeof(int64_t),
977 uint64_t{0xa5b85c5e198ed849});
978 const auto [it, inserted] =
979 equiv_modified_constraints.insert({
hash, {ct_index, ref}});
982 const auto [other_c_with_same_hash, other_ref] = it->second;
983 CHECK_NE(other_c_with_same_hash, ct_index);
984 const auto& other_ct =
985 context->working_model->constraints(other_c_with_same_hash);
986 TransformLinearWithSpecialBoolean(other_ct, other_ref,
988 if (temp_data == other_temp_data) {
991 ++num_bool_in_near_duplicate_ct;
994 context->StoreBooleanEqualityRelation(ref, other_ref);
998 ++num_deleted_constraints_;
999 context->working_model->mutable_constraints(ct_index)->Clear();
1000 context->UpdateConstraintVariableUsage(ct_index);
1008 if (!
ct.enforcement_literal().empty()) {
1009 if (
ct.constraint_case() == ConstraintProto::kLinear &&
1010 ct.linear().vars().size() == 1 &&
1011 ct.enforcement_literal().size() == 1 &&
1013 context->UpdateRuleStats(
"TODO dual: make linear1 equiv");
1016 "TODO dual: only one blocking enforced constraint?");
1019 context->UpdateRuleStats(
"TODO dual: only one blocking constraint?");
1023 if (
ct.enforcement_literal().size() != 1)
continue;
1038 int a =
ct.enforcement_literal(0);
1041 num_locks_[RefToIntegerVariable(
NegatedRef(
a))] == 1) {
1044 if (
ct.bool_and().literals().size() != 1)
continue;
1045 b =
ct.bool_and().literals(0);
1049 for (
const int lhs :
ct.bool_and().literals()) {
1051 num_locks_[RefToIntegerVariable(lhs)] == 1) {
1059 CHECK_EQ(num_locks_[RefToIntegerVariable(
NegatedRef(
a))], 1);
1063 context->StoreBooleanEqualityRelation(
a,
b);
1064 context->UpdateRuleStats(
"dual: enforced equivalence");
1067 if (num_bool_in_near_duplicate_ct) {
1069 "dual: equivalent Boolean in near-duplicate constraints",
1070 num_bool_in_near_duplicate_ct);
1073 VLOG(2) <<
"Num deleted constraints: " << num_deleted_constraints_;
1080 const CpModelProto& cp_model = *
context.working_model;
1081 const int num_vars = cp_model.variables().size();
1082 var_domination->
Reset(num_vars);
1083 dual_bound_strengthening->
Reset(num_vars);
1085 for (
int var = 0;
var < num_vars; ++
var) {
1102 }
else if (r.
coeff == -1) {
1115 std::vector<int> tmp;
1116 const int num_constraints = cp_model.constraints_size();
1118 for (
int c = 0; c < num_constraints; ++c) {
1119 const ConstraintProto&
ct = cp_model.constraints(c);
1123 switch (
ct.constraint_case()) {
1124 case ConstraintProto::kBoolOr:
1130 ct.bool_or().literals(),
1133 case ConstraintProto::kBoolAnd:
1148 for (
const int ref :
ct.enforcement_literal()) {
1151 for (
const int ref :
ct.bool_and().literals()) {
1158 case ConstraintProto::kAtMostOne:
1161 ct.at_most_one().literals(), c);
1164 ct.at_most_one().literals(),
1167 case ConstraintProto::kExactlyOne:
1169 dual_bound_strengthening->
CannotMove(
ct.exactly_one().literals(),
1175 case ConstraintProto::kLinear: {
1177 const auto [min_activity, max_activity] =
1178 context.ComputeMinMaxActivity(
ct.linear());
1181 false,
context,
ct.linear(), min_activity, max_activity, c);
1183 const bool domain_is_simple =
ct.linear().domain().size() == 2;
1184 const bool free_to_increase =
1185 domain_is_simple &&
ct.linear().domain(1) >= max_activity;
1186 const bool free_to_decrease =
1187 domain_is_simple &&
ct.linear().domain(0) <= min_activity;
1188 if (free_to_decrease && free_to_increase)
break;
1189 if (free_to_increase) {
1192 ct.linear().coeffs());
1193 }
else if (free_to_decrease) {
1196 ct.linear().coeffs());
1199 if (!
ct.enforcement_literal().empty()) {
1201 {},
ct.enforcement_literal(), {});
1204 ct.linear().coeffs());
1215 for (
const int var :
context.ConstraintToVars(c)) {
1224 if (cp_model.has_objective()) {
1228 context.WriteObjectiveToProto();
1230 const auto [min_activity, max_activity] =
1231 context.ComputeMinMaxActivity(cp_model.objective());
1232 const auto& domain = cp_model.objective().domain();
1233 if (
phase == 0 && !domain.empty()) {
1235 true,
context, cp_model.objective(), min_activity, max_activity);
1237 if (domain.empty() || (domain.size() == 2 && domain[0] <= min_activity)) {
1239 {}, cp_model.objective().vars(),
1240 cp_model.objective().coeffs());
1243 cp_model.objective().coeffs());
1258 int64_t num_unconstrained_refs = 0;
1259 int64_t num_dominated_refs = 0;
1260 int64_t num_dominance_relations = 0;
1261 for (
int var = 0;
var < num_vars; ++
var) {
1266 num_unconstrained_refs++;
1268 num_dominated_refs++;
1269 num_dominance_relations +=
1274 if (num_unconstrained_refs == 0 && num_dominated_refs == 0)
return;
1275 VLOG(1) <<
"Dominance:"
1276 <<
" num_unconstrained_refs=" << num_unconstrained_refs
1277 <<
" num_dominated_refs=" << num_dominated_refs
1278 <<
" num_dominance_relations=" << num_dominance_relations;
1283 bool ProcessAtMostOne(absl::Span<const int> literals,
1285 const VarDomination& var_domination,
1288 for (
const int ref : literals) {
1291 for (
const int ref : literals) {
1292 if (
context->IsFixed(ref))
continue;
1294 const auto dominating_ivars = var_domination.DominatingVariables(ref);
1295 if (dominating_ivars.empty())
continue;
1296 for (
const IntegerVariable ivar : dominating_ivars) {
1297 if (!(*in_constraints)[ivar])
continue;
1304 if (!
context->SetLiteralToFalse(ref))
return false;
1308 for (
const int ref : literals) {
1318 const CpModelProto& cp_model = *
context->working_model;
1319 const int num_vars = cp_model.variables_size();
1322 bool work_to_do =
false;
1323 for (
int var = 0;
var < num_vars; ++
var) {
1331 if (!work_to_do)
return true;
1337 absl::flat_hash_set<std::pair<int, int>> implications;
1338 const int num_constraints = cp_model.constraints_size();
1339 for (
int c = 0; c < num_constraints; ++c) {
1340 const ConstraintProto&
ct = cp_model.constraints(c);
1342 if (
ct.constraint_case() == ConstraintProto::kBoolAnd) {
1343 if (
ct.enforcement_literal().size() != 1)
continue;
1344 const int a =
ct.enforcement_literal(0);
1346 for (
const int b :
ct.bool_and().literals()) {
1348 implications.insert({
a,
b});
1352 for (
const IntegerVariable ivar :
1356 context->UpdateRuleStats(
"domination: in implication");
1357 if (!
context->SetLiteralToFalse(
a))
return false;
1364 for (
const IntegerVariable ivar :
1368 context->UpdateRuleStats(
"domination: in implication");
1369 if (!
context->SetLiteralToTrue(
b))
return false;
1377 if (!
ct.enforcement_literal().empty())
continue;
1383 if (
ct.constraint_case() == ConstraintProto::kAtMostOne) {
1384 if (!ProcessAtMostOne(
ct.at_most_one().literals(),
1385 "domination: in at most one", var_domination,
1389 }
else if (
ct.constraint_case() == ConstraintProto::kExactlyOne) {
1390 if (!ProcessAtMostOne(
ct.exactly_one().literals(),
1391 "domination: in exactly one", var_domination,
1397 if (
ct.constraint_case() != ConstraintProto::kLinear)
continue;
1399 int num_dominated = 0;
1400 for (
const int var :
context->ConstraintToVars(c)) {
1406 if (num_dominated == 0)
continue;
1409 int64_t min_activity = 0;
1410 int64_t max_activity = 0;
1411 const int num_terms =
ct.linear().vars_size();
1412 for (
int i = 0; i < num_terms; ++i) {
1413 int ref =
ct.linear().vars(i);
1414 int64_t coeff =
ct.linear().coeffs(i);
1419 const int64_t min_term = coeff *
context->MinOf(ref);
1420 const int64_t max_term = coeff *
context->MaxOf(ref);
1421 min_activity += min_term;
1422 max_activity += max_term;
1424 var_lb_to_ub_diff[ivar] = max_term - min_term;
1425 var_lb_to_ub_diff[
NegationOf(ivar)] = min_term - max_term;
1427 const int64_t rhs_lb =
ct.linear().domain(0);
1428 const int64_t rhs_ub =
ct.linear().domain(
ct.linear().domain_size() - 1);
1429 if (max_activity < rhs_lb || min_activity > rhs_ub) {
1430 return context->NotifyThatModelIsUnsat(
"linear equation unsat.");
1434 for (
int i = 0; i < num_terms; ++i) {
1435 const int ref =
ct.linear().vars(i);
1436 const int64_t coeff =
ct.linear().coeffs(i);
1438 if (
context->IsFixed(ref))
continue;
1440 for (
const int current_ref : {ref,
NegatedRef(ref)}) {
1441 const absl::Span<const IntegerVariable> dominated_by =
1443 if (dominated_by.empty())
continue;
1445 const bool ub_side = (coeff > 0) == (current_ref == ref);
1447 if (max_activity <= rhs_ub)
continue;
1449 if (min_activity >= rhs_lb)
continue;
1451 const int64_t slack =
1452 ub_side ? rhs_ub - min_activity : max_activity - rhs_lb;
1457 for (
const IntegerVariable ivar : dominated_by) {
1461 .NumIntervals() != 1) {
1471 const int64_t lb =
context->MinOf(current_ref);
1473 context->UpdateRuleStats(
"domination: fixed to lb.");
1474 if (!
context->IntersectDomainWith(current_ref,
Domain(lb))) {
1479 const IntegerVariable current_var =
1482 CHECK_GE(var_lb_to_ub_diff[current_var], 0);
1483 max_activity -= var_lb_to_ub_diff[current_var];
1485 CHECK_LE(var_lb_to_ub_diff[current_var], 0);
1486 min_activity -= var_lb_to_ub_diff[current_var];
1488 var_lb_to_ub_diff[current_var] = 0;
1489 var_lb_to_ub_diff[
NegationOf(current_var)] = 0;
1496 int64_t new_ub = lb + diff.value();
1497 if (new_ub < context->MaxOf(current_ref)) {
1501 new_ub =
context->DomainOf(current_ref)
1506 if (new_ub < context->MaxOf(current_ref)) {
1507 context->UpdateRuleStats(
"domination: reduced ub.");
1508 if (!
context->IntersectDomainWith(current_ref,
Domain(lb, new_ub))) {
1513 const IntegerVariable current_var =
1516 CHECK_GE(var_lb_to_ub_diff[current_var], 0);
1517 max_activity -= var_lb_to_ub_diff[current_var];
1519 CHECK_LE(var_lb_to_ub_diff[current_var], 0);
1520 min_activity -= var_lb_to_ub_diff[current_var];
1524 var_lb_to_ub_diff[current_var] = new_diff;
1525 var_lb_to_ub_diff[
NegationOf(current_var)] = -new_diff;
1526 max_activity += new_diff;
1528 var_lb_to_ub_diff[current_var] = -new_diff;
1529 var_lb_to_ub_diff[
NegationOf(current_var)] = +new_diff;
1530 min_activity -= new_diff;
1537 for (
const int ref :
ct.linear().vars()) {
1539 var_lb_to_ub_diff[ivar] = 0;
1563 for (
int positive_ref = 0; positive_ref < num_vars; ++positive_ref) {
1564 if (
context->IsFixed(positive_ref))
continue;
1565 if (
context->VariableIsNotUsedAnymore(positive_ref))
continue;
1566 if (
context->VariableWasRemoved(positive_ref))
continue;
1567 if (!
context->CanBeUsedAsLiteral(positive_ref))
continue;
1568 for (
const int ref : {positive_ref,
NegatedRef(positive_ref)}) {
1571 for (
const IntegerVariable dom :
1573 if (increase_is_forbidden[dom])
continue;
1575 if (
context->IsFixed(dom_ref))
continue;
1576 if (
context->VariableIsNotUsedAnymore(dom_ref))
continue;
1577 if (
context->VariableWasRemoved(dom_ref))
continue;
1578 if (!
context->CanBeUsedAsLiteral(dom_ref))
continue;
1579 if (implications.contains({ref, dom_ref}))
continue;
1582 context->AddImplication(ref, dom_ref);
1585 increase_is_forbidden[
var] =
true;
1586 increase_is_forbidden[
NegationOf(dom)] =
true;
1590 if (num_added > 0) {
1591 VLOG(1) <<
"Added " << num_added <<
" domination implications.";
1592 context->UpdateNewConstraintsVariableUsage();
1593 context->UpdateRuleStats(
"domination: added implications", num_added);
void assign(size_type n, const value_type &val)
void resize(size_type new_size)
void push_back(const value_type &x)
We call domain any subset of Int64 = [kint64min, kint64max].
Domain InverseMultiplicationBy(const int64_t coeff) const
Returns {x ∈ Int64, ∃ e ∈ D, x * coeff = e}.
Domain Complement() const
Returns the set Int64 ∖ D.
bool Contains(int64_t value) const
Returns true iff value is in Domain.
int64_t FixedValue() const
Returns the value of a fixed domain.
std::vector< int64_t > FlattenedIntervals() const
This method returns the flattened list of interval bounds of the domain.
bool IsFixed() const
Returns true iff the domain is reduced to a single value.
Domain IntersectionWith(const Domain &domain) const
Returns the intersection of D and domain.
int64_t Min() const
Returns the min value of the domain.
bool IsEmpty() const
Returns true if this is the empty set.
bool Strengthen(PresolveContext *context)
void Reset(int num_variables)
int64_t CanFreelyDecreaseUntil(int ref) const
void ProcessLinearConstraint(bool is_objective, const PresolveContext &context, const LinearProto &linear, int64_t min_activity, int64_t max_activity, int ct_index=-1)
void CannotMove(absl::Span< const int > refs, int ct_index=-1)
void CannotIncrease(absl::Span< const int > refs, int ct_index=-1)
void CannotDecrease(absl::Span< const int > refs, int ct_index=-1)
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)
bool CanFreelyDecrease(int ref) const
static int IntegerVariableToRef(IntegerVariable var)
void Reset(int num_variables)
void ActivityShouldNotDecrease(absl::Span< const int > enforcements, absl::Span< const int > refs, absl::Span< const int64_t > coeffs)
absl::Span< const IntegerVariable > DominatingVariables(int ref) const
std::string DominationDebugString(IntegerVariable var) const
static IntegerVariable RefToIntegerVariable(int ref)
void CanOnlyDominateEachOther(absl::Span< const int > refs)
DecisionBuilder *const phase
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)
IntegerValue CeilRatio(IntegerValue dividend, IntegerValue positive_divisor)
const IntegerVariable kNoIntegerVariable(-1)
void DetectDominanceRelations(const PresolveContext &context, VarDomination *var_domination, DualBoundStrengthening *dual_bound_strengthening)
IntegerVariable PositiveVariable(IntegerVariable i)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
bool ExploitDominanceRelations(const VarDomination &var_domination, PresolveContext *context)
Collection of objects used to extend the Constraint Solver library.
uint64_t fasthash64(const void *buf, size_t len, uint64_t seed)
Fractional coeff_magnitude
#define VLOG(verboselevel)