29 #include "absl/container/flat_hash_map.h"
30 #include "absl/container/flat_hash_set.h"
31 #include "absl/random/random.h"
32 #include "absl/strings/str_join.h"
57 bool CpModelPresolver::RemoveConstraint(ConstraintProto*
ct) {
65 std::vector<int> interval_mapping(context_->
working_model->constraints_size(),
67 int new_num_constraints = 0;
68 const int old_num_non_empty_constraints =
70 for (
int c = 0; c < old_num_non_empty_constraints; ++c) {
71 const auto type = context_->
working_model->constraints(c).constraint_case();
72 if (type == ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET)
continue;
73 if (type == ConstraintProto::ConstraintCase::kInterval) {
74 interval_mapping[c] = new_num_constraints;
76 context_->
working_model->mutable_constraints(new_num_constraints++)
79 context_->
working_model->mutable_constraints()->DeleteSubrange(
80 new_num_constraints, old_num_non_empty_constraints - new_num_constraints);
81 for (ConstraintProto& ct_ref :
84 [&interval_mapping](
int* ref) {
85 *ref = interval_mapping[*ref];
92 bool CpModelPresolver::PresolveEnforcementLiteral(ConstraintProto*
ct) {
97 const int old_size =
ct->enforcement_literal().size();
98 for (
const int literal :
ct->enforcement_literal()) {
107 return RemoveConstraint(
ct);
114 return RemoveConstraint(
ct);
121 const int64 obj_coeff =
125 context_->
UpdateRuleStats(
"enforcement literal with unique direction");
127 return RemoveConstraint(
ct);
131 ct->set_enforcement_literal(new_size++,
literal);
133 ct->mutable_enforcement_literal()->Truncate(new_size);
134 return new_size != old_size;
137 bool CpModelPresolver::PresolveBoolXor(ConstraintProto*
ct) {
142 bool changed =
false;
143 int num_true_literals = 0;
145 for (
const int literal :
ct->bool_xor().literals()) {
166 ct->mutable_bool_xor()->set_literals(new_size++,
literal);
170 }
else if (new_size == 2) {
173 if (num_true_literals % 2 == 1) {
175 ct->mutable_bool_xor()->set_literals(new_size++, true_literal);
177 if (num_true_literals > 1) {
178 context_->
UpdateRuleStats(
"bool_xor: remove even number of true literals");
181 ct->mutable_bool_xor()->mutable_literals()->Truncate(new_size);
185 bool CpModelPresolver::PresolveBoolOr(ConstraintProto*
ct) {
192 for (
const int literal :
ct->enforcement_literal()) {
195 ct->clear_enforcement_literal();
199 bool changed =
false;
202 for (
const int literal :
ct->bool_or().literals()) {
209 return RemoveConstraint(
ct);
217 return RemoveConstraint(
ct);
221 return RemoveConstraint(
ct);
240 return RemoveConstraint(
ct);
253 ct->mutable_bool_or()->mutable_literals()->Clear();
255 ct->mutable_bool_or()->add_literals(lit);
261 ABSL_MUST_USE_RESULT
bool CpModelPresolver::MarkConstraintAsFalse(
262 ConstraintProto*
ct) {
265 ct->mutable_bool_or()->clear_literals();
266 for (
const int lit :
ct->enforcement_literal()) {
269 ct->clear_enforcement_literal();
277 bool CpModelPresolver::PresolveBoolAnd(ConstraintProto*
ct) {
282 for (
const int literal :
ct->bool_and().literals()) {
285 return RemoveConstraint(
ct);
288 bool changed =
false;
290 for (
const int literal :
ct->bool_and().literals()) {
293 return MarkConstraintAsFalse(
ct);
313 ct->mutable_bool_and()->mutable_literals()->Clear();
315 ct->mutable_bool_and()->add_literals(lit);
324 if (
ct->enforcement_literal().size() == 1 &&
325 ct->bool_and().literals().size() == 1) {
326 const int enforcement =
ct->enforcement_literal(0);
336 ct->bool_and().literals(0));
344 bool CpModelPresolver::PresolveAtMostOne(ConstraintProto*
ct) {
349 if (
ct->at_most_one().literals_size() == 1) {
351 return RemoveConstraint(
ct);
355 std::sort(
ct->mutable_at_most_one()->mutable_literals()->begin(),
356 ct->mutable_at_most_one()->mutable_literals()->end());
358 for (
const int literal :
ct->at_most_one().literals()) {
366 bool changed =
false;
368 for (
const int literal :
ct->at_most_one().literals()) {
371 for (
const int other :
ct->at_most_one().literals()) {
376 return RemoveConstraint(
ct);
388 return RemoveConstraint(
ct);
392 ct->mutable_at_most_one()->mutable_literals()->Clear();
394 ct->mutable_at_most_one()->add_literals(lit);
401 bool CpModelPresolver::PresolveIntMax(ConstraintProto*
ct) {
403 if (
ct->int_max().vars().empty()) {
405 return MarkConstraintAsFalse(
ct);
407 const int target_ref =
ct->int_max().target();
412 bool contains_target_ref =
false;
413 std::set<int> used_ref;
415 for (
const int ref :
ct->int_max().vars()) {
416 if (ref == target_ref) contains_target_ref =
true;
422 used_ref.insert(ref);
423 ct->mutable_int_max()->set_vars(new_size++, ref);
427 if (new_size < ct->int_max().vars_size()) {
430 ct->mutable_int_max()->mutable_vars()->Truncate(new_size);
431 if (contains_target_ref) {
433 for (
const int ref :
ct->int_max().vars()) {
434 if (ref == target_ref)
continue;
435 ConstraintProto* new_ct = context_->
working_model->add_constraints();
436 *new_ct->mutable_enforcement_literal() =
ct->enforcement_literal();
437 auto* arg = new_ct->mutable_linear();
438 arg->add_vars(target_ref);
445 return RemoveConstraint(
ct);
449 Domain infered_domain;
450 for (
const int ref :
ct->int_max().vars()) {
451 infered_domain = infered_domain.UnionWith(
456 bool domain_reduced =
false;
468 const Domain& target_domain = context_->
DomainOf(target_ref);
469 if (infered_domain.IntersectionWith(Domain(
kint64min, target_domain.Max()))
470 .IsIncludedIn(target_domain)) {
471 if (infered_domain.Max() <= target_domain.Max()) {
474 }
else if (
ct->enforcement_literal().empty()) {
476 for (
const int ref :
ct->int_max().vars()) {
479 ref, Domain(
kint64min, target_domain.Max()))) {
487 for (
const int ref :
ct->int_max().vars()) {
488 ConstraintProto* new_ct = context_->
working_model->add_constraints();
489 *(new_ct->mutable_enforcement_literal()) =
ct->enforcement_literal();
490 ct->mutable_linear()->add_vars(ref);
491 ct->mutable_linear()->add_coeffs(1);
493 ct->mutable_linear()->add_domain(target_domain.Max());
500 return RemoveConstraint(
ct);
506 const int size =
ct->int_max().vars_size();
507 const int64 target_max = context_->
MaxOf(target_ref);
508 for (
const int ref :
ct->int_max().vars()) {
515 if (context_->
MaxOf(ref) >= infered_min) {
516 ct->mutable_int_max()->set_vars(new_size++, ref);
519 if (domain_reduced) {
523 bool modified =
false;
524 if (new_size < size) {
526 ct->mutable_int_max()->mutable_vars()->Truncate(new_size);
532 return MarkConstraintAsFalse(
ct);
538 ConstraintProto* new_ct = context_->
working_model->add_constraints();
540 auto* arg = new_ct->mutable_linear();
541 arg->add_vars(target_ref);
543 arg->add_vars(
ct->int_max().vars(0));
548 return RemoveConstraint(
ct);
553 bool CpModelPresolver::PresolveLinMin(ConstraintProto*
ct) {
556 const auto copy =
ct->lin_min();
558 ct->mutable_lin_max()->mutable_target());
559 for (
const LinearExpressionProto& expr : copy.exprs()) {
560 LinearExpressionProto*
const new_expr =
ct->mutable_lin_max()->add_exprs();
563 return PresolveLinMax(
ct);
566 bool CpModelPresolver::PresolveLinMax(ConstraintProto*
ct) {
568 if (
ct->lin_max().exprs().empty()) {
570 return MarkConstraintAsFalse(
ct);
576 int64 infered_min = context_->
MinOf(
ct->lin_max().target());
577 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
588 for (
int i = 0; i <
ct->lin_max().exprs_size(); ++i) {
589 const LinearExpressionProto& expr =
ct->lin_max().exprs(i);
590 if (context_->
MaxOf(expr) >= infered_min) {
591 *
ct->mutable_lin_max()->mutable_exprs(new_size) = expr;
596 if (new_size < ct->lin_max().exprs_size()) {
598 ct->mutable_lin_max()->mutable_exprs()->DeleteSubrange(
599 new_size,
ct->lin_max().exprs_size() - new_size);
606 bool CpModelPresolver::PresolveIntAbs(ConstraintProto*
ct) {
609 const int target_ref =
ct->int_max().target();
614 const Domain new_target_domain = var_domain.
UnionWith(var_domain.Negation())
624 const Domain target_domain = context_->
DomainOf(target_ref);
625 const Domain new_var_domain =
626 target_domain.
UnionWith(target_domain.Negation());
636 ConstraintProto* new_ct = context_->
working_model->add_constraints();
637 new_ct->set_name(
ct->name());
638 auto* arg = new_ct->mutable_linear();
639 arg->add_vars(target_ref);
646 return RemoveConstraint(
ct);
651 ConstraintProto* new_ct = context_->
working_model->add_constraints();
652 new_ct->set_name(
ct->name());
653 auto* arg = new_ct->mutable_linear();
654 arg->add_vars(target_ref);
661 return RemoveConstraint(
ct);
667 context_->
IsFixed(target_ref)) {
668 if (!context_->
IsFixed(target_ref)) {
673 return RemoveConstraint(
ct);
683 bool CpModelPresolver::PresolveIntMin(ConstraintProto*
ct) {
686 const auto copy =
ct->int_min();
687 ct->mutable_int_max()->set_target(
NegatedRef(copy.target()));
688 for (
const int ref : copy.vars()) {
691 return PresolveIntMax(
ct);
694 bool CpModelPresolver::PresolveIntProd(ConstraintProto*
ct) {
698 bool changed =
false;
703 for (
int i = 0; i <
ct->int_prod().vars().size(); ++i) {
704 const int ref =
ct->int_prod().vars(i);
706 if (r.representative != ref && r.offset == 0) {
708 ct->mutable_int_prod()->set_vars(i, r.representative);
723 const int old_target =
ct->int_prod().target();
724 const int new_target = context_->
working_model->variables_size();
726 IntegerVariableProto* var_proto = context_->
working_model->add_variables();
733 ct->mutable_int_prod()->set_target(new_target);
734 if (context_->
IsFixed(new_target)) {
746 ConstraintProto* new_ct = context_->
working_model->add_constraints();
747 LinearConstraintProto* lin = new_ct->mutable_linear();
748 lin->add_vars(old_target);
750 lin->add_vars(new_target);
751 lin->add_coeffs(-constant);
761 for (
const int ref :
ct->int_prod().vars()) {
762 implied = implied.ContinuousMultiplicationBy(context_->
DomainOf(ref));
764 bool modified =
false;
773 if (
ct->int_prod().vars_size() == 2) {
774 int a =
ct->int_prod().vars(0);
775 int b =
ct->int_prod().vars(1);
776 const int product =
ct->int_prod().target();
781 ConstraintProto*
const lin = context_->
working_model->add_constraints();
782 lin->mutable_linear()->add_vars(
b);
783 lin->mutable_linear()->add_coeffs(context_->
MinOf(
a));
784 lin->mutable_linear()->add_vars(product);
785 lin->mutable_linear()->add_coeffs(-1);
786 lin->mutable_linear()->add_domain(0);
787 lin->mutable_linear()->add_domain(0);
790 return RemoveConstraint(
ct);
791 }
else if (context_->
MinOf(
a) != 1) {
792 bool domain_modified =
false;
798 return RemoveConstraint(
ct);
801 return RemoveConstraint(
ct);
803 }
else if (
a ==
b &&
a == product) {
808 return RemoveConstraint(
ct);
813 const int target_ref =
ct->int_prod().target();
815 for (
const int var :
ct->int_prod().vars()) {
817 if (context_->
MinOf(
var) < 0)
return changed;
818 if (context_->
MaxOf(
var) > 1)
return changed;
827 ConstraintProto* new_ct = context_->
working_model->add_constraints();
828 new_ct->add_enforcement_literal(target_ref);
829 auto* arg = new_ct->mutable_bool_and();
830 for (
const int var :
ct->int_prod().vars()) {
831 arg->add_literals(
var);
835 ConstraintProto* new_ct = context_->
working_model->add_constraints();
836 auto* arg = new_ct->mutable_bool_or();
837 arg->add_literals(target_ref);
838 for (
const int var :
ct->int_prod().vars()) {
843 return RemoveConstraint(
ct);
846 bool CpModelPresolver::PresolveIntDiv(ConstraintProto*
ct) {
850 const int target =
ct->int_div().target();
851 const int ref_x =
ct->int_div().vars(0);
852 const int ref_div =
ct->int_div().vars(1);
859 const int64 divisor = context_->
MinOf(ref_div);
861 LinearConstraintProto*
const lin =
862 context_->
working_model->add_constraints()->mutable_linear();
863 lin->add_vars(ref_x);
865 lin->add_vars(target);
871 return RemoveConstraint(
ct);
873 bool domain_modified =
false;
877 if (domain_modified) {
879 "int_div: updated domain of target in target = X / cte");
890 if (context_->
MinOf(target) >= 0 && context_->
MinOf(ref_x) >= 0 &&
892 LinearConstraintProto*
const lin =
893 context_->
working_model->add_constraints()->mutable_linear();
894 lin->add_vars(ref_x);
896 lin->add_vars(target);
897 lin->add_coeffs(-divisor);
899 lin->add_domain(divisor - 1);
902 "int_div: linearize positive division with a constant divisor");
903 return RemoveConstraint(
ct);
911 bool CpModelPresolver::ExploitEquivalenceRelations(
int c, ConstraintProto*
ct) {
912 bool changed =
false;
917 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kLinear) {
918 for (
int& ref : *
ct->mutable_enforcement_literal()) {
930 bool work_to_do =
false;
933 if (r.representative !=
var) {
938 if (!work_to_do)
return false;
942 [&changed,
this](
int* ref) {
953 [&changed,
this](
int* ref) {
964 void CpModelPresolver::DivideLinearByGcd(ConstraintProto*
ct) {
969 const int num_vars =
ct->linear().vars().size();
970 for (
int i = 0; i < num_vars; ++i) {
971 const int64 magnitude = std::abs(
ct->linear().coeffs(i));
977 for (
int i = 0; i < num_vars; ++i) {
978 ct->mutable_linear()->set_coeffs(i,
ct->linear().coeffs(i) / gcd);
982 if (
ct->linear().domain_size() == 0) {
983 return (
void)MarkConstraintAsFalse(
ct);
988 void CpModelPresolver::PresolveLinearEqualityModuloTwo(ConstraintProto*
ct) {
989 if (!
ct->enforcement_literal().empty())
return;
990 if (
ct->linear().domain().size() != 2)
return;
991 if (
ct->linear().domain(0) !=
ct->linear().domain(1))
return;
996 std::vector<int> literals;
997 for (
int i = 0; i <
ct->linear().vars().size(); ++i) {
998 const int64 coeff =
ct->linear().coeffs(i);
999 const int ref =
ct->linear().vars(i);
1000 if (coeff % 2 == 0)
continue;
1003 if (literals.size() > 2)
return;
1005 if (literals.size() == 1) {
1006 const int64 rhs = std::abs(
ct->linear().domain(0));
1007 context_->
UpdateRuleStats(
"linear: only one odd Boolean in equality");
1009 }
else if (literals.size() == 2) {
1010 const int64 rhs = std::abs(
ct->linear().domain(0));
1011 context_->
UpdateRuleStats(
"linear: only two odd Booleans in equality");
1021 bool CpModelPresolver::CanonicalizeLinear(ConstraintProto*
ct) {
1022 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1034 int64 sum_of_fixed_terms = 0;
1035 bool remapped =
false;
1036 const int num_vars =
ct->linear().vars().size();
1037 DCHECK_EQ(num_vars,
ct->linear().coeffs().size());
1038 for (
int i = 0; i < num_vars; ++i) {
1039 const int ref =
ct->linear().vars(i);
1043 if (coeff == 0)
continue;
1046 sum_of_fixed_terms += coeff * context_->
MinOf(
var);
1052 bool removed =
false;
1053 for (
const int enf :
ct->enforcement_literal()) {
1057 sum_of_fixed_terms += coeff;
1066 context_->
UpdateRuleStats(
"linear: enforcement literal in constraint");
1071 if (r.representative !=
var) {
1073 sum_of_fixed_terms += coeff * r.
offset;
1075 tmp_terms_.push_back({r.representative, coeff * r.coeff});
1078 if (sum_of_fixed_terms != 0) {
1080 rhs = rhs.AdditionWith({-sum_of_fixed_terms, -sum_of_fixed_terms});
1084 ct->mutable_linear()->clear_vars();
1085 ct->mutable_linear()->clear_coeffs();
1086 std::sort(tmp_terms_.begin(), tmp_terms_.end());
1087 int current_var = 0;
1088 int64 current_coeff = 0;
1089 for (
const auto entry : tmp_terms_) {
1091 if (entry.first == current_var) {
1092 current_coeff += entry.second;
1094 if (current_coeff != 0) {
1095 ct->mutable_linear()->add_vars(current_var);
1096 ct->mutable_linear()->add_coeffs(current_coeff);
1098 current_var = entry.first;
1099 current_coeff = entry.second;
1102 if (current_coeff != 0) {
1103 ct->mutable_linear()->add_vars(current_var);
1104 ct->mutable_linear()->add_coeffs(current_coeff);
1106 DivideLinearByGcd(
ct);
1108 bool var_constraint_graph_changed =
false;
1111 var_constraint_graph_changed =
true;
1113 if (
ct->linear().vars().size() < num_vars) {
1115 var_constraint_graph_changed =
true;
1117 return var_constraint_graph_changed;
1120 bool CpModelPresolver::RemoveSingletonInLinear(ConstraintProto*
ct) {
1121 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1126 std::set<int> index_to_erase;
1127 const int num_vars =
ct->linear().vars().size();
1131 for (
int i = 0; i < num_vars; ++i) {
1132 const int var =
ct->linear().vars(i);
1133 const int64 coeff =
ct->linear().coeffs(i);
1137 const auto term_domain =
1139 if (!exact)
continue;
1143 if (new_rhs.NumIntervals() > 100)
continue;
1150 index_to_erase.insert(i);
1157 if (index_to_erase.empty()) {
1159 if (options_.
parameters.presolve_substitution_level() <= 0)
return false;
1160 if (!
ct->enforcement_literal().empty())
return false;
1164 if (rhs.Min() != rhs.Max())
return false;
1166 for (
int i = 0; i < num_vars; ++i) {
1167 const int var =
ct->linear().vars(i);
1168 const int64 coeff =
ct->linear().coeffs(i);
1186 const int64 objective_coeff =
1189 if (objective_coeff % coeff != 0)
continue;
1193 const auto term_domain =
1195 if (!exact)
continue;
1197 if (new_rhs.NumIntervals() > 100)
continue;
1205 objective_coeff))) {
1222 LOG(
WARNING) <<
"This was not supposed to happen and the presolve "
1223 "could be improved.";
1226 context_->
UpdateRuleStats(
"linear: singleton column define objective.");
1230 return RemoveConstraint(
ct);
1236 "linear: singleton column in equality and in objective.");
1243 index_to_erase.insert(i);
1247 if (index_to_erase.empty())
return false;
1256 for (
int i = 0; i < num_vars; ++i) {
1257 if (index_to_erase.count(i)) {
1261 ct->mutable_linear()->set_coeffs(new_size,
ct->linear().coeffs(i));
1262 ct->mutable_linear()->set_vars(new_size,
ct->linear().vars(i));
1265 ct->mutable_linear()->mutable_vars()->Truncate(new_size);
1266 ct->mutable_linear()->mutable_coeffs()->Truncate(new_size);
1268 DivideLinearByGcd(
ct);
1272 bool CpModelPresolver::PresolveSmallLinear(ConstraintProto*
ct) {
1273 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1279 if (
ct->linear().vars().empty()) {
1282 if (rhs.Contains(0)) {
1283 return RemoveConstraint(
ct);
1285 return MarkConstraintAsFalse(
ct);
1292 if (
ct->linear().vars_size() == 1 &&
ct->enforcement_literal_size() > 0 &&
1293 ct->linear().coeffs(0) == 1 &&
1296 context_->
UpdateRuleStats(
"linear: remove abs from abs(x) in domain");
1297 const Domain implied_abs_target_domain =
1300 .IntersectionWith(context_->
DomainOf(
ct->linear().vars(0)));
1302 if (implied_abs_target_domain.IsEmpty()) {
1303 return MarkConstraintAsFalse(
ct);
1306 const Domain new_abs_var_domain =
1307 implied_abs_target_domain
1308 .UnionWith(implied_abs_target_domain.Negation())
1309 .IntersectionWith(context_->
DomainOf(abs_arg));
1311 if (new_abs_var_domain.IsEmpty()) {
1312 return MarkConstraintAsFalse(
ct);
1315 ConstraintProto* new_ct = context_->
working_model->add_constraints();
1316 new_ct->set_name(
ct->name());
1317 for (
const int literal :
ct->enforcement_literal()) {
1318 new_ct->add_enforcement_literal(
literal);
1320 auto* arg = new_ct->mutable_linear();
1321 arg->add_vars(abs_arg);
1325 return RemoveConstraint(
ct);
1329 if (
ct->enforcement_literal_size() != 1 ||
ct->linear().vars_size() != 1 ||
1330 (
ct->linear().coeffs(0) != 1 &&
ct->linear().coeffs(0) == -1)) {
1334 const int literal =
ct->enforcement_literal(0);
1335 const LinearConstraintProto& linear =
ct->linear();
1336 const int ref = linear.vars(0);
1341 if (linear.domain_size() == 2 && linear.domain(0) == linear.domain(1)) {
1343 : -linear.domain(0) * coeff;
1352 if (complement.Size() != 1)
return false;
1354 : -complement.Min() * coeff;
1369 if (
ct->linear().vars().size() == 1) {
1371 ?
ct->linear().coeffs(0)
1372 : -
ct->linear().coeffs(0);
1377 rhs.InverseMultiplicationBy(coeff))) {
1380 return RemoveConstraint(
ct);
1387 const LinearConstraintProto& arg =
ct->linear();
1388 if (arg.vars_size() == 2) {
1390 const int64 rhs_min = rhs.Min();
1391 const int64 rhs_max = rhs.Max();
1392 if (rhs_min == rhs_max) {
1393 const int v1 = arg.vars(0);
1394 const int v2 = arg.vars(1);
1395 const int64 coeff1 = arg.coeffs(0);
1396 const int64 coeff2 = arg.coeffs(1);
1400 }
else if (coeff2 == 1) {
1402 }
else if (coeff1 == -1) {
1404 }
else if (coeff2 == -1) {
1407 if (added)
return RemoveConstraint(
ct);
1417 bool IsLeConstraint(
const Domain& domain,
const Domain& all_values) {
1418 return all_values.IntersectionWith(Domain(
kint64min, domain.Max()))
1419 .IsIncludedIn(domain);
1423 bool IsGeConstraint(
const Domain& domain,
const Domain& all_values) {
1424 return all_values.IntersectionWith(Domain(domain.Min(),
kint64max))
1425 .IsIncludedIn(domain);
1431 bool RhsCanBeFixedToMin(
int64 coeff,
const Domain& var_domain,
1432 const Domain& terms,
const Domain& rhs) {
1433 if (var_domain.NumIntervals() != 1)
return false;
1434 if (std::abs(coeff) != 1)
return false;
1442 if (coeff == 1 && terms.Max() + var_domain.Min() <= rhs.Min()) {
1445 if (coeff == -1 && terms.Max() - var_domain.Max() <= rhs.Min()) {
1451 bool RhsCanBeFixedToMax(
int64 coeff,
const Domain& var_domain,
1452 const Domain& terms,
const Domain& rhs) {
1453 if (var_domain.NumIntervals() != 1)
return false;
1454 if (std::abs(coeff) != 1)
return false;
1456 if (coeff == 1 && terms.Min() + var_domain.Max() >= rhs.Max()) {
1459 if (coeff == -1 && terms.Min() - var_domain.Min() >= rhs.Max()) {
1466 void TakeIntersectionWith(
const absl::flat_hash_set<int>& current,
1467 absl::flat_hash_set<int>* to_clear) {
1468 std::vector<int> new_set;
1469 for (
const int c : *to_clear) {
1470 if (current.contains(c)) new_set.push_back(c);
1473 for (
const int c : new_set) to_clear->insert(c);
1478 bool CpModelPresolver::PropagateDomainsInLinear(
int c, ConstraintProto*
ct) {
1479 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1487 const int num_vars =
ct->linear().vars_size();
1488 term_domains.resize(num_vars + 1);
1489 left_domains.resize(num_vars + 1);
1490 left_domains[0] = Domain(0);
1491 for (
int i = 0; i < num_vars; ++i) {
1492 const int var =
ct->linear().vars(i);
1493 const int64 coeff =
ct->linear().coeffs(i);
1496 left_domains[i + 1] =
1499 const Domain& implied_rhs = left_domains[num_vars];
1503 if (implied_rhs.IsIncludedIn(old_rhs)) {
1505 return RemoveConstraint(
ct);
1509 Domain rhs = old_rhs.SimplifyUsingImpliedDomain(implied_rhs);
1510 if (rhs.IsEmpty()) {
1512 return MarkConstraintAsFalse(
ct);
1514 if (rhs != old_rhs) {
1522 bool is_le_constraint = IsLeConstraint(rhs, implied_rhs);
1523 bool is_ge_constraint = IsGeConstraint(rhs, implied_rhs);
1526 if (
ct->enforcement_literal().size() > 1)
return false;
1528 bool new_bounds =
false;
1529 bool recanonicalize =
false;
1530 Domain negated_rhs = rhs.Negation();
1531 Domain right_domain(0);
1533 Domain implied_term_domain;
1534 term_domains[num_vars] = Domain(0);
1535 for (
int i = num_vars - 1; i >= 0; --i) {
1536 const int var =
ct->linear().vars(i);
1537 const int64 var_coeff =
ct->linear().coeffs(i);
1539 right_domain.AdditionWith(term_domains[i + 1]).RelaxIfTooComplex();
1540 implied_term_domain = left_domains[i].AdditionWith(right_domain);
1541 new_domain = implied_term_domain.AdditionWith(negated_rhs)
1542 .InverseMultiplicationBy(-var_coeff);
1544 if (
ct->enforcement_literal().empty()) {
1549 }
else if (
ct->enforcement_literal().size() == 1) {
1560 recanonicalize =
true;
1564 if (is_le_constraint || is_ge_constraint) {
1565 CHECK_NE(is_le_constraint, is_ge_constraint);
1566 if ((var_coeff > 0) == is_ge_constraint) {
1581 const bool is_in_objective =
1585 const int64 obj_coeff =
1594 if (obj_coeff <= 0 &&
1604 recanonicalize =
true;
1608 if (obj_coeff >= 0 &&
1618 recanonicalize =
true;
1626 if (!
ct->enforcement_literal().empty())
continue;
1638 if (rhs.Min() != rhs.Max() &&
1642 if ((var_coeff > 0 == obj_coeff > 0) &&
1643 RhsCanBeFixedToMin(var_coeff, context_->
DomainOf(
var),
1644 implied_term_domain, rhs)) {
1645 rhs = Domain(rhs.Min());
1648 if ((var_coeff > 0 != obj_coeff > 0) &&
1649 RhsCanBeFixedToMax(var_coeff, context_->
DomainOf(
var),
1650 implied_term_domain, rhs)) {
1651 rhs = Domain(rhs.Max());
1657 negated_rhs = rhs.Negation();
1661 right_domain = Domain(0);
1665 is_le_constraint =
false;
1666 is_ge_constraint =
false;
1667 for (
const int var :
ct->linear().vars()) {
1684 if (
ct->linear().vars().size() <= 2)
continue;
1689 if (rhs.Min() != rhs.Max())
continue;
1695 if (context_->
DomainOf(
var) != new_domain)
continue;
1696 if (std::abs(var_coeff) != 1)
continue;
1697 if (options_.
parameters.presolve_substitution_level() <= 0)
continue;
1703 bool is_in_objective =
false;
1705 is_in_objective =
true;
1711 if (is_in_objective) col_size--;
1712 const int row_size =
ct->linear().vars_size();
1716 const int num_entries_added = (row_size - 1) * (col_size - 1);
1717 const int num_entries_removed = col_size + row_size - 1;
1719 if (num_entries_added > num_entries_removed) {
1725 std::vector<int> others;
1734 if (context_->
working_model->constraints(c).constraint_case() !=
1735 ConstraintProto::ConstraintCase::kLinear) {
1739 for (
const int ref :
1740 context_->
working_model->constraints(c).enforcement_literal()) {
1746 others.push_back(c);
1748 if (abort)
continue;
1751 for (
const int c : others) {
1764 if (is_in_objective) {
1769 absl::StrCat(
"linear: variable substitution ", others.size()));
1776 return RemoveConstraint(
ct);
1781 if (recanonicalize)
return CanonicalizeLinear(
ct);
1792 void CpModelPresolver::ExtractEnforcementLiteralFromLinearConstraint(
1793 int ct_index, ConstraintProto*
ct) {
1794 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1799 const LinearConstraintProto& arg =
ct->linear();
1800 const int num_vars = arg.vars_size();
1804 if (num_vars <= 1)
return;
1808 int64 max_coeff_magnitude = 0;
1809 for (
int i = 0; i < num_vars; ++i) {
1810 const int ref = arg.vars(i);
1811 const int64 coeff = arg.coeffs(i);
1812 const int64 term_a = coeff * context_->
MinOf(ref);
1813 const int64 term_b = coeff * context_->
MaxOf(ref);
1814 max_coeff_magnitude =
std::max(max_coeff_magnitude, std::abs(coeff));
1815 min_sum +=
std::min(term_a, term_b);
1816 max_sum +=
std::max(term_a, term_b);
1825 const auto& domain =
ct->linear().domain();
1826 const int64 ub_threshold = domain[domain.size() - 2] - min_sum;
1827 const int64 lb_threshold = max_sum - domain[1];
1829 if (max_coeff_magnitude <
std::max(ub_threshold, lb_threshold))
return;
1848 const bool lower_bounded = min_sum < rhs_domain.Min();
1849 const bool upper_bounded = max_sum > rhs_domain.Max();
1850 if (!lower_bounded && !upper_bounded)
return;
1851 if (lower_bounded && upper_bounded) {
1853 ConstraintProto* new_ct1 = context_->
working_model->add_constraints();
1855 if (!
ct->name().empty()) {
1856 new_ct1->set_name(absl::StrCat(
ct->name(),
" (part 1)"));
1859 new_ct1->mutable_linear());
1861 ConstraintProto* new_ct2 = context_->
working_model->add_constraints();
1863 if (!
ct->name().empty()) {
1864 new_ct2->set_name(absl::StrCat(
ct->name(),
" (part 2)"));
1867 new_ct2->mutable_linear());
1870 return (
void)RemoveConstraint(
ct);
1876 const int64 threshold = lower_bounded ? ub_threshold : lb_threshold;
1879 const bool only_booleans =
1880 !options_.
parameters.presolve_extract_integer_enforcement();
1885 int64 rhs_offset = 0;
1886 bool some_integer_encoding_were_extracted =
false;
1887 LinearConstraintProto* mutable_arg =
ct->mutable_linear();
1888 for (
int i = 0; i < arg.vars_size(); ++i) {
1889 int ref = arg.vars(i);
1890 int64 coeff = arg.coeffs(i);
1897 if (context_->
IsFixed(ref) || coeff < threshold ||
1898 (only_booleans && !is_boolean)) {
1900 mutable_arg->set_vars(new_size, mutable_arg->vars(i));
1901 mutable_arg->set_coeffs(new_size, mutable_arg->coeffs(i));
1909 some_integer_encoding_were_extracted =
true;
1911 "linear: extracted integer enforcement literal");
1913 if (lower_bounded) {
1914 ct->add_enforcement_literal(is_boolean
1917 ref, context_->
MinOf(ref)));
1918 rhs_offset -= coeff * context_->
MinOf(ref);
1920 ct->add_enforcement_literal(is_boolean
1923 ref, context_->
MaxOf(ref)));
1924 rhs_offset -= coeff * context_->
MaxOf(ref);
1927 mutable_arg->mutable_vars()->Truncate(new_size);
1928 mutable_arg->mutable_coeffs()->Truncate(new_size);
1930 if (some_integer_encoding_were_extracted) {
1936 void CpModelPresolver::ExtractAtMostOneFromLinear(ConstraintProto*
ct) {
1941 const LinearConstraintProto& arg =
ct->linear();
1942 const int num_vars = arg.vars_size();
1945 for (
int i = 0; i < num_vars; ++i) {
1946 const int ref = arg.vars(i);
1947 const int64 coeff = arg.coeffs(i);
1948 const int64 term_a = coeff * context_->
MinOf(ref);
1949 const int64 term_b = coeff * context_->
MaxOf(ref);
1950 min_sum +=
std::min(term_a, term_b);
1951 max_sum +=
std::max(term_a, term_b);
1953 for (
const int type : {0, 1}) {
1954 std::vector<int> at_most_one;
1955 for (
int i = 0; i < num_vars; ++i) {
1956 const int ref = arg.vars(i);
1957 const int64 coeff = arg.coeffs(i);
1958 if (context_->
MinOf(ref) != 0)
continue;
1959 if (context_->
MaxOf(ref) != 1)
continue;
1964 if (min_sum + 2 * std::abs(coeff) > rhs.Max()) {
1965 at_most_one.push_back(coeff > 0 ? ref :
NegatedRef(ref));
1968 if (max_sum - 2 * std::abs(coeff) < rhs.Min()) {
1969 at_most_one.push_back(coeff > 0 ?
NegatedRef(ref) : ref);
1973 if (at_most_one.size() > 1) {
1979 ConstraintProto* new_ct = context_->
working_model->add_constraints();
1980 new_ct->set_name(
ct->name());
1981 for (
const int ref : at_most_one) {
1982 new_ct->mutable_at_most_one()->add_literals(ref);
1991 bool CpModelPresolver::PresolveLinearOnBooleans(ConstraintProto*
ct) {
1992 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1997 const LinearConstraintProto& arg =
ct->linear();
1998 const int num_vars = arg.vars_size();
2000 int64 max_coeff = 0;
2003 for (
int i = 0; i < num_vars; ++i) {
2005 const int var = arg.vars(i);
2006 const int64 coeff = arg.coeffs(i);
2009 if (context_->
MinOf(
var) != 0)
return false;
2010 if (context_->
MaxOf(
var) != 1)
return false;
2014 min_coeff =
std::min(min_coeff, coeff);
2015 max_coeff =
std::max(max_coeff, coeff);
2019 min_coeff =
std::min(min_coeff, -coeff);
2020 max_coeff =
std::max(max_coeff, -coeff);
2032 if ((!rhs_domain.Contains(min_sum) &&
2033 min_sum + min_coeff > rhs_domain.Max()) ||
2034 (!rhs_domain.Contains(max_sum) &&
2035 max_sum - min_coeff < rhs_domain.Min())) {
2036 context_->
UpdateRuleStats(
"linear: all booleans and trivially false");
2037 return MarkConstraintAsFalse(
ct);
2039 if (Domain(min_sum, max_sum).IsIncludedIn(rhs_domain)) {
2041 return RemoveConstraint(
ct);
2048 DCHECK(!rhs_domain.IsEmpty());
2049 if (min_sum + min_coeff > rhs_domain.Max()) {
2052 const auto copy = arg;
2053 ct->mutable_bool_and()->clear_literals();
2054 for (
int i = 0; i < num_vars; ++i) {
2055 ct->mutable_bool_and()->add_literals(
2056 copy.coeffs(i) > 0 ?
NegatedRef(copy.vars(i)) : copy.vars(i));
2058 return PresolveBoolAnd(
ct);
2059 }
else if (max_sum - min_coeff < rhs_domain.Min()) {
2062 const auto copy = arg;
2063 ct->mutable_bool_and()->clear_literals();
2064 for (
int i = 0; i < num_vars; ++i) {
2065 ct->mutable_bool_and()->add_literals(
2066 copy.coeffs(i) > 0 ? copy.vars(i) :
NegatedRef(copy.vars(i)));
2068 return PresolveBoolAnd(
ct);
2069 }
else if (min_sum + min_coeff >= rhs_domain.Min() &&
2070 rhs_domain.front().end >= max_sum) {
2073 const auto copy = arg;
2074 ct->mutable_bool_or()->clear_literals();
2075 for (
int i = 0; i < num_vars; ++i) {
2076 ct->mutable_bool_or()->add_literals(
2077 copy.coeffs(i) > 0 ? copy.vars(i) :
NegatedRef(copy.vars(i)));
2079 return PresolveBoolOr(
ct);
2080 }
else if (max_sum - min_coeff <= rhs_domain.Max() &&
2081 rhs_domain.back().start <= min_sum) {
2084 const auto copy = arg;
2085 ct->mutable_bool_or()->clear_literals();
2086 for (
int i = 0; i < num_vars; ++i) {
2087 ct->mutable_bool_or()->add_literals(
2088 copy.coeffs(i) > 0 ?
NegatedRef(copy.vars(i)) : copy.vars(i));
2090 return PresolveBoolOr(
ct);
2092 min_sum + max_coeff <= rhs_domain.Max() &&
2093 min_sum + 2 * min_coeff > rhs_domain.Max() &&
2094 rhs_domain.back().start <= min_sum) {
2097 const auto copy = arg;
2098 ct->mutable_at_most_one()->clear_literals();
2099 for (
int i = 0; i < num_vars; ++i) {
2100 ct->mutable_at_most_one()->add_literals(
2101 copy.coeffs(i) > 0 ? copy.vars(i) :
NegatedRef(copy.vars(i)));
2105 max_sum - max_coeff >= rhs_domain.Min() &&
2106 max_sum - 2 * min_coeff < rhs_domain.Min() &&
2107 rhs_domain.front().end >= max_sum) {
2110 const auto copy = arg;
2111 ct->mutable_at_most_one()->clear_literals();
2112 for (
int i = 0; i < num_vars; ++i) {
2113 ct->mutable_at_most_one()->add_literals(
2114 copy.coeffs(i) > 0 ?
NegatedRef(copy.vars(i)) : copy.vars(i));
2118 min_sum < rhs_domain.Min() &&
2119 min_sum + min_coeff >= rhs_domain.Min() &&
2120 min_sum + 2 * min_coeff > rhs_domain.Max() &&
2121 min_sum + max_coeff <= rhs_domain.Max()) {
2123 ConstraintProto* at_least_one = context_->
working_model->add_constraints();
2124 ConstraintProto* at_most_one = context_->
working_model->add_constraints();
2125 at_least_one->set_name(
ct->name());
2126 at_most_one->set_name(
ct->name());
2127 for (
int i = 0; i < num_vars; ++i) {
2128 at_least_one->mutable_bool_or()->add_literals(
2129 arg.coeffs(i) > 0 ? arg.vars(i) :
NegatedRef(arg.vars(i)));
2130 at_most_one->mutable_at_most_one()->add_literals(
2131 arg.coeffs(i) > 0 ? arg.vars(i) :
NegatedRef(arg.vars(i)));
2134 return RemoveConstraint(
ct);
2136 max_sum > rhs_domain.Max() &&
2137 max_sum - min_coeff <= rhs_domain.Max() &&
2138 max_sum - 2 * min_coeff < rhs_domain.Min() &&
2139 max_sum - max_coeff >= rhs_domain.Min()) {
2141 ConstraintProto* at_least_one = context_->
working_model->add_constraints();
2142 ConstraintProto* at_most_one = context_->
working_model->add_constraints();
2143 at_least_one->set_name(
ct->name());
2144 at_most_one->set_name(
ct->name());
2145 for (
int i = 0; i < num_vars; ++i) {
2146 at_least_one->mutable_bool_or()->add_literals(
2147 arg.coeffs(i) > 0 ?
NegatedRef(arg.vars(i)) : arg.vars(i));
2148 at_most_one->mutable_at_most_one()->add_literals(
2149 arg.coeffs(i) > 0 ?
NegatedRef(arg.vars(i)) : arg.vars(i));
2152 return RemoveConstraint(
ct);
2159 if (num_vars > 3)
return false;
2164 const int max_mask = (1 << arg.vars_size());
2165 for (
int mask = 0; mask < max_mask; ++mask) {
2167 for (
int i = 0; i < num_vars; ++i) {
2168 if ((mask >> i) & 1)
value += arg.coeffs(i);
2170 if (rhs_domain.Contains(
value))
continue;
2173 ConstraintProto* new_ct = context_->
working_model->add_constraints();
2174 auto* new_arg = new_ct->mutable_bool_or();
2176 *new_ct->mutable_enforcement_literal() =
ct->enforcement_literal();
2178 for (
int i = 0; i < num_vars; ++i) {
2179 new_arg->add_literals(((mask >> i) & 1) ?
NegatedRef(arg.vars(i))
2185 return RemoveConstraint(
ct);
2188 bool CpModelPresolver::PresolveInterval(
int c, ConstraintProto*
ct) {
2191 const int start =
ct->interval().start();
2192 const int end =
ct->interval().end();
2193 const int size =
ct->interval().size();
2195 if (
ct->enforcement_literal().empty()) {
2196 bool changed =
false;
2197 const Domain start_domain = context_->
DomainOf(start);
2198 const Domain end_domain = context_->
DomainOf(end);
2199 const Domain size_domain = context_->
DomainOf(size);
2206 end, start_domain.AdditionWith(size_domain), &changed)) {
2210 start, end_domain.AdditionWith(size_domain.Negation()), &changed)) {
2214 size, end_domain.AdditionWith(start_domain.Negation()), &changed)) {
2224 ConstraintProto* new_ct = context_->
working_model->add_constraints();
2225 *(new_ct->mutable_enforcement_literal()) =
ct->enforcement_literal();
2226 new_ct->mutable_linear()->add_domain(0);
2227 new_ct->mutable_linear()->add_domain(0);
2228 new_ct->mutable_linear()->add_vars(start);
2229 new_ct->mutable_linear()->add_coeffs(1);
2230 new_ct->mutable_linear()->add_vars(size);
2231 new_ct->mutable_linear()->add_coeffs(1);
2232 new_ct->mutable_linear()->add_vars(end);
2233 new_ct->mutable_linear()->add_coeffs(-1);
2237 return RemoveConstraint(
ct);
2245 if ( (
false) &&
ct->enforcement_literal().empty() &&
2248 1, context_->
MinOf(size));
2255 bool CpModelPresolver::PresolveElement(ConstraintProto*
ct) {
2258 const int index_ref =
ct->element().index();
2259 const int target_ref =
ct->element().target();
2265 bool all_constants =
true;
2266 absl::flat_hash_set<int64> constant_set;
2267 bool all_included_in_target_domain =
true;
2270 bool reduced_index_domain =
false;
2272 Domain(0,
ct->element().vars_size() - 1),
2273 &reduced_index_domain)) {
2279 Domain infered_domain;
2280 const Domain& initial_index_domain = context_->
DomainOf(index_ref);
2281 const Domain& target_domain = context_->
DomainOf(target_ref);
2282 for (
const ClosedInterval
interval : initial_index_domain) {
2286 const int ref =
ct->element().vars(
value);
2287 const Domain& domain = context_->
DomainOf(ref);
2288 if (domain.IntersectionWith(target_domain).IsEmpty()) {
2289 bool domain_modified =
false;
2291 index_ref, Domain(
value).Complement(), &domain_modified)) {
2294 reduced_index_domain =
true;
2297 if (domain.IsFixed()) {
2298 constant_set.insert(domain.Min());
2300 all_constants =
false;
2302 if (!domain.IsIncludedIn(target_domain)) {
2303 all_included_in_target_domain =
false;
2305 infered_domain = infered_domain.
UnionWith(domain);
2309 if (reduced_index_domain) {
2312 bool domain_modified =
false;
2314 &domain_modified)) {
2317 if (domain_modified) {
2323 if (context_->
IsFixed(index_ref)) {
2324 const int var =
ct->element().vars(context_->
MinOf(index_ref));
2325 if (
var != target_ref) {
2326 LinearConstraintProto*
const lin =
2327 context_->
working_model->add_constraints()->mutable_linear();
2329 lin->add_coeffs(-1);
2330 lin->add_vars(target_ref);
2337 return RemoveConstraint(
ct);
2343 if (all_constants && constant_set.size() == 1) {
2346 return RemoveConstraint(
ct);
2351 if (context_->
MinOf(index_ref) == 0 && context_->
MaxOf(index_ref) == 1 &&
2353 const int64 v0 = context_->
MinOf(
ct->element().vars(0));
2354 const int64 v1 = context_->
MinOf(
ct->element().vars(1));
2356 LinearConstraintProto*
const lin =
2357 context_->
working_model->add_constraints()->mutable_linear();
2358 lin->add_vars(target_ref);
2360 lin->add_vars(index_ref);
2361 lin->add_coeffs(v0 - v1);
2362 lin->add_domain(v0);
2363 lin->add_domain(v0);
2365 context_->
UpdateRuleStats(
"element: linearize constant element of size 2");
2366 return RemoveConstraint(
ct);
2370 const AffineRelation::Relation r_index =
2372 if (r_index.representative != index_ref) {
2374 if (context_->
DomainOf(r_index.representative).
Size() >
2382 const int array_size =
ct->element().vars_size();
2384 context_->
UpdateRuleStats(
"TODO element: representative has bad domain");
2385 }
else if (r_index.offset >= 0 && r_index.offset < array_size &&
2386 r_index.offset + r_max * r_index.coeff >= 0 &&
2387 r_index.offset + r_max * r_index.coeff < array_size) {
2389 ElementConstraintProto*
const element =
2390 context_->
working_model->add_constraints()->mutable_element();
2391 for (
int64 v = 0; v <= r_max; ++v) {
2392 const int64 scaled_index = v * r_index.coeff + r_index.offset;
2394 CHECK_LT(scaled_index, array_size);
2395 element->add_vars(
ct->element().vars(scaled_index));
2397 element->set_index(r_ref);
2398 element->set_target(target_ref);
2400 if (r_index.coeff == 1) {
2406 return RemoveConstraint(
ct);
2412 if (all_constants && unique_index) {
2416 context_->
UpdateRuleStats(
"element: trivial target domain reduction");
2419 return RemoveConstraint(
ct);
2422 const bool unique_target =
2424 context_->
IsFixed(target_ref);
2425 if (all_included_in_target_domain && unique_target) {
2429 return RemoveConstraint(
ct);
2432 if (target_ref == index_ref) {
2434 std::vector<int64> possible_indices;
2435 const Domain& index_domain = context_->
DomainOf(index_ref);
2436 for (
const ClosedInterval&
interval : index_domain) {
2438 const int ref =
ct->element().vars(
value);
2440 possible_indices.push_back(
value);
2444 if (possible_indices.size() < index_domain.Size()) {
2451 "element: reduce index domain when target equals index");
2456 if (unique_target && !context_->
IsFixed(target_ref)) {
2466 bool CpModelPresolver::PresolveTable(ConstraintProto*
ct) {
2469 if (
ct->table().vars().empty()) {
2471 return RemoveConstraint(
ct);
2477 const int num_vars =
ct->table().vars_size();
2478 const int num_tuples =
ct->table().values_size() / num_vars;
2479 std::vector<int64> tuple(num_vars);
2480 std::vector<std::vector<int64>> new_tuples;
2481 new_tuples.reserve(num_tuples);
2482 std::vector<absl::flat_hash_set<int64>> new_domains(num_vars);
2483 std::vector<AffineRelation::Relation> affine_relations;
2485 absl::flat_hash_set<int> visited;
2486 for (
const int ref :
ct->table().vars()) {
2494 bool modified_variables =
false;
2495 for (
int v = 0; v < num_vars; ++v) {
2496 const int ref =
ct->table().vars(v);
2498 affine_relations.push_back(r);
2499 if (r.representative != ref) {
2500 modified_variables =
true;
2504 for (
int i = 0; i < num_tuples; ++i) {
2505 bool delete_row =
false;
2507 for (
int j = 0; j < num_vars; ++j) {
2508 const int ref =
ct->table().vars(j);
2509 int64 v =
ct->table().values(i * num_vars + j);
2510 const AffineRelation::Relation& r = affine_relations[j];
2511 if (r.representative != ref) {
2512 const int64 inverse_value = (v - r.offset) / r.coeff;
2513 if (inverse_value * r.coeff + r.offset != v) {
2526 if (delete_row)
continue;
2527 new_tuples.push_back(tuple);
2528 for (
int j = 0; j < num_vars; ++j) {
2529 const int64 v = tuple[j];
2530 new_domains[j].insert(v);
2536 if (new_tuples.size() < num_tuples || modified_variables) {
2537 ct->mutable_table()->clear_values();
2538 for (
const std::vector<int64>& t : new_tuples) {
2539 for (
const int64 v : t) {
2540 ct->mutable_table()->add_values(v);
2543 if (new_tuples.size() < num_tuples) {
2548 if (modified_variables) {
2549 for (
int j = 0; j < num_vars; ++j) {
2550 const AffineRelation::Relation& r = affine_relations[j];
2551 if (r.representative !=
ct->table().vars(j)) {
2552 ct->mutable_table()->set_vars(j, r.representative);
2556 "table: replace variable by canonical affine one");
2560 if (
ct->table().negated())
return modified_variables;
2563 bool changed =
false;
2564 for (
int j = 0; j < num_vars; ++j) {
2565 const int ref =
ct->table().vars(j);
2569 new_domains[j].end())),
2577 if (num_vars == 1) {
2580 return RemoveConstraint(
ct);
2585 for (
int j = 0; j < num_vars; ++j) prod *= new_domains[j].size();
2586 if (prod == new_tuples.size()) {
2588 return RemoveConstraint(
ct);
2594 if (new_tuples.size() > 0.7 * prod) {
2596 std::vector<std::vector<int64>> var_to_values(num_vars);
2597 for (
int j = 0; j < num_vars; ++j) {
2598 var_to_values[j].assign(new_domains[j].begin(), new_domains[j].end());
2600 std::vector<std::vector<int64>> all_tuples(prod);
2601 for (
int i = 0; i < prod; ++i) {
2602 all_tuples[i].resize(num_vars);
2604 for (
int j = 0; j < num_vars; ++j) {
2605 all_tuples[i][j] = var_to_values[j][
index % var_to_values[j].size()];
2606 index /= var_to_values[j].size();
2612 std::vector<std::vector<int64>> diff(prod - new_tuples.size());
2613 std::set_difference(all_tuples.begin(), all_tuples.end(),
2614 new_tuples.begin(), new_tuples.end(), diff.begin());
2617 ct->mutable_table()->set_negated(!
ct->table().negated());
2618 ct->mutable_table()->clear_values();
2619 for (
const std::vector<int64>& t : diff) {
2620 for (
const int64 v : t)
ct->mutable_table()->add_values(v);
2624 return modified_variables;
2627 bool CpModelPresolver::PresolveAllDiff(ConstraintProto*
ct) {
2631 AllDifferentConstraintProto& all_diff = *
ct->mutable_all_diff();
2633 bool constraint_has_changed =
false;
2635 const int size = all_diff.vars_size();
2638 return RemoveConstraint(
ct);
2642 return RemoveConstraint(
ct);
2645 bool something_was_propagated =
false;
2646 std::vector<int> new_variables;
2647 for (
int i = 0; i < size; ++i) {
2648 if (!context_->
IsFixed(all_diff.vars(i))) {
2649 new_variables.push_back(all_diff.vars(i));
2654 bool propagated =
false;
2655 for (
int j = 0; j < size; ++j) {
2656 if (i == j)
continue;
2659 Domain(
value).Complement())) {
2667 something_was_propagated =
true;
2671 std::sort(new_variables.begin(), new_variables.end());
2672 for (
int i = 1; i < new_variables.size(); ++i) {
2673 if (new_variables[i] == new_variables[i - 1]) {
2675 "Duplicate variable in all_diff");
2679 if (new_variables.size() < all_diff.vars_size()) {
2680 all_diff.mutable_vars()->Clear();
2681 for (
const int var : new_variables) {
2682 all_diff.add_vars(
var);
2685 something_was_propagated =
true;
2686 constraint_has_changed =
true;
2687 if (new_variables.size() <= 1)
continue;
2692 Domain domain = context_->
DomainOf(all_diff.vars(0));
2693 for (
int i = 1; i < all_diff.vars_size(); ++i) {
2696 if (all_diff.vars_size() == domain.Size()) {
2697 absl::flat_hash_map<int64, std::vector<int>> value_to_refs;
2698 for (
const int ref : all_diff.vars()) {
2701 value_to_refs[v].push_back(ref);
2705 bool propagated =
false;
2706 for (
const auto& it : value_to_refs) {
2707 if (it.second.size() == 1 &&
2709 const int ref = it.second.
front();
2718 "all_diff: propagated mandatory values in permutation");
2719 something_was_propagated =
true;
2722 if (!something_was_propagated)
break;
2725 return constraint_has_changed;
2732 std::vector<int> GetLiteralsFromSetPPCConstraint(ConstraintProto*
ct) {
2733 std::vector<int> sorted_literals;
2734 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne) {
2735 for (
const int literal :
ct->at_most_one().literals()) {
2736 sorted_literals.push_back(
literal);
2738 }
else if (
ct->constraint_case() ==
2739 ConstraintProto::ConstraintCase::kBoolOr) {
2740 for (
const int literal :
ct->bool_or().literals()) {
2741 sorted_literals.push_back(
literal);
2744 std::sort(sorted_literals.begin(), sorted_literals.end());
2745 return sorted_literals;
2750 void AddImplication(
int lhs,
int rhs, CpModelProto*
proto,
2751 absl::flat_hash_map<int, int>* ref_to_bool_and) {
2752 if (ref_to_bool_and->contains(lhs)) {
2753 const int ct_index = (*ref_to_bool_and)[lhs];
2754 proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(rhs);
2755 }
else if (ref_to_bool_and->contains(
NegatedRef(rhs))) {
2756 const int ct_index = (*ref_to_bool_and)[
NegatedRef(rhs)];
2757 proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(
2760 (*ref_to_bool_and)[lhs] =
proto->constraints_size();
2761 ConstraintProto*
ct =
proto->add_constraints();
2762 ct->add_enforcement_literal(lhs);
2763 ct->mutable_bool_and()->add_literals(rhs);
2767 template <
typename ClauseContainer>
2768 void ExtractClauses(
bool use_bool_and,
const ClauseContainer& container,
2769 CpModelProto*
proto) {
2776 absl::flat_hash_map<int, int> ref_to_bool_and;
2777 for (
int i = 0; i < container.NumClauses(); ++i) {
2778 const std::vector<Literal>& clause = container.Clause(i);
2779 if (clause.empty())
continue;
2782 if (use_bool_and && clause.size() == 2) {
2783 const int a = clause[0].IsPositive()
2784 ? clause[0].Variable().value()
2786 const int b = clause[1].IsPositive()
2787 ? clause[1].Variable().value()
2794 ConstraintProto*
ct =
proto->add_constraints();
2795 for (
const Literal l : clause) {
2796 if (l.IsPositive()) {
2797 ct->mutable_bool_or()->add_literals(l.Variable().value());
2799 ct->mutable_bool_or()->add_literals(
NegatedRef(l.Variable().value()));
2807 bool CpModelPresolver::PresolveNoOverlap(ConstraintProto*
ct) {
2810 const NoOverlapConstraintProto&
proto =
ct->no_overlap();
2814 for (
int i = 0; i <
proto.intervals_size(); ++i) {
2815 const int interval_index =
proto.intervals(i);
2817 .constraint_case() ==
2818 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
2821 ct->mutable_no_overlap()->set_intervals(new_size++, interval_index);
2823 ct->mutable_no_overlap()->mutable_intervals()->Truncate(new_size);
2827 ct->mutable_no_overlap()->mutable_intervals()->begin(),
2828 ct->mutable_no_overlap()->mutable_intervals()->end(),
2829 [
this](
int i1,
int i2) {
2830 return context_->MinOf(context_->working_model->constraints(i1)
2834 context_->working_model->constraints(i2).interval().start());
2843 for (
int i = 0; i <
proto.intervals_size(); ++i) {
2844 const int interval_index =
proto.intervals(i);
2845 const IntervalConstraintProto&
interval =
2846 context_->
working_model->constraints(interval_index).interval();
2847 const int64 end_max_of_previous_intervals = end_max_so_far;
2849 if (context_->
MinOf(
interval.start()) >= end_max_of_previous_intervals &&
2850 (i + 1 ==
proto.intervals_size() ||
2859 ct->mutable_no_overlap()->set_intervals(new_size++, interval_index);
2861 ct->mutable_no_overlap()->mutable_intervals()->Truncate(new_size);
2863 if (
proto.intervals_size() == 1) {
2865 return RemoveConstraint(
ct);
2867 if (
proto.intervals().empty()) {
2869 return RemoveConstraint(
ct);
2874 bool CpModelPresolver::PresolveCumulative(ConstraintProto*
ct) {
2877 const CumulativeConstraintProto&
proto =
ct->cumulative();
2881 bool changed =
false;
2882 int num_zero_demand_removed = 0;
2883 for (
int i = 0; i <
proto.intervals_size(); ++i) {
2885 .constraint_case() ==
2886 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
2890 const int demand_ref =
proto.demands(i);
2891 const int64 demand_max = context_->
MaxOf(demand_ref);
2892 if (demand_max == 0) {
2893 num_zero_demand_removed++;
2897 ct->mutable_cumulative()->set_intervals(new_size,
proto.intervals(i));
2898 ct->mutable_cumulative()->set_demands(new_size,
proto.demands(i));
2901 if (new_size <
proto.intervals_size()) {
2903 ct->mutable_cumulative()->mutable_intervals()->Truncate(new_size);
2904 ct->mutable_cumulative()->mutable_demands()->Truncate(new_size);
2907 if (num_zero_demand_removed > 0) {
2908 context_->
UpdateRuleStats(
"cumulative: removed intervals with no demands");
2911 if (new_size == 0) {
2913 return RemoveConstraint(
ct);
2917 if (!context_->
IsFixed(
proto.capacity()))
return changed;
2920 const int size =
proto.intervals_size();
2921 std::vector<int> start_indices(size, -1);
2923 int num_duration_one = 0;
2924 int num_greater_half_capacity = 0;
2926 bool has_optional_interval =
false;
2927 for (
int i = 0; i < size; ++i) {
2929 const ConstraintProto&
ct =
2931 if (!
ct.enforcement_literal().empty()) has_optional_interval =
true;
2932 const IntervalConstraintProto&
interval =
ct.interval();
2933 start_indices[i] =
interval.start();
2934 const int duration_ref =
interval.size();
2935 const int demand_ref =
proto.demands(i);
2936 if (context_->
IsFixed(duration_ref) && context_->
MinOf(duration_ref) == 1) {
2939 if (context_->
MinOf(duration_ref) == 0) {
2944 const int64 demand_min = context_->
MinOf(demand_ref);
2945 const int64 demand_max = context_->
MaxOf(demand_ref);
2947 num_greater_half_capacity++;
2951 if (
ct.enforcement_literal().empty()) {
2954 CHECK_EQ(
ct.enforcement_literal().size(), 1);
2960 }
else if (demand_max >
capacity) {
2961 if (
ct.enforcement_literal().empty()) {
2962 context_->
UpdateRuleStats(
"cumulative: demand_max exceeds capacity.");
2971 "cumulative: demand_max of optional interval exceeds capacity.");
2977 if (num_greater_half_capacity == size) {
2978 if (num_duration_one == size && !has_optional_interval) {
2980 ConstraintProto* new_ct = context_->
working_model->add_constraints();
2981 auto* arg = new_ct->mutable_all_diff();
2982 for (
const int var : start_indices) {
2986 return RemoveConstraint(
ct);
2989 ConstraintProto* new_ct = context_->
working_model->add_constraints();
2990 auto* arg = new_ct->mutable_no_overlap();
2995 return RemoveConstraint(
ct);
3002 bool CpModelPresolver::PresolveRoutes(ConstraintProto*
ct) {
3005 RoutesConstraintProto&
proto = *
ct->mutable_routes();
3008 const int num_arcs =
proto.literals_size();
3009 for (
int i = 0; i < num_arcs; ++i) {
3010 const int ref =
proto.literals(i);
3017 proto.set_literals(new_size, ref);
3022 if (new_size < num_arcs) {
3023 proto.mutable_literals()->Truncate(new_size);
3024 proto.mutable_tails()->Truncate(new_size);
3025 proto.mutable_heads()->Truncate(new_size);
3031 bool CpModelPresolver::PresolveCircuit(ConstraintProto*
ct) {
3034 CircuitConstraintProto&
proto = *
ct->mutable_circuit();
3038 ct->mutable_circuit()->mutable_heads());
3042 std::vector<std::vector<int>> incoming_arcs;
3043 std::vector<std::vector<int>> outgoing_arcs;
3045 const int num_arcs =
proto.literals_size();
3046 for (
int i = 0; i < num_arcs; ++i) {
3047 const int ref =
proto.literals(i);
3055 incoming_arcs[
head].push_back(ref);
3056 outgoing_arcs[
tail].push_back(ref);
3064 bool loop_again =
true;
3065 int num_fixed_at_true = 0;
3066 while (loop_again) {
3068 for (
const auto* node_to_refs : {&incoming_arcs, &outgoing_arcs}) {
3069 for (
const std::vector<int>& refs : *node_to_refs) {
3070 if (refs.size() == 1) {
3072 ++num_fixed_at_true;
3081 for (
const int ref : refs) {
3091 if (num_true == 1) {
3092 for (
const int ref : refs) {
3093 if (ref != true_ref) {
3094 if (!context_->
IsFixed(ref)) {
3105 if (num_fixed_at_true > 0) {
3112 int circuit_start = -1;
3113 std::vector<int>
next(num_nodes, -1);
3114 std::vector<int> new_in_degree(num_nodes, 0);
3115 std::vector<int> new_out_degree(num_nodes, 0);
3116 for (
int i = 0; i < num_arcs; ++i) {
3117 const int ref =
proto.literals(i);
3125 circuit_start =
proto.tails(i);
3129 ++new_out_degree[
proto.tails(i)];
3130 ++new_in_degree[
proto.heads(i)];
3133 proto.set_literals(new_size,
proto.literals(i));
3143 for (
int i = 0; i < num_nodes; ++i) {
3145 if (incoming_arcs[i].empty() && outgoing_arcs[i].empty())
continue;
3147 if (new_in_degree[i] == 0 || new_out_degree[i] == 0) {
3153 if (circuit_start != -1) {
3154 std::vector<bool> visited(num_nodes,
false);
3155 int current = circuit_start;
3156 while (current != -1 && !visited[current]) {
3157 visited[current] =
true;
3158 current =
next[current];
3160 if (current == circuit_start) {
3163 for (
int i = 0; i < num_arcs; ++i) {
3164 if (visited[
proto.tails(i)])
continue;
3172 return RemoveConstraint(
ct);
3176 if (num_true == new_size) {
3178 return RemoveConstraint(
ct);
3184 for (
int i = 0; i < num_nodes; ++i) {
3185 for (
const std::vector<int>* arc_literals :
3186 {&incoming_arcs[i], &outgoing_arcs[i]}) {
3187 std::vector<int> literals;
3188 for (
const int ref : *arc_literals) {
3194 literals.push_back(ref);
3196 if (literals.size() == 2 && literals[0] !=
NegatedRef(literals[1])) {
3205 if (new_size < num_arcs) {
3206 proto.mutable_tails()->Truncate(new_size);
3207 proto.mutable_heads()->Truncate(new_size);
3208 proto.mutable_literals()->Truncate(new_size);
3215 bool CpModelPresolver::PresolveAutomaton(ConstraintProto*
ct) {
3218 AutomatonConstraintProto&
proto = *
ct->mutable_automaton();
3219 if (
proto.vars_size() == 0 ||
proto.transition_label_size() == 0) {
3223 bool all_affine =
true;
3224 std::vector<AffineRelation::Relation> affine_relations;
3225 for (
int v = 0; v <
proto.vars_size(); ++v) {
3226 const int var =
ct->automaton().vars(v);
3228 affine_relations.push_back(r);
3229 if (r.representative ==
var) {
3233 if (v > 0 && (r.coeff != affine_relations[v - 1].coeff ||
3234 r.offset != affine_relations[v - 1].offset)) {
3241 for (
int v = 0; v <
proto.vars_size(); ++v) {
3244 const AffineRelation::Relation rep = affine_relations.front();
3246 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
3247 const int64 label =
proto.transition_label(t);
3248 int64 inverse_label = (label - rep.offset) / rep.coeff;
3249 if (inverse_label * rep.coeff + rep.offset == label) {
3250 if (new_size != t) {
3251 proto.set_transition_tail(new_size,
proto.transition_tail(t));
3252 proto.set_transition_head(new_size,
proto.transition_head(t));
3254 proto.set_transition_label(new_size, inverse_label);
3258 if (new_size <
proto.transition_tail_size()) {
3259 proto.mutable_transition_tail()->Truncate(new_size);
3260 proto.mutable_transition_label()->Truncate(new_size);
3261 proto.mutable_transition_head()->Truncate(new_size);
3269 for (
int v = 1; v <
proto.vars_size(); ++v) {
3274 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
3275 const int64 label =
proto.transition_label(t);
3276 if (hull.Contains(label)) {
3277 if (new_size != t) {
3278 proto.set_transition_tail(new_size,
proto.transition_tail(t));
3279 proto.set_transition_label(new_size, label);
3280 proto.set_transition_head(new_size,
proto.transition_head(t));
3285 if (new_size <
proto.transition_tail_size()) {
3286 proto.mutable_transition_tail()->Truncate(new_size);
3287 proto.mutable_transition_label()->Truncate(new_size);
3288 proto.mutable_transition_head()->Truncate(new_size);
3293 const int n =
proto.vars_size();
3294 const std::vector<int> vars = {
proto.vars().begin(),
proto.vars().end()};
3297 std::vector<std::set<int64>> reachable_states(n + 1);
3298 reachable_states[0].insert(
proto.starting_state());
3299 reachable_states[n] = {
proto.final_states().begin(),
3300 proto.final_states().end()};
3307 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
3309 const int64 label =
proto.transition_label(t);
3313 reachable_states[
time + 1].insert(
head);
3317 std::vector<std::set<int64>> reached_values(n);
3321 std::set<int64> new_set;
3322 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
3324 const int64 label =
proto.transition_label(t);
3330 new_set.insert(
tail);
3331 reached_values[
time].insert(label);
3333 reachable_states[
time].swap(new_set);
3336 bool removed_values =
false;
3341 {reached_values[time].begin(), reached_values[time].end()}),
3346 if (removed_values) {
3355 void CpModelPresolver::ExtractBoolAnd() {
3356 absl::flat_hash_map<int, int> ref_to_bool_and;
3357 const int num_constraints = context_->
working_model->constraints_size();
3358 std::vector<int> to_remove;
3359 for (
int c = 0; c < num_constraints; ++c) {
3363 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr &&
3364 ct.bool_or().literals().size() == 2) {
3368 to_remove.push_back(c);
3372 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne &&
3373 ct.at_most_one().literals().size() == 2) {
3374 AddImplication(
ct.at_most_one().literals(0),
3377 to_remove.push_back(c);
3383 for (
const int c : to_remove) {
3384 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
3390 void CpModelPresolver::Probe() {
3394 for (
int i = 0; i < context_->
working_model->variables_size(); ++i) {
3414 auto* local_param =
model.GetOrCreate<SatParameters>();
3416 local_param->set_use_implied_bounds(
false);
3418 model.GetOrCreate<TimeLimit>()->MergeWithGlobalTimeLimit(options_.
time_limit);
3419 auto* encoder =
model.GetOrCreate<IntegerEncoder>();
3420 encoder->DisableImplicationBetweenLiteral();
3421 auto* mapping =
model.GetOrCreate<CpModelMapping>();
3425 auto* sat_solver =
model.GetOrCreate<SatSolver>();
3426 for (
const ConstraintProto&
ct :
model_proto.constraints()) {
3427 if (mapping->ConstraintIsAlreadyLoaded(&
ct))
continue;
3429 if (sat_solver->IsModelUnsat()) {
3433 encoder->AddAllImplicationsBetweenAssociatedLiterals();
3434 if (!sat_solver->Propagate()) {
3442 auto* implication_graph =
model.GetOrCreate<BinaryImplicationGraph>();
3443 auto* prober =
model.GetOrCreate<Prober>();
3444 prober->ProbeBooleanVariables(1.0);
3447 model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
3449 if (sat_solver->IsModelUnsat() || !implication_graph->DetectEquivalences()) {
3454 CHECK_EQ(sat_solver->CurrentDecisionLevel(), 0);
3455 for (
int i = 0; i < sat_solver->LiteralTrail().
Index(); ++i) {
3456 const Literal l = sat_solver->LiteralTrail()[i];
3457 const int var = mapping->GetProtoVariableFromBooleanVariable(l.Variable());
3464 const int num_variables = context_->
working_model->variables().size();
3465 auto* integer_trail =
model.GetOrCreate<IntegerTrail>();
3466 for (
int var = 0;
var < num_variables; ++
var) {
3469 if (!mapping->IsBoolean(
var)) {
3472 integer_trail->InitialVariableDomain(mapping->Integer(
var)))) {
3479 const Literal l = mapping->Literal(
var);
3480 const Literal r = implication_graph->RepresentativeOf(l);
3483 mapping->GetProtoVariableFromBooleanVariable(r.Variable());
3491 void CpModelPresolver::PresolvePureSatPart() {
3496 const int num_variables = context_->
working_model->variables_size();
3497 SatPostsolver sat_postsolver(num_variables);
3498 SatPresolver sat_presolver(&sat_postsolver);
3499 sat_presolver.SetNumVariables(num_variables);
3500 sat_presolver.SetTimeLimit(options_.
time_limit);
3509 if (params.cp_model_postsolve_with_full_solver()) {
3510 params.set_presolve_blocked_clause(
false);
3516 params.set_presolve_use_bva(
false);
3517 sat_presolver.SetParameters(params);
3520 absl::flat_hash_set<int> used_variables;
3521 auto convert = [&used_variables](
int ref) {
3523 if (
RefIsPositive(ref))
return Literal(BooleanVariable(ref),
true);
3524 return Literal(BooleanVariable(
NegatedRef(ref)),
false);
3532 for (
int c = 0; c < context_->
working_model->constraints_size(); ++c) {
3534 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr ||
3535 ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolAnd) {
3554 std::vector<Literal> clause;
3555 int num_removed_constraints = 0;
3556 for (
int i = 0; i < context_->
working_model->constraints_size(); ++i) {
3559 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr) {
3560 ++num_removed_constraints;
3562 for (
const int ref :
ct.bool_or().literals()) {
3563 clause.push_back(convert(ref));
3565 for (
const int ref :
ct.enforcement_literal()) {
3566 clause.push_back(convert(ref).Negated());
3568 sat_presolver.AddClause(clause);
3575 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolAnd) {
3576 ++num_removed_constraints;
3577 std::vector<Literal> clause;
3578 for (
const int ref :
ct.enforcement_literal()) {
3579 clause.push_back(convert(ref).Negated());
3582 for (
const int ref :
ct.bool_and().literals()) {
3583 clause.back() = convert(ref);
3584 sat_presolver.AddClause(clause);
3594 if (num_removed_constraints == 0)
return;
3604 std::vector<bool> can_be_removed(num_variables,
false);
3605 for (
int i = 0; i < num_variables; ++i) {
3607 can_be_removed[i] =
true;
3613 if (used_variables.contains(i) && context_->
IsFixed(i)) {
3615 sat_presolver.AddClause({convert(i)});
3617 sat_presolver.AddClause({convert(
NegatedRef(i))});
3625 const int num_passes = params.presolve_use_bva() ? 4 : 1;
3626 for (
int i = 0; i < num_passes; ++i) {
3627 const int old_num_clause = sat_postsolver.NumClauses();
3628 if (!sat_presolver.Presolve(can_be_removed, options_.
log_info)) {
3629 VLOG(1) <<
"UNSAT during SAT presolve.";
3632 if (old_num_clause == sat_postsolver.NumClauses())
break;
3636 const int new_num_variables = sat_presolver.NumVariables();
3637 if (new_num_variables > context_->
working_model->variables_size()) {
3638 VLOG(1) <<
"New variables added by the SAT presolver.";
3640 i < new_num_variables; ++i) {
3641 IntegerVariableProto* var_proto =
3643 var_proto->add_domain(0);
3644 var_proto->add_domain(1);
3650 ExtractClauses(
true, sat_presolver, context_->
working_model);
3658 ExtractClauses(
false, sat_postsolver,
3666 void CpModelPresolver::ExpandObjective() {
3685 int unique_expanded_constraint = -1;
3686 const bool objective_was_a_single_variable =
3691 const int num_variables = context_->
working_model->variables_size();
3692 const int num_constraints = context_->
working_model->constraints_size();
3693 absl::flat_hash_set<int> relevant_constraints;
3694 std::vector<int> var_to_num_relevant_constraints(num_variables, 0);
3695 for (
int ct_index = 0; ct_index < num_constraints; ++ct_index) {
3696 const ConstraintProto&
ct = context_->
working_model->constraints(ct_index);
3698 if (!
ct.enforcement_literal().empty() ||
3699 ct.constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
3700 ct.linear().domain().size() != 2 ||
3701 ct.linear().domain(0) !=
ct.linear().domain(1)) {
3705 relevant_constraints.insert(ct_index);
3706 const int num_terms =
ct.linear().vars_size();
3707 for (
int i = 0; i < num_terms; ++i) {
3708 var_to_num_relevant_constraints[
PositiveRef(
ct.linear().vars(i))]++;
3712 std::set<int> var_to_process;
3714 const int var = entry.first;
3716 if (var_to_num_relevant_constraints[
var] != 0) {
3717 var_to_process.insert(
var);
3722 int num_expansions = 0;
3723 absl::flat_hash_set<int> processed_vars;
3724 std::vector<int> new_vars_in_objective;
3725 while (!relevant_constraints.empty()) {
3727 int objective_var = -1;
3728 while (!var_to_process.empty()) {
3729 const int var = *var_to_process.begin();
3730 CHECK(!processed_vars.contains(
var));
3731 if (var_to_num_relevant_constraints[
var] == 0) {
3732 processed_vars.insert(
var);
3733 var_to_process.erase(
var);
3738 var_to_process.erase(
var);
3741 objective_var =
var;
3745 if (objective_var == -1)
break;
3747 processed_vars.insert(objective_var);
3748 var_to_process.erase(objective_var);
3750 int expanded_linear_index = -1;
3751 int64 objective_coeff_in_expanded_constraint;
3752 int64 size_of_expanded_constraint = 0;
3753 const auto& non_deterministic_list =
3755 std::vector<int> constraints_with_objective(non_deterministic_list.begin(),
3756 non_deterministic_list.end());
3757 std::sort(constraints_with_objective.begin(),
3758 constraints_with_objective.end());
3759 for (
const int ct_index : constraints_with_objective) {
3760 if (relevant_constraints.count(ct_index) == 0)
continue;
3761 const ConstraintProto&
ct =
3766 relevant_constraints.erase(ct_index);
3767 const int num_terms =
ct.linear().vars_size();
3768 for (
int i = 0; i < num_terms; ++i) {
3769 var_to_num_relevant_constraints[
PositiveRef(
ct.linear().vars(i))]--;
3781 bool is_present =
false;
3782 int64 objective_coeff;
3783 for (
int i = 0; i < num_terms; ++i) {
3784 const int ref =
ct.linear().vars(i);
3785 const int64 coeff =
ct.linear().coeffs(i);
3787 CHECK(!is_present) <<
"Duplicate variables not supported.";
3789 objective_coeff = (ref == objective_var) ? coeff : -coeff;
3802 if (std::abs(objective_coeff) == 1 &&
3803 num_terms > size_of_expanded_constraint) {
3804 expanded_linear_index = ct_index;
3805 size_of_expanded_constraint = num_terms;
3806 objective_coeff_in_expanded_constraint = objective_coeff;
3810 if (expanded_linear_index != -1) {
3811 context_->
UpdateRuleStats(
"objective: expanded objective constraint.");
3815 CHECK_EQ(std::abs(objective_coeff_in_expanded_constraint), 1);
3816 const ConstraintProto&
ct =
3817 context_->
working_model->constraints(expanded_linear_index);
3819 objective_var, objective_coeff_in_expanded_constraint,
ct,
3820 &new_vars_in_objective);
3823 for (
const int var : new_vars_in_objective) {
3824 if (!processed_vars.contains(
var)) var_to_process.insert(
var);
3837 for (
int i = 0; i < size_of_expanded_constraint; ++i) {
3838 const int ref =
ct.linear().vars(i);
3843 -
ct.linear().coeffs(i)))
3844 .RelaxIfTooComplex();
3846 implied_domain = implied_domain.InverseMultiplicationBy(
3847 objective_coeff_in_expanded_constraint);
3851 if (implied_domain.IsIncludedIn(context_->
DomainOf(objective_var))) {
3852 context_->
UpdateRuleStats(
"objective: removed objective constraint.");
3854 context_->
working_model->mutable_constraints(expanded_linear_index)
3858 unique_expanded_constraint = expanded_linear_index;
3868 if (num_expansions == 1 && objective_was_a_single_variable &&
3869 unique_expanded_constraint != -1) {
3871 "objective: removed unique objective constraint.");
3872 ConstraintProto* mutable_ct = context_->
working_model->mutable_constraints(
3873 unique_expanded_constraint);
3874 *(context_->
mapping_model->add_constraints()) = *mutable_ct;
3875 mutable_ct->Clear();
3887 void CpModelPresolver::MergeNoOverlapConstraints() {
3890 const int num_constraints = context_->
working_model->constraints_size();
3891 int old_num_no_overlaps = 0;
3892 int old_num_intervals = 0;
3895 std::vector<int> disjunctive_index;
3896 std::vector<std::vector<Literal>> cliques;
3897 for (
int c = 0; c < num_constraints; ++c) {
3899 if (
ct.constraint_case() != ConstraintProto::ConstraintCase::kNoOverlap) {
3902 std::vector<Literal> clique;
3903 for (
const int i :
ct.no_overlap().intervals()) {
3904 clique.push_back(Literal(BooleanVariable(i),
true));
3906 cliques.push_back(clique);
3907 disjunctive_index.push_back(c);
3909 old_num_no_overlaps++;
3910 old_num_intervals += clique.size();
3912 if (old_num_no_overlaps == 0)
return;
3916 local_model.GetOrCreate<Trail>()->Resize(num_constraints);
3917 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
3918 graph->Resize(num_constraints);
3919 for (
const std::vector<Literal>& clique : cliques) {
3922 CHECK(graph->AddAtMostOne(clique));
3924 CHECK(graph->DetectEquivalences());
3925 graph->TransformIntoMaxCliques(
3926 &cliques, options_.
parameters.merge_no_overlap_work_limit());
3929 int new_num_no_overlaps = 0;
3930 int new_num_intervals = 0;
3931 for (
int i = 0; i < cliques.size(); ++i) {
3932 const int ct_index = disjunctive_index[i];
3933 ConstraintProto*
ct =
3936 if (cliques[i].empty())
continue;
3937 for (
const Literal l : cliques[i]) {
3938 CHECK(l.IsPositive());
3939 ct->mutable_no_overlap()->add_intervals(l.Variable().value());
3941 new_num_no_overlaps++;
3942 new_num_intervals += cliques[i].size();
3944 if (old_num_intervals != new_num_intervals ||
3945 old_num_no_overlaps != new_num_no_overlaps) {
3946 VLOG(1) << absl::StrCat(
"Merged ", old_num_no_overlaps,
" no-overlaps (",
3947 old_num_intervals,
" intervals) into ",
3948 new_num_no_overlaps,
" no-overlaps (",
3949 new_num_intervals,
" intervals).");
3954 void CpModelPresolver::TransformIntoMaxCliques() {
3957 auto convert = [](
int ref) {
3958 if (
RefIsPositive(ref))
return Literal(BooleanVariable(ref),
true);
3959 return Literal(BooleanVariable(
NegatedRef(ref)),
false);
3961 const int num_constraints = context_->
working_model->constraints_size();
3964 std::vector<std::vector<Literal>> cliques;
3966 for (
int c = 0; c < num_constraints; ++c) {
3967 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
3968 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne) {
3969 std::vector<Literal> clique;
3970 for (
const int ref :
ct->at_most_one().literals()) {
3971 clique.push_back(convert(ref));
3973 cliques.push_back(clique);
3974 if (RemoveConstraint(
ct)) {
3977 }
else if (
ct->constraint_case() ==
3978 ConstraintProto::ConstraintCase::kBoolAnd) {
3979 if (
ct->enforcement_literal().size() != 1)
continue;
3980 const Literal enforcement = convert(
ct->enforcement_literal(0));
3981 for (
const int ref :
ct->bool_and().literals()) {
3982 cliques.push_back({enforcement, convert(ref).Negated()});
3984 if (RemoveConstraint(
ct)) {
3990 const int num_old_cliques = cliques.size();
3994 const int num_variables = context_->
working_model->variables().size();
3995 local_model.GetOrCreate<Trail>()->Resize(num_variables);
3996 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
3997 graph->Resize(num_variables);
3998 for (
const std::vector<Literal>& clique : cliques) {
3999 if (!graph->AddAtMostOne(clique)) {
4003 if (!graph->DetectEquivalences()) {
4006 graph->TransformIntoMaxCliques(
4007 &cliques, options_.
parameters.merge_at_most_one_work_limit());
4012 for (
int var = 0;
var < num_variables; ++
var) {
4013 const Literal l = Literal(BooleanVariable(
var),
true);
4014 if (graph->RepresentativeOf(l) != l) {
4015 const Literal r = graph->RepresentativeOf(l);
4017 var, r.IsPositive() ? r.Variable().value()
4022 int num_new_cliques = 0;
4023 for (
const std::vector<Literal>& clique : cliques) {
4024 if (clique.empty())
continue;
4027 for (
const Literal
literal : clique) {
4029 ct->mutable_at_most_one()->add_literals(
literal.Variable().value());
4031 ct->mutable_at_most_one()->add_literals(
4037 if (num_new_cliques != num_old_cliques) {
4038 context_->
UpdateRuleStats(
"at_most_one: transformed into max clique.");
4042 LOG(
INFO) <<
"Merged " << num_old_cliques <<
" into " << num_new_cliques
4049 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
4052 if (ExploitEquivalenceRelations(c,
ct)) {
4057 if (PresolveEnforcementLiteral(
ct)) {
4062 switch (
ct->constraint_case()) {
4063 case ConstraintProto::ConstraintCase::kBoolOr:
4064 return PresolveBoolOr(
ct);
4065 case ConstraintProto::ConstraintCase::kBoolAnd:
4066 return PresolveBoolAnd(
ct);
4067 case ConstraintProto::ConstraintCase::kAtMostOne:
4068 return PresolveAtMostOne(
ct);
4069 case ConstraintProto::ConstraintCase::kBoolXor:
4070 return PresolveBoolXor(
ct);
4071 case ConstraintProto::ConstraintCase::kIntMax:
4072 if (
ct->int_max().vars_size() == 2 &&
4074 return PresolveIntAbs(
ct);
4076 return PresolveIntMax(
ct);
4078 case ConstraintProto::ConstraintCase::kIntMin:
4079 return PresolveIntMin(
ct);
4080 case ConstraintProto::ConstraintCase::kLinMax:
4081 return PresolveLinMax(
ct);
4082 case ConstraintProto::ConstraintCase::kLinMin:
4083 return PresolveLinMin(
ct);
4084 case ConstraintProto::ConstraintCase::kIntProd:
4085 return PresolveIntProd(
ct);
4086 case ConstraintProto::ConstraintCase::kIntDiv:
4087 return PresolveIntDiv(
ct);
4088 case ConstraintProto::ConstraintCase::kLinear: {
4089 if (CanonicalizeLinear(
ct)) {
4092 if (PresolveSmallLinear(
ct)) {
4095 if (PropagateDomainsInLinear(c,
ct)) {
4098 if (PresolveSmallLinear(
ct)) {
4102 if (RemoveSingletonInLinear(
ct)) {
4107 if (PresolveSmallLinear(
ct)) {
4111 if (PresolveLinearOnBooleans(
ct)) {
4114 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kLinear) {
4115 const int old_num_enforcement_literals =
ct->enforcement_literal_size();
4116 ExtractEnforcementLiteralFromLinearConstraint(c,
ct);
4117 if (
ct->constraint_case() ==
4118 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
4122 if (
ct->enforcement_literal_size() > old_num_enforcement_literals &&
4123 PresolveSmallLinear(
ct)) {
4126 PresolveLinearEqualityModuloTwo(
ct);
4130 case ConstraintProto::ConstraintCase::kInterval:
4131 return PresolveInterval(c,
ct);
4132 case ConstraintProto::ConstraintCase::kElement:
4133 return PresolveElement(
ct);
4134 case ConstraintProto::ConstraintCase::kTable:
4135 return PresolveTable(
ct);
4136 case ConstraintProto::ConstraintCase::kAllDiff:
4137 return PresolveAllDiff(
ct);
4138 case ConstraintProto::ConstraintCase::kNoOverlap:
4139 return PresolveNoOverlap(
ct);
4140 case ConstraintProto::ConstraintCase::kCumulative:
4141 return PresolveCumulative(
ct);
4142 case ConstraintProto::ConstraintCase::kCircuit:
4143 return PresolveCircuit(
ct);
4144 case ConstraintProto::ConstraintCase::kRoutes:
4145 return PresolveRoutes(
ct);
4146 case ConstraintProto::ConstraintCase::kAutomaton:
4147 return PresolveAutomaton(
ct);
4153 bool CpModelPresolver::ProcessSetPPCSubset(
4154 int c1,
int c2,
const std::vector<int>& c2_minus_c1,
4155 const std::vector<int>& original_constraint_index,
4156 std::vector<bool>* marked_for_removal) {
4158 CHECK(!(*marked_for_removal)[c1]);
4159 CHECK(!(*marked_for_removal)[c2]);
4160 ConstraintProto* ct1 = context_->
working_model->mutable_constraints(
4161 original_constraint_index[c1]);
4162 ConstraintProto* ct2 = context_->
working_model->mutable_constraints(
4163 original_constraint_index[c2]);
4164 if (ct1->constraint_case() == ConstraintProto::ConstraintCase::kBoolOr &&
4165 ct2->constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne) {
4167 for (
const int literal : c2_minus_c1) {
4173 if (ct1->constraint_case() == ct2->constraint_case()) {
4174 if (ct1->constraint_case() == ConstraintProto::ConstraintCase::kBoolOr) {
4175 (*marked_for_removal)[c2] =
true;
4180 ConstraintProto::ConstraintCase::kAtMostOne);
4181 (*marked_for_removal)[c1] =
true;
4191 bool CpModelPresolver::ProcessSetPPC() {
4192 bool changed =
false;
4193 const int num_constraints = context_->
working_model->constraints_size();
4197 std::vector<uint64> signatures;
4201 std::vector<std::vector<int>> constraint_literals;
4205 std::vector<std::vector<int>> literals_to_constraints;
4210 std::vector<bool> marked_for_removal;
4214 std::vector<int> original_constraint_index;
4218 int num_setppc_constraints = 0;
4219 for (
int c = 0; c < num_constraints; ++c) {
4220 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
4221 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kBoolOr ||
4222 ct->constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne) {
4231 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kBoolOr ||
4232 ct->constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne) {
4233 constraint_literals.push_back(GetLiteralsFromSetPPCConstraint(
ct));
4236 for (
const int literal : constraint_literals.back()) {
4238 signature |= (
int64{1} << (positive_literal % 64));
4240 if (positive_literal >= literals_to_constraints.size()) {
4241 literals_to_constraints.resize(positive_literal + 1);
4243 literals_to_constraints[positive_literal].push_back(
4244 num_setppc_constraints);
4246 signatures.push_back(signature);
4247 marked_for_removal.push_back(
false);
4248 original_constraint_index.push_back(c);
4249 num_setppc_constraints++;
4252 VLOG(1) <<
"#setppc constraints: " << num_setppc_constraints;
4255 absl::flat_hash_set<std::pair<int, int>> compared_constraints;
4256 for (
const std::vector<int>& literal_to_constraints :
4257 literals_to_constraints) {
4258 for (
int index1 = 0; index1 < literal_to_constraints.size(); ++index1) {
4263 const int c1 = literal_to_constraints[index1];
4264 if (marked_for_removal[c1])
continue;
4265 const std::vector<int>& c1_literals = constraint_literals[c1];
4266 ConstraintProto* ct1 = context_->
working_model->mutable_constraints(
4267 original_constraint_index[c1]);
4268 for (
int index2 = index1 + 1; index2 < literal_to_constraints.size();
4270 const int c2 = literal_to_constraints[index2];
4271 if (marked_for_removal[c2])
continue;
4272 if (marked_for_removal[c1])
break;
4274 if (c1 == c2)
continue;
4278 std::pair<int, int>(c1, c2))) {
4281 compared_constraints.insert({c1, c2});
4285 if (compared_constraints.size() >= 50000)
return changed;
4287 const bool smaller = (signatures[c1] & ~signatures[c2]) == 0;
4288 const bool larger = (signatures[c2] & ~signatures[c1]) == 0;
4290 if (!(smaller || larger)) {
4295 const std::vector<int>& c2_literals = constraint_literals[c2];
4296 ConstraintProto* ct2 = context_->
working_model->mutable_constraints(
4297 original_constraint_index[c2]);
4300 std::vector<int> c1_minus_c2;
4302 std::vector<int> c2_minus_c1;
4305 if (c1_minus_c2.empty() && c2_minus_c1.empty()) {
4306 if (ct1->constraint_case() == ct2->constraint_case()) {
4307 marked_for_removal[c2] =
true;
4310 }
else if (c1_minus_c2.empty()) {
4311 if (ProcessSetPPCSubset(c1, c2, c2_minus_c1,
4312 original_constraint_index,
4313 &marked_for_removal)) {
4315 original_constraint_index[c1]);
4317 original_constraint_index[c2]);
4319 }
else if (c2_minus_c1.empty()) {
4320 if (ProcessSetPPCSubset(c2, c1, c1_minus_c2,
4321 original_constraint_index,
4322 &marked_for_removal)) {
4324 original_constraint_index[c1]);
4326 original_constraint_index[c2]);
4332 for (
int c = 0; c < num_setppc_constraints; ++c) {
4333 if (marked_for_removal[c]) {
4335 original_constraint_index[c]);
4336 changed = RemoveConstraint(
ct);
4344 void CpModelPresolver::TryToSimplifyDomain(
int var) {
4352 if (r.representative !=
var)
return;
4367 if (domain.Size() == 2 && (domain.Min() != 0 || domain.Max() != 1)) {
4372 if (domain.NumIntervals() != domain.Size())
return;
4374 const int64 var_min = domain.Min();
4375 int64 gcd = domain[1].start - var_min;
4377 const ClosedInterval& i = domain[
index];
4379 const int64 shifted_value = i.start - var_min;
4383 if (gcd == 1)
break;
4385 if (gcd == 1)
return;
4389 std::vector<int64> scaled_values;
4391 const ClosedInterval& i = domain[
index];
4393 const int64 shifted_value = i.start - var_min;
4394 scaled_values.push_back(shifted_value / gcd);
4406 void CpModelPresolver::EncodeAllAffineRelations() {
4407 int64 num_added = 0;
4412 if (r.representative ==
var)
continue;
4419 if (!PresolveAffineRelationIfAny(
var))
break;
4426 auto* arg =
ct->mutable_linear();
4429 arg->add_vars(r.representative);
4430 arg->add_coeffs(-r.coeff);
4431 arg->add_domain(r.offset);
4432 arg->add_domain(r.offset);
4440 if (options_.
log_info && num_added > 0) {
4441 LOG(
INFO) << num_added <<
" affine relations still in the model.";
4446 bool CpModelPresolver::PresolveAffineRelationIfAny(
int var) {
4450 if (r.representative ==
var)
return true;
4469 auto* arg =
ct->mutable_linear();
4472 arg->add_vars(r.representative);
4473 arg->add_coeffs(-r.coeff);
4474 arg->add_domain(r.offset);
4475 arg->add_domain(r.offset);
4481 void CpModelPresolver::PresolveToFixPoint() {
4485 const int64 max_num_operations =
4486 options_.
parameters.cp_model_max_num_presolve_operations() > 0
4487 ? options_.
parameters.cp_model_max_num_presolve_operations()
4493 absl::flat_hash_set<std::pair<int, int>> var_constraint_pair_already_called;
4498 std::vector<bool> in_queue(context_->
working_model->constraints_size(),
4500 std::deque<int> queue;
4501 for (
int c = 0; c < in_queue.size(); ++c) {
4502 if (context_->
working_model->constraints(c).constraint_case() !=
4503 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
4513 std::sort(queue.begin(), queue.end(), [
this](
int a,
int b) {
4514 const int score_a = context_->ConstraintToVars(a).size();
4515 const int score_b = context_->ConstraintToVars(b).size();
4516 return score_a < score_b || (score_a == score_b && a < b);
4525 const int c = queue.front();
4526 in_queue[c] =
false;
4529 const int old_num_constraint =
4533 LOG(
INFO) <<
"Unsat after presolving constraint #" << c
4534 <<
" (warning, dump might be inconsistent): "
4535 << context_->
working_model->constraints(c).ShortDebugString();
4539 const int new_num_constraints =
4541 if (new_num_constraints > old_num_constraint) {
4543 in_queue.resize(new_num_constraints,
true);
4544 for (
int c = old_num_constraint; c < new_num_constraints; ++c) {
4561 const int current_num_variables = context_->
working_model->variables_size();
4562 for (
int v = 0; v < current_num_variables; ++v) {
4564 if (!PresolveAffineRelationIfAny(v))
return;
4569 TryToSimplifyDomain(v);
4578 in_queue.resize(context_->
working_model->constraints_size(),
false);
4583 if (c >= 0 && !in_queue[c]) {
4593 const int num_vars = context_->
working_model->variables_size();
4594 for (
int v = 0; v < num_vars; ++v) {
4596 if (constraints.size() != 1)
continue;
4597 const int c = *constraints.begin();
4598 if (c < 0)
continue;
4604 std::pair<int, int>(v, c))) {
4607 var_constraint_pair_already_called.insert({v, c});
4617 std::sort(queue.begin(), queue.end());
4630 VarDomination var_dom;
4631 DualBoundStrengthening dual_bound_strengthening;
4633 if (!dual_bound_strengthening.Strengthen(context_))
return;
4645 const int num_constraints = context_->
working_model->constraints_size();
4646 for (
int c = 0; c < num_constraints; ++c) {
4647 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
4648 switch (
ct->constraint_case()) {
4649 case ConstraintProto::ConstraintCase::kNoOverlap:
4651 if (PresolveNoOverlap(
ct)) {
4655 case ConstraintProto::ConstraintCase::kNoOverlap2D:
4659 case ConstraintProto::ConstraintCase::kCumulative:
4661 if (PresolveCumulative(
ct)) {
4665 case ConstraintProto::ConstraintCase::kBoolOr: {
4668 for (
const auto& pair :
4670 bool modified =
false;
4691 <<
" affine relations were detected.";
4693 <<
" variable equivalence relations were detected.";
4694 std::map<std::string, int> sorted_rules(
context->stats_by_rule_name.begin(),
4695 context->stats_by_rule_name.end());
4696 for (
const auto& entry : sorted_rules) {
4697 if (entry.second == 1) {
4698 LOG(
INFO) <<
"- rule '" << entry.first <<
"' was applied 1 time.";
4700 LOG(
INFO) <<
"- rule '" << entry.first <<
"' was applied " << entry.second
4711 std::vector<int>* postsolve_mapping) {
4718 std::vector<int>* postsolve_mapping)
4719 : options_(options),
4720 postsolve_mapping_(postsolve_mapping),
4723 options.
parameters.keep_all_feasible_solutions_in_presolve() ||
4724 options.
parameters.enumerate_all_solutions() ||
4725 options.
parameters.fill_tightened_domains_in_response() ||
4729 for (
const auto& decision_strategy :
4731 *(context_->
mapping_model->add_search_strategy()) = decision_strategy;
4766 if (!options_.
parameters.cp_model_presolve()) {
4778 for (
int c = 0; c < context_->
working_model->constraints_size(); ++c) {
4779 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
4780 PresolveEnforcementLiteral(
ct);
4781 switch (
ct->constraint_case()) {
4782 case ConstraintProto::ConstraintCase::kBoolOr:
4785 case ConstraintProto::ConstraintCase::kBoolAnd:
4786 PresolveBoolAnd(
ct);
4788 case ConstraintProto::ConstraintCase::kAtMostOne:
4789 PresolveAtMostOne(
ct);
4791 case ConstraintProto::ConstraintCase::kLinear:
4792 CanonicalizeLinear(
ct);
4804 for (
int iter = 0; iter < options_.
parameters.max_presolve_iterations();
4809 int old_num_non_empty_constraints = 0;
4810 for (
int c = 0; c < context_->
working_model->constraints_size(); ++c) {
4813 if (type == ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET)
continue;
4814 old_num_non_empty_constraints++;
4821 PresolveToFixPoint();
4828 if (options_.
parameters.cp_model_probing_level() > 0) {
4832 PresolveToFixPoint();
4839 if (options_.
parameters.cp_model_use_sat_presolve()) {
4842 PresolvePureSatPart();
4855 const int old_size = context_->
working_model->constraints_size();
4856 for (
int c = 0; c < old_size; ++c) {
4857 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
4858 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear) {
4861 ExtractAtMostOneFromLinear(
ct);
4866 if (iter == 0) TransformIntoMaxCliques();
4876 PresolveToFixPoint();
4882 old_num_non_empty_constraints)) {
4889 MergeNoOverlapConstraints();
4899 EncodeAllAffineRelations();
4906 const std::vector<int> duplicates =
4908 if (!duplicates.empty()) {
4909 for (
const int c : duplicates) {
4912 if (type == ConstraintProto::ConstraintCase::kInterval) {
4929 context_->
working_model->add_constraints()->mutable_bool_or();
4941 absl::flat_hash_set<int> used_variables;
4942 for (DecisionStrategyProto& strategy :
4944 DecisionStrategyProto copy = strategy;
4945 strategy.clear_variables();
4946 for (
const int ref : copy.variables()) {
4955 used_variables.insert(
var);
4963 strategy.add_variables(rep);
4964 if (strategy.variable_selection_strategy() !=
4965 DecisionStrategyProto::CHOOSE_FIRST) {
4966 DecisionStrategyProto::AffineTransformation* t =
4967 strategy.add_transformations();
4970 t->set_positive_coeff(std::abs(r.
coeff));
4978 strategy.add_variables(ref);
4984 for (
int i = 0; i < context_->
working_model->variables_size(); ++i) {
4995 postsolve_mapping_->clear();
4996 std::vector<int> mapping(context_->
working_model->variables_size(), -1);
4997 for (
int i = 0; i < context_->
working_model->variables_size(); ++i) {
5002 mapping[i] = postsolve_mapping_->size();
5003 postsolve_mapping_->push_back(i);
5027 if (!error.empty()) {
5029 LOG(
INFO) <<
"Error while validating postsolved model: " << error;
5036 if (!error.empty()) {
5038 LOG(
INFO) <<
"Error while validating mapping_model model: " << error;
5052 auto mapping_function = [&mapping](
int* ref) {
5057 for (ConstraintProto& ct_ref : *
proto->mutable_constraints()) {
5063 if (
proto->has_objective()) {
5064 for (
int& mutable_ref : *
proto->mutable_objective()->mutable_vars()) {
5065 mapping_function(&mutable_ref);
5070 for (
int& mutable_ref : *
proto->mutable_assumptions()) {
5071 mapping_function(&mutable_ref);
5076 for (DecisionStrategyProto& strategy : *
proto->mutable_search_strategy()) {
5077 DecisionStrategyProto copy = strategy;
5078 strategy.clear_variables();
5079 for (
const int ref : copy.variables()) {
5085 strategy.clear_transformations();
5086 for (
const auto& transform : copy.transformations()) {
5087 const int ref = transform.var();
5090 auto* new_transform = strategy.add_transformations();
5091 *new_transform = transform;
5098 if (
proto->has_solution_hint()) {
5099 auto* mutable_hint =
proto->mutable_solution_hint();
5101 for (
int i = 0; i < mutable_hint->vars_size(); ++i) {
5102 const int old_ref = mutable_hint->vars(i);
5103 const int64 old_value = mutable_hint->values(i);
5111 const int image = mapping[
var];
5113 mutable_hint->set_vars(new_size, image);
5114 mutable_hint->set_values(new_size,
value);
5119 mutable_hint->mutable_vars()->Truncate(new_size);
5120 mutable_hint->mutable_values()->Truncate(new_size);
5122 proto->clear_solution_hint();
5127 std::vector<IntegerVariableProto> new_variables;
5128 for (
int i = 0; i < mapping.size(); ++i) {
5129 const int image = mapping[i];
5130 if (image < 0)
continue;
5131 if (image >= new_variables.size()) {
5132 new_variables.resize(image + 1, IntegerVariableProto());
5134 new_variables[image].Swap(
proto->mutable_variables(i));
5136 proto->clear_variables();
5137 for (IntegerVariableProto& proto_ref : new_variables) {
5138 proto->add_variables()->Swap(&proto_ref);
5142 for (
const IntegerVariableProto& v :
proto->variables()) {
5148 std::vector<int> result;
5151 absl::flat_hash_map<int64, int> equiv_constraints;
5154 const int num_constraints =
model_proto.constraints().size();
5155 for (
int c = 0; c < num_constraints; ++c) {
5156 if (
model_proto.constraints(c).constraint_case() ==
5157 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
5160 s =
model_proto.constraints(c).SerializeAsString();
5161 const int64 hash = std::hash<std::string>()(s);
5162 const auto insert = equiv_constraints.insert({
hash, c});
5163 if (!insert.second) {
5165 const int other_c_with_same_hash = insert.first->second;
5167 model_proto.constraints(other_c_with_same_hash).SerializeAsString()) {
5168 result.push_back(c);