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"
56 bool CpModelPresolver::RemoveConstraint(ConstraintProto*
ct) {
64 std::vector<int> interval_mapping(context_->
working_model->constraints_size(),
66 int new_num_constraints = 0;
67 const int old_num_non_empty_constraints =
69 for (
int c = 0; c < old_num_non_empty_constraints; ++c) {
70 const auto type = context_->
working_model->constraints(c).constraint_case();
71 if (type == ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET)
continue;
72 if (type == ConstraintProto::ConstraintCase::kInterval) {
73 interval_mapping[c] = new_num_constraints;
75 context_->
working_model->mutable_constraints(new_num_constraints++)
78 context_->
working_model->mutable_constraints()->DeleteSubrange(
79 new_num_constraints, old_num_non_empty_constraints - new_num_constraints);
80 for (ConstraintProto& ct_ref :
83 [&interval_mapping](
int* ref) {
84 *ref = interval_mapping[*ref];
91 bool CpModelPresolver::PresolveEnforcementLiteral(ConstraintProto*
ct) {
96 const int old_size =
ct->enforcement_literal().size();
97 for (
const int literal :
ct->enforcement_literal()) {
106 return RemoveConstraint(
ct);
113 return RemoveConstraint(
ct);
120 const int64 obj_coeff =
124 context_->
UpdateRuleStats(
"enforcement literal with unique direction");
126 return RemoveConstraint(
ct);
130 ct->set_enforcement_literal(new_size++,
literal);
132 ct->mutable_enforcement_literal()->Truncate(new_size);
133 return new_size != old_size;
136 bool CpModelPresolver::PresolveBoolXor(ConstraintProto*
ct) {
141 bool changed =
false;
142 int num_true_literals = 0;
144 for (
const int literal :
ct->bool_xor().literals()) {
165 ct->mutable_bool_xor()->set_literals(new_size++,
literal);
169 }
else if (new_size == 2) {
172 if (num_true_literals % 2 == 1) {
174 ct->mutable_bool_xor()->set_literals(new_size++, true_literal);
176 if (num_true_literals > 1) {
177 context_->
UpdateRuleStats(
"bool_xor: remove even number of true literals");
180 ct->mutable_bool_xor()->mutable_literals()->Truncate(new_size);
184 bool CpModelPresolver::PresolveBoolOr(ConstraintProto*
ct) {
191 for (
const int literal :
ct->enforcement_literal()) {
194 ct->clear_enforcement_literal();
198 bool changed =
false;
201 for (
const int literal :
ct->bool_or().literals()) {
208 return RemoveConstraint(
ct);
216 return RemoveConstraint(
ct);
220 return RemoveConstraint(
ct);
239 return RemoveConstraint(
ct);
252 ct->mutable_bool_or()->mutable_literals()->Clear();
254 ct->mutable_bool_or()->add_literals(lit);
260 ABSL_MUST_USE_RESULT
bool CpModelPresolver::MarkConstraintAsFalse(
261 ConstraintProto*
ct) {
264 ct->mutable_bool_or()->clear_literals();
265 for (
const int lit :
ct->enforcement_literal()) {
268 ct->clear_enforcement_literal();
276 bool CpModelPresolver::PresolveBoolAnd(ConstraintProto*
ct) {
281 for (
const int literal :
ct->bool_and().literals()) {
284 return RemoveConstraint(
ct);
287 bool changed =
false;
289 for (
const int literal :
ct->bool_and().literals()) {
292 return MarkConstraintAsFalse(
ct);
312 ct->mutable_bool_and()->mutable_literals()->Clear();
314 ct->mutable_bool_and()->add_literals(lit);
323 if (
ct->enforcement_literal().size() == 1 &&
324 ct->bool_and().literals().size() == 1) {
325 const int enforcement =
ct->enforcement_literal(0);
335 ct->bool_and().literals(0));
343 bool CpModelPresolver::PresolveAtMostOne(ConstraintProto*
ct) {
348 if (
ct->at_most_one().literals_size() == 1) {
350 return RemoveConstraint(
ct);
354 std::sort(
ct->mutable_at_most_one()->mutable_literals()->begin(),
355 ct->mutable_at_most_one()->mutable_literals()->end());
357 for (
const int literal :
ct->at_most_one().literals()) {
365 bool changed =
false;
367 for (
const int literal :
ct->at_most_one().literals()) {
370 for (
const int other :
ct->at_most_one().literals()) {
375 return RemoveConstraint(
ct);
387 return RemoveConstraint(
ct);
391 ct->mutable_at_most_one()->mutable_literals()->Clear();
393 ct->mutable_at_most_one()->add_literals(lit);
400 bool CpModelPresolver::PresolveIntMax(ConstraintProto*
ct) {
402 if (
ct->int_max().vars().empty()) {
404 return MarkConstraintAsFalse(
ct);
406 const int target_ref =
ct->int_max().target();
411 bool contains_target_ref =
false;
412 std::set<int> used_ref;
414 for (
const int ref :
ct->int_max().vars()) {
415 if (ref == target_ref) contains_target_ref =
true;
421 used_ref.insert(ref);
422 ct->mutable_int_max()->set_vars(new_size++, ref);
426 if (new_size < ct->int_max().vars_size()) {
429 ct->mutable_int_max()->mutable_vars()->Truncate(new_size);
430 if (contains_target_ref) {
432 for (
const int ref :
ct->int_max().vars()) {
433 if (ref == target_ref)
continue;
434 ConstraintProto* new_ct = context_->
working_model->add_constraints();
435 *new_ct->mutable_enforcement_literal() =
ct->enforcement_literal();
436 auto* arg = new_ct->mutable_linear();
437 arg->add_vars(target_ref);
444 return RemoveConstraint(
ct);
448 Domain infered_domain;
449 for (
const int ref :
ct->int_max().vars()) {
450 infered_domain = infered_domain.UnionWith(
455 bool domain_reduced =
false;
467 const Domain& target_domain = context_->
DomainOf(target_ref);
468 if (infered_domain.IntersectionWith(Domain(
kint64min, target_domain.Max()))
469 .IsIncludedIn(target_domain)) {
470 if (infered_domain.Max() <= target_domain.Max()) {
473 }
else if (
ct->enforcement_literal().empty()) {
475 for (
const int ref :
ct->int_max().vars()) {
478 ref, Domain(
kint64min, target_domain.Max()))) {
486 for (
const int ref :
ct->int_max().vars()) {
487 ConstraintProto* new_ct = context_->
working_model->add_constraints();
488 *(new_ct->mutable_enforcement_literal()) =
ct->enforcement_literal();
489 ct->mutable_linear()->add_vars(ref);
490 ct->mutable_linear()->add_coeffs(1);
492 ct->mutable_linear()->add_domain(target_domain.Max());
499 return RemoveConstraint(
ct);
505 const int size =
ct->int_max().vars_size();
506 const int64 target_max = context_->
MaxOf(target_ref);
507 for (
const int ref :
ct->int_max().vars()) {
514 if (context_->
MaxOf(ref) >= infered_min) {
515 ct->mutable_int_max()->set_vars(new_size++, ref);
518 if (domain_reduced) {
522 bool modified =
false;
523 if (new_size < size) {
525 ct->mutable_int_max()->mutable_vars()->Truncate(new_size);
531 return MarkConstraintAsFalse(
ct);
537 ConstraintProto* new_ct = context_->
working_model->add_constraints();
539 auto* arg = new_ct->mutable_linear();
540 arg->add_vars(target_ref);
542 arg->add_vars(
ct->int_max().vars(0));
547 return RemoveConstraint(
ct);
552 bool CpModelPresolver::PresolveLinMin(ConstraintProto*
ct) {
555 const auto copy =
ct->lin_min();
557 ct->mutable_lin_max()->mutable_target());
558 for (
const LinearExpressionProto& expr : copy.exprs()) {
559 LinearExpressionProto*
const new_expr =
ct->mutable_lin_max()->add_exprs();
562 return PresolveLinMax(
ct);
565 bool CpModelPresolver::PresolveLinMax(ConstraintProto*
ct) {
567 if (
ct->lin_max().exprs().empty()) {
569 return MarkConstraintAsFalse(
ct);
575 int64 infered_min = context_->
MinOf(
ct->lin_max().target());
576 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
587 for (
int i = 0; i <
ct->lin_max().exprs_size(); ++i) {
588 const LinearExpressionProto& expr =
ct->lin_max().exprs(i);
589 if (context_->
MaxOf(expr) >= infered_min) {
590 *
ct->mutable_lin_max()->mutable_exprs(new_size) = expr;
595 if (new_size < ct->lin_max().exprs_size()) {
597 ct->mutable_lin_max()->mutable_exprs()->DeleteSubrange(
598 new_size,
ct->lin_max().exprs_size() - new_size);
605 bool CpModelPresolver::PresolveIntAbs(ConstraintProto*
ct) {
608 const int target_ref =
ct->int_max().target();
613 const Domain new_target_domain = var_domain.
UnionWith(var_domain.Negation())
623 const Domain target_domain = context_->
DomainOf(target_ref);
624 const Domain new_var_domain =
625 target_domain.
UnionWith(target_domain.Negation());
635 ConstraintProto* new_ct = context_->
working_model->add_constraints();
636 new_ct->set_name(
ct->name());
637 auto* arg = new_ct->mutable_linear();
638 arg->add_vars(target_ref);
645 return RemoveConstraint(
ct);
650 ConstraintProto* new_ct = context_->
working_model->add_constraints();
651 new_ct->set_name(
ct->name());
652 auto* arg = new_ct->mutable_linear();
653 arg->add_vars(target_ref);
660 return RemoveConstraint(
ct);
666 context_->
IsFixed(target_ref)) {
667 if (!context_->
IsFixed(target_ref)) {
672 return RemoveConstraint(
ct);
682 bool CpModelPresolver::PresolveIntMin(ConstraintProto*
ct) {
685 const auto copy =
ct->int_min();
686 ct->mutable_int_max()->set_target(
NegatedRef(copy.target()));
687 for (
const int ref : copy.vars()) {
690 return PresolveIntMax(
ct);
693 bool CpModelPresolver::PresolveIntProd(ConstraintProto*
ct) {
697 bool changed =
false;
702 for (
int i = 0; i <
ct->int_prod().vars().size(); ++i) {
703 const int ref =
ct->int_prod().vars(i);
705 if (r.representative != ref && r.offset == 0) {
707 ct->mutable_int_prod()->set_vars(i, r.representative);
722 const int old_target =
ct->int_prod().target();
723 const int new_target = context_->
working_model->variables_size();
725 IntegerVariableProto* var_proto = context_->
working_model->add_variables();
732 ct->mutable_int_prod()->set_target(new_target);
733 if (context_->
IsFixed(new_target)) {
745 ConstraintProto* new_ct = context_->
working_model->add_constraints();
746 LinearConstraintProto* lin = new_ct->mutable_linear();
747 lin->add_vars(old_target);
749 lin->add_vars(new_target);
750 lin->add_coeffs(-constant);
760 for (
const int ref :
ct->int_prod().vars()) {
761 implied = implied.ContinuousMultiplicationBy(context_->
DomainOf(ref));
763 bool modified =
false;
772 if (
ct->int_prod().vars_size() == 2) {
773 int a =
ct->int_prod().vars(0);
774 int b =
ct->int_prod().vars(1);
775 const int product =
ct->int_prod().target();
780 ConstraintProto*
const lin = context_->
working_model->add_constraints();
781 lin->mutable_linear()->add_vars(
b);
782 lin->mutable_linear()->add_coeffs(context_->
MinOf(
a));
783 lin->mutable_linear()->add_vars(product);
784 lin->mutable_linear()->add_coeffs(-1);
785 lin->mutable_linear()->add_domain(0);
786 lin->mutable_linear()->add_domain(0);
789 return RemoveConstraint(
ct);
790 }
else if (context_->
MinOf(
a) != 1) {
791 bool domain_modified =
false;
797 return RemoveConstraint(
ct);
800 return RemoveConstraint(
ct);
802 }
else if (
a ==
b &&
a == product) {
807 return RemoveConstraint(
ct);
812 const int target_ref =
ct->int_prod().target();
814 for (
const int var :
ct->int_prod().vars()) {
816 if (context_->
MinOf(
var) < 0)
return changed;
817 if (context_->
MaxOf(
var) > 1)
return changed;
826 ConstraintProto* new_ct = context_->
working_model->add_constraints();
827 new_ct->add_enforcement_literal(target_ref);
828 auto* arg = new_ct->mutable_bool_and();
829 for (
const int var :
ct->int_prod().vars()) {
830 arg->add_literals(
var);
834 ConstraintProto* new_ct = context_->
working_model->add_constraints();
835 auto* arg = new_ct->mutable_bool_or();
836 arg->add_literals(target_ref);
837 for (
const int var :
ct->int_prod().vars()) {
842 return RemoveConstraint(
ct);
845 bool CpModelPresolver::PresolveIntDiv(ConstraintProto*
ct) {
849 const int target =
ct->int_div().target();
850 const int ref_x =
ct->int_div().vars(0);
851 const int ref_div =
ct->int_div().vars(1);
858 const int64 divisor = context_->
MinOf(ref_div);
860 LinearConstraintProto*
const lin =
861 context_->
working_model->add_constraints()->mutable_linear();
862 lin->add_vars(ref_x);
864 lin->add_vars(target);
870 return RemoveConstraint(
ct);
872 bool domain_modified =
false;
876 if (domain_modified) {
878 "int_div: updated domain of target in target = X / cte");
889 if (context_->
MinOf(target) >= 0 && context_->
MinOf(ref_x) >= 0 &&
891 LinearConstraintProto*
const lin =
892 context_->
working_model->add_constraints()->mutable_linear();
893 lin->add_vars(ref_x);
895 lin->add_vars(target);
896 lin->add_coeffs(-divisor);
898 lin->add_domain(divisor - 1);
901 "int_div: linearize positive division with a constant divisor");
902 return RemoveConstraint(
ct);
910 bool CpModelPresolver::ExploitEquivalenceRelations(
int c, ConstraintProto*
ct) {
911 bool changed =
false;
916 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kLinear) {
917 for (
int& ref : *
ct->mutable_enforcement_literal()) {
929 bool work_to_do =
false;
932 if (r.representative !=
var) {
937 if (!work_to_do)
return false;
941 [&changed,
this](
int* ref) {
952 [&changed,
this](
int* ref) {
963 void CpModelPresolver::DivideLinearByGcd(ConstraintProto*
ct) {
968 const int num_vars =
ct->linear().vars().size();
969 for (
int i = 0; i < num_vars; ++i) {
970 const int64 magnitude = std::abs(
ct->linear().coeffs(i));
976 for (
int i = 0; i < num_vars; ++i) {
977 ct->mutable_linear()->set_coeffs(i,
ct->linear().coeffs(i) / gcd);
981 if (
ct->linear().domain_size() == 0) {
982 return (
void)MarkConstraintAsFalse(
ct);
987 void CpModelPresolver::PresolveLinearEqualityModuloTwo(ConstraintProto*
ct) {
988 if (!
ct->enforcement_literal().empty())
return;
989 if (
ct->linear().domain().size() != 2)
return;
990 if (
ct->linear().domain(0) !=
ct->linear().domain(1))
return;
995 std::vector<int> literals;
996 for (
int i = 0; i <
ct->linear().vars().size(); ++i) {
997 const int64 coeff =
ct->linear().coeffs(i);
998 const int ref =
ct->linear().vars(i);
999 if (coeff % 2 == 0)
continue;
1002 if (literals.size() > 2)
return;
1004 if (literals.size() == 1) {
1005 const int64 rhs = std::abs(
ct->linear().domain(0));
1006 context_->
UpdateRuleStats(
"linear: only one odd Boolean in equality");
1008 }
else if (literals.size() == 2) {
1009 const int64 rhs = std::abs(
ct->linear().domain(0));
1010 context_->
UpdateRuleStats(
"linear: only two odd Booleans in equality");
1020 bool CpModelPresolver::CanonicalizeLinear(ConstraintProto*
ct) {
1021 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1033 int64 sum_of_fixed_terms = 0;
1034 bool remapped =
false;
1035 const int num_vars =
ct->linear().vars().size();
1036 DCHECK_EQ(num_vars,
ct->linear().coeffs().size());
1037 for (
int i = 0; i < num_vars; ++i) {
1038 const int ref =
ct->linear().vars(i);
1042 if (coeff == 0)
continue;
1045 sum_of_fixed_terms += coeff * context_->
MinOf(
var);
1051 bool removed =
false;
1052 for (
const int enf :
ct->enforcement_literal()) {
1056 sum_of_fixed_terms += coeff;
1065 context_->
UpdateRuleStats(
"linear: enforcement literal in constraint");
1070 if (r.representative !=
var) {
1072 sum_of_fixed_terms += coeff * r.
offset;
1074 tmp_terms_.push_back({r.representative, coeff * r.coeff});
1077 if (sum_of_fixed_terms != 0) {
1079 rhs = rhs.AdditionWith({-sum_of_fixed_terms, -sum_of_fixed_terms});
1083 ct->mutable_linear()->clear_vars();
1084 ct->mutable_linear()->clear_coeffs();
1085 std::sort(tmp_terms_.begin(), tmp_terms_.end());
1086 int current_var = 0;
1087 int64 current_coeff = 0;
1088 for (
const auto entry : tmp_terms_) {
1090 if (entry.first == current_var) {
1091 current_coeff += entry.second;
1093 if (current_coeff != 0) {
1094 ct->mutable_linear()->add_vars(current_var);
1095 ct->mutable_linear()->add_coeffs(current_coeff);
1097 current_var = entry.first;
1098 current_coeff = entry.second;
1101 if (current_coeff != 0) {
1102 ct->mutable_linear()->add_vars(current_var);
1103 ct->mutable_linear()->add_coeffs(current_coeff);
1105 DivideLinearByGcd(
ct);
1107 bool var_constraint_graph_changed =
false;
1110 var_constraint_graph_changed =
true;
1112 if (
ct->linear().vars().size() < num_vars) {
1114 var_constraint_graph_changed =
true;
1116 return var_constraint_graph_changed;
1119 bool CpModelPresolver::RemoveSingletonInLinear(ConstraintProto*
ct) {
1120 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1125 std::set<int> index_to_erase;
1126 const int num_vars =
ct->linear().vars().size();
1130 for (
int i = 0; i < num_vars; ++i) {
1131 const int var =
ct->linear().vars(i);
1132 const int64 coeff =
ct->linear().coeffs(i);
1136 const auto term_domain =
1138 if (!exact)
continue;
1142 if (new_rhs.NumIntervals() > 100)
continue;
1149 index_to_erase.insert(i);
1156 if (index_to_erase.empty()) {
1158 if (options_.
parameters.presolve_substitution_level() <= 0)
return false;
1159 if (!
ct->enforcement_literal().empty())
return false;
1163 if (rhs.Min() != rhs.Max())
return false;
1165 for (
int i = 0; i < num_vars; ++i) {
1166 const int var =
ct->linear().vars(i);
1167 const int64 coeff =
ct->linear().coeffs(i);
1185 const int64 objective_coeff =
1188 if (objective_coeff % coeff != 0)
continue;
1192 const auto term_domain =
1194 if (!exact)
continue;
1196 if (new_rhs.NumIntervals() > 100)
continue;
1204 objective_coeff))) {
1221 LOG(
WARNING) <<
"This was not supposed to happen and the presolve "
1222 "could be improved.";
1225 context_->
UpdateRuleStats(
"linear: singleton column define objective.");
1229 return RemoveConstraint(
ct);
1235 "linear: singleton column in equality and in objective.");
1238 index_to_erase.insert(i);
1242 if (index_to_erase.empty())
return false;
1251 for (
int i = 0; i < num_vars; ++i) {
1252 if (index_to_erase.count(i)) {
1256 ct->mutable_linear()->set_coeffs(new_size,
ct->linear().coeffs(i));
1257 ct->mutable_linear()->set_vars(new_size,
ct->linear().vars(i));
1260 ct->mutable_linear()->mutable_vars()->Truncate(new_size);
1261 ct->mutable_linear()->mutable_coeffs()->Truncate(new_size);
1263 DivideLinearByGcd(
ct);
1267 bool CpModelPresolver::PresolveSmallLinear(ConstraintProto*
ct) {
1268 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1274 if (
ct->linear().vars().empty()) {
1277 if (rhs.Contains(0)) {
1278 return RemoveConstraint(
ct);
1280 return MarkConstraintAsFalse(
ct);
1287 if (
ct->linear().vars_size() == 1 &&
ct->enforcement_literal_size() > 0 &&
1288 ct->linear().coeffs(0) == 1 &&
1291 context_->
UpdateRuleStats(
"linear: remove abs from abs(x) in domain");
1292 const Domain implied_abs_target_domain =
1295 .IntersectionWith(context_->
DomainOf(
ct->linear().vars(0)));
1297 if (implied_abs_target_domain.IsEmpty()) {
1298 return MarkConstraintAsFalse(
ct);
1301 const Domain new_abs_var_domain =
1302 implied_abs_target_domain
1303 .UnionWith(implied_abs_target_domain.Negation())
1304 .IntersectionWith(context_->
DomainOf(abs_arg));
1306 if (new_abs_var_domain.IsEmpty()) {
1307 return MarkConstraintAsFalse(
ct);
1310 ConstraintProto* new_ct = context_->
working_model->add_constraints();
1311 new_ct->set_name(
ct->name());
1312 for (
const int literal :
ct->enforcement_literal()) {
1313 new_ct->add_enforcement_literal(
literal);
1315 auto* arg = new_ct->mutable_linear();
1316 arg->add_vars(abs_arg);
1320 return RemoveConstraint(
ct);
1324 if (
ct->enforcement_literal_size() != 1 ||
ct->linear().vars_size() != 1 ||
1325 (
ct->linear().coeffs(0) != 1 &&
ct->linear().coeffs(0) == -1)) {
1329 const int literal =
ct->enforcement_literal(0);
1330 const LinearConstraintProto& linear =
ct->linear();
1331 const int ref = linear.vars(0);
1336 if (linear.domain_size() == 2 && linear.domain(0) == linear.domain(1)) {
1338 : -linear.domain(0) * coeff;
1347 if (complement.Size() != 1)
return false;
1349 : -complement.Min() * coeff;
1364 if (
ct->linear().vars().size() == 1) {
1366 ?
ct->linear().coeffs(0)
1367 : -
ct->linear().coeffs(0);
1372 rhs.InverseMultiplicationBy(coeff))) {
1375 return RemoveConstraint(
ct);
1382 const LinearConstraintProto& arg =
ct->linear();
1383 if (arg.vars_size() == 2) {
1385 const int64 rhs_min = rhs.Min();
1386 const int64 rhs_max = rhs.Max();
1387 if (rhs_min == rhs_max) {
1388 const int v1 = arg.vars(0);
1389 const int v2 = arg.vars(1);
1390 const int64 coeff1 = arg.coeffs(0);
1391 const int64 coeff2 = arg.coeffs(1);
1395 }
else if (coeff2 == 1) {
1397 }
else if (coeff1 == -1) {
1399 }
else if (coeff2 == -1) {
1402 if (added)
return RemoveConstraint(
ct);
1412 bool IsLeConstraint(
const Domain& domain,
const Domain& all_values) {
1413 return all_values.IntersectionWith(Domain(
kint64min, domain.Max()))
1414 .IsIncludedIn(domain);
1418 bool IsGeConstraint(
const Domain& domain,
const Domain& all_values) {
1419 return all_values.IntersectionWith(Domain(domain.Min(),
kint64max))
1420 .IsIncludedIn(domain);
1426 bool RhsCanBeFixedToMin(
int64 coeff,
const Domain& var_domain,
1427 const Domain& terms,
const Domain& rhs) {
1428 if (var_domain.NumIntervals() != 1)
return false;
1429 if (std::abs(coeff) != 1)
return false;
1437 if (coeff == 1 && terms.Max() + var_domain.Min() <= rhs.Min()) {
1440 if (coeff == -1 && terms.Max() - var_domain.Max() <= rhs.Min()) {
1446 bool RhsCanBeFixedToMax(
int64 coeff,
const Domain& var_domain,
1447 const Domain& terms,
const Domain& rhs) {
1448 if (var_domain.NumIntervals() != 1)
return false;
1449 if (std::abs(coeff) != 1)
return false;
1451 if (coeff == 1 && terms.Min() + var_domain.Max() >= rhs.Max()) {
1454 if (coeff == -1 && terms.Min() - var_domain.Min() >= rhs.Max()) {
1461 void TakeIntersectionWith(
const absl::flat_hash_set<int>& current,
1462 absl::flat_hash_set<int>* to_clear) {
1463 std::vector<int> new_set;
1464 for (
const int c : *to_clear) {
1465 if (current.contains(c)) new_set.push_back(c);
1468 for (
const int c : new_set) to_clear->insert(c);
1473 bool CpModelPresolver::PropagateDomainsInLinear(
int c, ConstraintProto*
ct) {
1474 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1482 const int num_vars =
ct->linear().vars_size();
1483 term_domains.resize(num_vars + 1);
1484 left_domains.resize(num_vars + 1);
1485 left_domains[0] = Domain(0);
1486 for (
int i = 0; i < num_vars; ++i) {
1487 const int var =
ct->linear().vars(i);
1488 const int64 coeff =
ct->linear().coeffs(i);
1491 left_domains[i + 1] =
1494 const Domain& implied_rhs = left_domains[num_vars];
1498 if (implied_rhs.IsIncludedIn(old_rhs)) {
1500 return RemoveConstraint(
ct);
1504 Domain rhs = old_rhs.SimplifyUsingImpliedDomain(implied_rhs);
1505 if (rhs.IsEmpty()) {
1507 return MarkConstraintAsFalse(
ct);
1509 if (rhs != old_rhs) {
1517 bool is_le_constraint = IsLeConstraint(rhs, implied_rhs);
1518 bool is_ge_constraint = IsGeConstraint(rhs, implied_rhs);
1521 if (
ct->enforcement_literal().size() > 1)
return false;
1523 bool new_bounds =
false;
1524 bool recanonicalize =
false;
1525 Domain negated_rhs = rhs.Negation();
1526 Domain right_domain(0);
1528 Domain implied_term_domain;
1529 term_domains[num_vars] = Domain(0);
1530 for (
int i = num_vars - 1; i >= 0; --i) {
1531 const int var =
ct->linear().vars(i);
1532 const int64 var_coeff =
ct->linear().coeffs(i);
1534 right_domain.AdditionWith(term_domains[i + 1]).RelaxIfTooComplex();
1535 implied_term_domain = left_domains[i].AdditionWith(right_domain);
1536 new_domain = implied_term_domain.AdditionWith(negated_rhs)
1537 .InverseMultiplicationBy(-var_coeff);
1539 if (
ct->enforcement_literal().empty()) {
1544 }
else if (
ct->enforcement_literal().size() == 1) {
1555 recanonicalize =
true;
1559 if (is_le_constraint || is_ge_constraint) {
1560 CHECK_NE(is_le_constraint, is_ge_constraint);
1561 if ((var_coeff > 0) == is_ge_constraint) {
1576 const bool is_in_objective =
1580 const int64 obj_coeff =
1589 if (obj_coeff <= 0 &&
1599 recanonicalize =
true;
1603 if (obj_coeff >= 0 &&
1613 recanonicalize =
true;
1621 if (!
ct->enforcement_literal().empty())
continue;
1633 if (rhs.Min() != rhs.Max() &&
1637 if ((var_coeff > 0 == obj_coeff > 0) &&
1638 RhsCanBeFixedToMin(var_coeff, context_->
DomainOf(
var),
1639 implied_term_domain, rhs)) {
1640 rhs = Domain(rhs.Min());
1643 if ((var_coeff > 0 != obj_coeff > 0) &&
1644 RhsCanBeFixedToMax(var_coeff, context_->
DomainOf(
var),
1645 implied_term_domain, rhs)) {
1646 rhs = Domain(rhs.Max());
1652 negated_rhs = rhs.Negation();
1656 right_domain = Domain(0);
1660 is_le_constraint =
false;
1661 is_ge_constraint =
false;
1662 for (
const int var :
ct->linear().vars()) {
1679 if (
ct->linear().vars().size() <= 2)
continue;
1684 if (rhs.Min() != rhs.Max())
continue;
1690 if (context_->
DomainOf(
var) != new_domain)
continue;
1691 if (std::abs(var_coeff) != 1)
continue;
1692 if (options_.
parameters.presolve_substitution_level() <= 0)
continue;
1698 bool is_in_objective =
false;
1700 is_in_objective =
true;
1706 if (is_in_objective) col_size--;
1707 const int row_size =
ct->linear().vars_size();
1711 const int num_entries_added = (row_size - 1) * (col_size - 1);
1712 const int num_entries_removed = col_size + row_size - 1;
1714 if (num_entries_added > num_entries_removed) {
1720 std::vector<int> others;
1729 if (context_->
working_model->constraints(c).constraint_case() !=
1730 ConstraintProto::ConstraintCase::kLinear) {
1734 for (
const int ref :
1735 context_->
working_model->constraints(c).enforcement_literal()) {
1741 others.push_back(c);
1743 if (abort)
continue;
1746 for (
const int c : others) {
1759 if (is_in_objective) {
1764 absl::StrCat(
"linear: variable substitution ", others.size()));
1771 return RemoveConstraint(
ct);
1776 if (recanonicalize)
return CanonicalizeLinear(
ct);
1787 void CpModelPresolver::ExtractEnforcementLiteralFromLinearConstraint(
1788 int ct_index, ConstraintProto*
ct) {
1789 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1794 const LinearConstraintProto& arg =
ct->linear();
1795 const int num_vars = arg.vars_size();
1799 if (num_vars <= 1)
return;
1803 int64 max_coeff_magnitude = 0;
1804 for (
int i = 0; i < num_vars; ++i) {
1805 const int ref = arg.vars(i);
1806 const int64 coeff = arg.coeffs(i);
1807 const int64 term_a = coeff * context_->
MinOf(ref);
1808 const int64 term_b = coeff * context_->
MaxOf(ref);
1809 max_coeff_magnitude =
std::max(max_coeff_magnitude, std::abs(coeff));
1810 min_sum +=
std::min(term_a, term_b);
1811 max_sum +=
std::max(term_a, term_b);
1820 const auto& domain =
ct->linear().domain();
1821 const int64 ub_threshold = domain[domain.size() - 2] - min_sum;
1822 const int64 lb_threshold = max_sum - domain[1];
1824 if (max_coeff_magnitude <
std::max(ub_threshold, lb_threshold))
return;
1843 const bool lower_bounded = min_sum < rhs_domain.Min();
1844 const bool upper_bounded = max_sum > rhs_domain.Max();
1845 if (!lower_bounded && !upper_bounded)
return;
1846 if (lower_bounded && upper_bounded) {
1848 ConstraintProto* new_ct1 = context_->
working_model->add_constraints();
1850 if (!
ct->name().empty()) {
1851 new_ct1->set_name(absl::StrCat(
ct->name(),
" (part 1)"));
1854 new_ct1->mutable_linear());
1856 ConstraintProto* new_ct2 = context_->
working_model->add_constraints();
1858 if (!
ct->name().empty()) {
1859 new_ct2->set_name(absl::StrCat(
ct->name(),
" (part 2)"));
1862 new_ct2->mutable_linear());
1865 return (
void)RemoveConstraint(
ct);
1871 const int64 threshold = lower_bounded ? ub_threshold : lb_threshold;
1874 const bool only_booleans =
1875 !options_.
parameters.presolve_extract_integer_enforcement();
1880 int64 rhs_offset = 0;
1881 bool some_integer_encoding_were_extracted =
false;
1882 LinearConstraintProto* mutable_arg =
ct->mutable_linear();
1883 for (
int i = 0; i < arg.vars_size(); ++i) {
1884 int ref = arg.vars(i);
1885 int64 coeff = arg.coeffs(i);
1892 if (context_->
IsFixed(ref) || coeff < threshold ||
1893 (only_booleans && !is_boolean)) {
1895 mutable_arg->set_vars(new_size, mutable_arg->vars(i));
1896 mutable_arg->set_coeffs(new_size, mutable_arg->coeffs(i));
1904 some_integer_encoding_were_extracted =
true;
1906 "linear: extracted integer enforcement literal");
1908 if (lower_bounded) {
1909 ct->add_enforcement_literal(is_boolean
1912 ref, context_->
MinOf(ref)));
1913 rhs_offset -= coeff * context_->
MinOf(ref);
1915 ct->add_enforcement_literal(is_boolean
1918 ref, context_->
MaxOf(ref)));
1919 rhs_offset -= coeff * context_->
MaxOf(ref);
1922 mutable_arg->mutable_vars()->Truncate(new_size);
1923 mutable_arg->mutable_coeffs()->Truncate(new_size);
1925 if (some_integer_encoding_were_extracted) {
1931 void CpModelPresolver::ExtractAtMostOneFromLinear(ConstraintProto*
ct) {
1936 const LinearConstraintProto& arg =
ct->linear();
1937 const int num_vars = arg.vars_size();
1940 for (
int i = 0; i < num_vars; ++i) {
1941 const int ref = arg.vars(i);
1942 const int64 coeff = arg.coeffs(i);
1943 const int64 term_a = coeff * context_->
MinOf(ref);
1944 const int64 term_b = coeff * context_->
MaxOf(ref);
1945 min_sum +=
std::min(term_a, term_b);
1946 max_sum +=
std::max(term_a, term_b);
1948 for (
const int type : {0, 1}) {
1949 std::vector<int> at_most_one;
1950 for (
int i = 0; i < num_vars; ++i) {
1951 const int ref = arg.vars(i);
1952 const int64 coeff = arg.coeffs(i);
1953 if (context_->
MinOf(ref) != 0)
continue;
1954 if (context_->
MaxOf(ref) != 1)
continue;
1959 if (min_sum + 2 * std::abs(coeff) > rhs.Max()) {
1960 at_most_one.push_back(coeff > 0 ? ref :
NegatedRef(ref));
1963 if (max_sum - 2 * std::abs(coeff) < rhs.Min()) {
1964 at_most_one.push_back(coeff > 0 ?
NegatedRef(ref) : ref);
1968 if (at_most_one.size() > 1) {
1974 ConstraintProto* new_ct = context_->
working_model->add_constraints();
1975 new_ct->set_name(
ct->name());
1976 for (
const int ref : at_most_one) {
1977 new_ct->mutable_at_most_one()->add_literals(ref);
1986 bool CpModelPresolver::PresolveLinearOnBooleans(ConstraintProto*
ct) {
1987 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1992 const LinearConstraintProto& arg =
ct->linear();
1993 const int num_vars = arg.vars_size();
1995 int64 max_coeff = 0;
1998 for (
int i = 0; i < num_vars; ++i) {
2000 const int var = arg.vars(i);
2001 const int64 coeff = arg.coeffs(i);
2004 if (context_->
MinOf(
var) != 0)
return false;
2005 if (context_->
MaxOf(
var) != 1)
return false;
2009 min_coeff =
std::min(min_coeff, coeff);
2010 max_coeff =
std::max(max_coeff, coeff);
2014 min_coeff =
std::min(min_coeff, -coeff);
2015 max_coeff =
std::max(max_coeff, -coeff);
2027 if ((!rhs_domain.Contains(min_sum) &&
2028 min_sum + min_coeff > rhs_domain.Max()) ||
2029 (!rhs_domain.Contains(max_sum) &&
2030 max_sum - min_coeff < rhs_domain.Min())) {
2031 context_->
UpdateRuleStats(
"linear: all booleans and trivially false");
2032 return MarkConstraintAsFalse(
ct);
2034 if (Domain(min_sum, max_sum).IsIncludedIn(rhs_domain)) {
2036 return RemoveConstraint(
ct);
2043 DCHECK(!rhs_domain.IsEmpty());
2044 if (min_sum + min_coeff > rhs_domain.Max()) {
2047 const auto copy = arg;
2048 ct->mutable_bool_and()->clear_literals();
2049 for (
int i = 0; i < num_vars; ++i) {
2050 ct->mutable_bool_and()->add_literals(
2051 copy.coeffs(i) > 0 ?
NegatedRef(copy.vars(i)) : copy.vars(i));
2053 return PresolveBoolAnd(
ct);
2054 }
else if (max_sum - min_coeff < rhs_domain.Min()) {
2057 const auto copy = arg;
2058 ct->mutable_bool_and()->clear_literals();
2059 for (
int i = 0; i < num_vars; ++i) {
2060 ct->mutable_bool_and()->add_literals(
2061 copy.coeffs(i) > 0 ? copy.vars(i) :
NegatedRef(copy.vars(i)));
2063 return PresolveBoolAnd(
ct);
2064 }
else if (min_sum + min_coeff >= rhs_domain.Min() &&
2065 rhs_domain.front().end >= max_sum) {
2068 const auto copy = arg;
2069 ct->mutable_bool_or()->clear_literals();
2070 for (
int i = 0; i < num_vars; ++i) {
2071 ct->mutable_bool_or()->add_literals(
2072 copy.coeffs(i) > 0 ? copy.vars(i) :
NegatedRef(copy.vars(i)));
2074 return PresolveBoolOr(
ct);
2075 }
else if (max_sum - min_coeff <= rhs_domain.Max() &&
2076 rhs_domain.back().start <= min_sum) {
2079 const auto copy = arg;
2080 ct->mutable_bool_or()->clear_literals();
2081 for (
int i = 0; i < num_vars; ++i) {
2082 ct->mutable_bool_or()->add_literals(
2083 copy.coeffs(i) > 0 ?
NegatedRef(copy.vars(i)) : copy.vars(i));
2085 return PresolveBoolOr(
ct);
2087 min_sum + max_coeff <= rhs_domain.Max() &&
2088 min_sum + 2 * min_coeff > rhs_domain.Max() &&
2089 rhs_domain.back().start <= min_sum) {
2092 const auto copy = arg;
2093 ct->mutable_at_most_one()->clear_literals();
2094 for (
int i = 0; i < num_vars; ++i) {
2095 ct->mutable_at_most_one()->add_literals(
2096 copy.coeffs(i) > 0 ? copy.vars(i) :
NegatedRef(copy.vars(i)));
2100 max_sum - max_coeff >= rhs_domain.Min() &&
2101 max_sum - 2 * min_coeff < rhs_domain.Min() &&
2102 rhs_domain.front().end >= max_sum) {
2105 const auto copy = arg;
2106 ct->mutable_at_most_one()->clear_literals();
2107 for (
int i = 0; i < num_vars; ++i) {
2108 ct->mutable_at_most_one()->add_literals(
2109 copy.coeffs(i) > 0 ?
NegatedRef(copy.vars(i)) : copy.vars(i));
2113 min_sum < rhs_domain.Min() &&
2114 min_sum + min_coeff >= rhs_domain.Min() &&
2115 min_sum + 2 * min_coeff > rhs_domain.Max() &&
2116 min_sum + max_coeff <= rhs_domain.Max()) {
2118 ConstraintProto* at_least_one = context_->
working_model->add_constraints();
2119 ConstraintProto* at_most_one = context_->
working_model->add_constraints();
2120 at_least_one->set_name(
ct->name());
2121 at_most_one->set_name(
ct->name());
2122 for (
int i = 0; i < num_vars; ++i) {
2123 at_least_one->mutable_bool_or()->add_literals(
2124 arg.coeffs(i) > 0 ? arg.vars(i) :
NegatedRef(arg.vars(i)));
2125 at_most_one->mutable_at_most_one()->add_literals(
2126 arg.coeffs(i) > 0 ? arg.vars(i) :
NegatedRef(arg.vars(i)));
2129 return RemoveConstraint(
ct);
2131 max_sum > rhs_domain.Max() &&
2132 max_sum - min_coeff <= rhs_domain.Max() &&
2133 max_sum - 2 * min_coeff < rhs_domain.Min() &&
2134 max_sum - max_coeff >= rhs_domain.Min()) {
2136 ConstraintProto* at_least_one = context_->
working_model->add_constraints();
2137 ConstraintProto* at_most_one = context_->
working_model->add_constraints();
2138 at_least_one->set_name(
ct->name());
2139 at_most_one->set_name(
ct->name());
2140 for (
int i = 0; i < num_vars; ++i) {
2141 at_least_one->mutable_bool_or()->add_literals(
2142 arg.coeffs(i) > 0 ?
NegatedRef(arg.vars(i)) : arg.vars(i));
2143 at_most_one->mutable_at_most_one()->add_literals(
2144 arg.coeffs(i) > 0 ?
NegatedRef(arg.vars(i)) : arg.vars(i));
2147 return RemoveConstraint(
ct);
2154 if (num_vars > 3)
return false;
2159 const int max_mask = (1 << arg.vars_size());
2160 for (
int mask = 0; mask < max_mask; ++mask) {
2162 for (
int i = 0; i < num_vars; ++i) {
2163 if ((mask >> i) & 1)
value += arg.coeffs(i);
2165 if (rhs_domain.Contains(
value))
continue;
2168 ConstraintProto* new_ct = context_->
working_model->add_constraints();
2169 auto* new_arg = new_ct->mutable_bool_or();
2171 *new_ct->mutable_enforcement_literal() =
ct->enforcement_literal();
2173 for (
int i = 0; i < num_vars; ++i) {
2174 new_arg->add_literals(((mask >> i) & 1) ?
NegatedRef(arg.vars(i))
2180 return RemoveConstraint(
ct);
2183 bool CpModelPresolver::PresolveInterval(
int c, ConstraintProto*
ct) {
2186 const int start =
ct->interval().start();
2187 const int end =
ct->interval().end();
2188 const int size =
ct->interval().size();
2190 if (
ct->enforcement_literal().empty()) {
2191 bool changed =
false;
2192 const Domain start_domain = context_->
DomainOf(start);
2193 const Domain end_domain = context_->
DomainOf(end);
2194 const Domain size_domain = context_->
DomainOf(size);
2201 end, start_domain.AdditionWith(size_domain), &changed)) {
2205 start, end_domain.AdditionWith(size_domain.Negation()), &changed)) {
2209 size, end_domain.AdditionWith(start_domain.Negation()), &changed)) {
2219 ConstraintProto* new_ct = context_->
working_model->add_constraints();
2220 *(new_ct->mutable_enforcement_literal()) =
ct->enforcement_literal();
2221 new_ct->mutable_linear()->add_domain(0);
2222 new_ct->mutable_linear()->add_domain(0);
2223 new_ct->mutable_linear()->add_vars(start);
2224 new_ct->mutable_linear()->add_coeffs(1);
2225 new_ct->mutable_linear()->add_vars(size);
2226 new_ct->mutable_linear()->add_coeffs(1);
2227 new_ct->mutable_linear()->add_vars(end);
2228 new_ct->mutable_linear()->add_coeffs(-1);
2232 return RemoveConstraint(
ct);
2240 if ( (
false) &&
ct->enforcement_literal().empty() &&
2243 1, context_->
MinOf(size));
2250 bool CpModelPresolver::PresolveElement(ConstraintProto*
ct) {
2253 const int index_ref =
ct->element().index();
2254 const int target_ref =
ct->element().target();
2260 bool all_constants =
true;
2261 absl::flat_hash_set<int64> constant_set;
2262 bool all_included_in_target_domain =
true;
2265 bool reduced_index_domain =
false;
2267 Domain(0,
ct->element().vars_size() - 1),
2268 &reduced_index_domain)) {
2274 Domain infered_domain;
2275 const Domain& initial_index_domain = context_->
DomainOf(index_ref);
2276 const Domain& target_domain = context_->
DomainOf(target_ref);
2277 for (
const ClosedInterval
interval : initial_index_domain) {
2281 const int ref =
ct->element().vars(
value);
2282 const Domain& domain = context_->
DomainOf(ref);
2283 if (domain.IntersectionWith(target_domain).IsEmpty()) {
2284 bool domain_modified =
false;
2286 index_ref, Domain(
value).Complement(), &domain_modified)) {
2289 reduced_index_domain =
true;
2292 if (domain.IsFixed()) {
2293 constant_set.insert(domain.Min());
2295 all_constants =
false;
2297 if (!domain.IsIncludedIn(target_domain)) {
2298 all_included_in_target_domain =
false;
2300 infered_domain = infered_domain.
UnionWith(domain);
2304 if (reduced_index_domain) {
2307 bool domain_modified =
false;
2309 &domain_modified)) {
2312 if (domain_modified) {
2318 if (context_->
IsFixed(index_ref)) {
2319 const int var =
ct->element().vars(context_->
MinOf(index_ref));
2320 if (
var != target_ref) {
2321 LinearConstraintProto*
const lin =
2322 context_->
working_model->add_constraints()->mutable_linear();
2324 lin->add_coeffs(-1);
2325 lin->add_vars(target_ref);
2332 return RemoveConstraint(
ct);
2338 if (all_constants && constant_set.size() == 1) {
2341 return RemoveConstraint(
ct);
2346 if (context_->
MinOf(index_ref) == 0 && context_->
MaxOf(index_ref) == 1 &&
2348 const int64 v0 = context_->
MinOf(
ct->element().vars(0));
2349 const int64 v1 = context_->
MinOf(
ct->element().vars(1));
2351 LinearConstraintProto*
const lin =
2352 context_->
working_model->add_constraints()->mutable_linear();
2353 lin->add_vars(target_ref);
2355 lin->add_vars(index_ref);
2356 lin->add_coeffs(v0 - v1);
2357 lin->add_domain(v0);
2358 lin->add_domain(v0);
2360 context_->
UpdateRuleStats(
"element: linearize constant element of size 2");
2361 return RemoveConstraint(
ct);
2365 const AffineRelation::Relation r_index =
2367 if (r_index.representative != index_ref) {
2369 if (context_->
DomainOf(r_index.representative).
Size() >
2377 const int array_size =
ct->element().vars_size();
2379 context_->
UpdateRuleStats(
"TODO element: representative has bad domain");
2380 }
else if (r_index.offset >= 0 && r_index.offset < array_size &&
2381 r_index.offset + r_max * r_index.coeff >= 0 &&
2382 r_index.offset + r_max * r_index.coeff < array_size) {
2384 ElementConstraintProto*
const element =
2385 context_->
working_model->add_constraints()->mutable_element();
2386 for (
int64 v = 0; v <= r_max; ++v) {
2387 const int64 scaled_index = v * r_index.coeff + r_index.offset;
2389 CHECK_LT(scaled_index, array_size);
2390 element->add_vars(
ct->element().vars(scaled_index));
2392 element->set_index(r_ref);
2393 element->set_target(target_ref);
2395 if (r_index.coeff == 1) {
2401 return RemoveConstraint(
ct);
2407 if (all_constants && unique_index) {
2411 context_->
UpdateRuleStats(
"element: trivial target domain reduction");
2414 return RemoveConstraint(
ct);
2417 const bool unique_target =
2419 context_->
IsFixed(target_ref);
2420 if (all_included_in_target_domain && unique_target) {
2424 return RemoveConstraint(
ct);
2427 if (target_ref == index_ref) {
2429 std::vector<int64> possible_indices;
2430 const Domain& index_domain = context_->
DomainOf(index_ref);
2431 for (
const ClosedInterval&
interval : index_domain) {
2433 const int ref =
ct->element().vars(
value);
2435 possible_indices.push_back(
value);
2439 if (possible_indices.size() < index_domain.Size()) {
2446 "element: reduce index domain when target equals index");
2451 if (unique_target && !context_->
IsFixed(target_ref)) {
2461 bool CpModelPresolver::PresolveTable(ConstraintProto*
ct) {
2464 if (
ct->table().vars().empty()) {
2466 return RemoveConstraint(
ct);
2472 const int num_vars =
ct->table().vars_size();
2473 const int num_tuples =
ct->table().values_size() / num_vars;
2474 std::vector<int64> tuple(num_vars);
2475 std::vector<std::vector<int64>> new_tuples;
2476 new_tuples.reserve(num_tuples);
2477 std::vector<absl::flat_hash_set<int64>> new_domains(num_vars);
2478 std::vector<AffineRelation::Relation> affine_relations;
2480 absl::flat_hash_set<int> visited;
2481 for (
const int ref :
ct->table().vars()) {
2489 bool modified_variables =
false;
2490 for (
int v = 0; v < num_vars; ++v) {
2491 const int ref =
ct->table().vars(v);
2493 affine_relations.push_back(r);
2494 if (r.representative != ref) {
2495 modified_variables =
true;
2499 for (
int i = 0; i < num_tuples; ++i) {
2500 bool delete_row =
false;
2502 for (
int j = 0; j < num_vars; ++j) {
2503 const int ref =
ct->table().vars(j);
2504 int64 v =
ct->table().values(i * num_vars + j);
2505 const AffineRelation::Relation& r = affine_relations[j];
2506 if (r.representative != ref) {
2507 const int64 inverse_value = (v - r.offset) / r.coeff;
2508 if (inverse_value * r.coeff + r.offset != v) {
2521 if (delete_row)
continue;
2522 new_tuples.push_back(tuple);
2523 for (
int j = 0; j < num_vars; ++j) {
2524 const int64 v = tuple[j];
2525 new_domains[j].insert(v);
2531 if (new_tuples.size() < num_tuples || modified_variables) {
2532 ct->mutable_table()->clear_values();
2533 for (
const std::vector<int64>& t : new_tuples) {
2534 for (
const int64 v : t) {
2535 ct->mutable_table()->add_values(v);
2538 if (new_tuples.size() < num_tuples) {
2543 if (modified_variables) {
2544 for (
int j = 0; j < num_vars; ++j) {
2545 const AffineRelation::Relation& r = affine_relations[j];
2546 if (r.representative !=
ct->table().vars(j)) {
2547 ct->mutable_table()->set_vars(j, r.representative);
2551 "table: replace variable by canonical affine one");
2555 if (
ct->table().negated())
return modified_variables;
2558 bool changed =
false;
2559 for (
int j = 0; j < num_vars; ++j) {
2560 const int ref =
ct->table().vars(j);
2564 new_domains[j].end())),
2572 if (num_vars == 1) {
2575 return RemoveConstraint(
ct);
2580 for (
int j = 0; j < num_vars; ++j) prod *= new_domains[j].size();
2581 if (prod == new_tuples.size()) {
2583 return RemoveConstraint(
ct);
2589 if (new_tuples.size() > 0.7 * prod) {
2591 std::vector<std::vector<int64>> var_to_values(num_vars);
2592 for (
int j = 0; j < num_vars; ++j) {
2593 var_to_values[j].assign(new_domains[j].begin(), new_domains[j].end());
2595 std::vector<std::vector<int64>> all_tuples(prod);
2596 for (
int i = 0; i < prod; ++i) {
2597 all_tuples[i].resize(num_vars);
2599 for (
int j = 0; j < num_vars; ++j) {
2600 all_tuples[i][j] = var_to_values[j][
index % var_to_values[j].size()];
2601 index /= var_to_values[j].size();
2607 std::vector<std::vector<int64>> diff(prod - new_tuples.size());
2608 std::set_difference(all_tuples.begin(), all_tuples.end(),
2609 new_tuples.begin(), new_tuples.end(), diff.begin());
2612 ct->mutable_table()->set_negated(!
ct->table().negated());
2613 ct->mutable_table()->clear_values();
2614 for (
const std::vector<int64>& t : diff) {
2615 for (
const int64 v : t)
ct->mutable_table()->add_values(v);
2619 return modified_variables;
2622 bool CpModelPresolver::PresolveAllDiff(ConstraintProto*
ct) {
2626 AllDifferentConstraintProto& all_diff = *
ct->mutable_all_diff();
2628 bool constraint_has_changed =
false;
2630 const int size = all_diff.vars_size();
2633 return RemoveConstraint(
ct);
2637 return RemoveConstraint(
ct);
2640 bool something_was_propagated =
false;
2641 std::vector<int> new_variables;
2642 for (
int i = 0; i < size; ++i) {
2643 if (!context_->
IsFixed(all_diff.vars(i))) {
2644 new_variables.push_back(all_diff.vars(i));
2649 bool propagated =
false;
2650 for (
int j = 0; j < size; ++j) {
2651 if (i == j)
continue;
2654 Domain(
value).Complement())) {
2662 something_was_propagated =
true;
2666 std::sort(new_variables.begin(), new_variables.end());
2667 for (
int i = 1; i < new_variables.size(); ++i) {
2668 if (new_variables[i] == new_variables[i - 1]) {
2670 "Duplicate variable in all_diff");
2674 if (new_variables.size() < all_diff.vars_size()) {
2675 all_diff.mutable_vars()->Clear();
2676 for (
const int var : new_variables) {
2677 all_diff.add_vars(
var);
2680 something_was_propagated =
true;
2681 constraint_has_changed =
true;
2682 if (new_variables.size() <= 1)
continue;
2687 Domain domain = context_->
DomainOf(all_diff.vars(0));
2688 for (
int i = 1; i < all_diff.vars_size(); ++i) {
2691 if (all_diff.vars_size() == domain.Size()) {
2692 absl::flat_hash_map<int64, std::vector<int>> value_to_refs;
2693 for (
const int ref : all_diff.vars()) {
2696 value_to_refs[v].push_back(ref);
2700 bool propagated =
false;
2701 for (
const auto& it : value_to_refs) {
2702 if (it.second.size() == 1 &&
2704 const int ref = it.second.
front();
2713 "all_diff: propagated mandatory values in permutation");
2714 something_was_propagated =
true;
2717 if (!something_was_propagated)
break;
2720 return constraint_has_changed;
2727 std::vector<int> GetLiteralsFromSetPPCConstraint(ConstraintProto*
ct) {
2728 std::vector<int> sorted_literals;
2729 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne) {
2730 for (
const int literal :
ct->at_most_one().literals()) {
2731 sorted_literals.push_back(
literal);
2733 }
else if (
ct->constraint_case() ==
2734 ConstraintProto::ConstraintCase::kBoolOr) {
2735 for (
const int literal :
ct->bool_or().literals()) {
2736 sorted_literals.push_back(
literal);
2739 std::sort(sorted_literals.begin(), sorted_literals.end());
2740 return sorted_literals;
2745 void AddImplication(
int lhs,
int rhs, CpModelProto*
proto,
2746 absl::flat_hash_map<int, int>* ref_to_bool_and) {
2747 if (ref_to_bool_and->contains(lhs)) {
2748 const int ct_index = (*ref_to_bool_and)[lhs];
2749 proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(rhs);
2750 }
else if (ref_to_bool_and->contains(
NegatedRef(rhs))) {
2751 const int ct_index = (*ref_to_bool_and)[
NegatedRef(rhs)];
2752 proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(
2755 (*ref_to_bool_and)[lhs] =
proto->constraints_size();
2756 ConstraintProto*
ct =
proto->add_constraints();
2757 ct->add_enforcement_literal(lhs);
2758 ct->mutable_bool_and()->add_literals(rhs);
2762 template <
typename ClauseContainer>
2763 void ExtractClauses(
bool use_bool_and,
const ClauseContainer& container,
2764 CpModelProto*
proto) {
2771 absl::flat_hash_map<int, int> ref_to_bool_and;
2772 for (
int i = 0; i < container.NumClauses(); ++i) {
2773 const std::vector<Literal>& clause = container.Clause(i);
2774 if (clause.empty())
continue;
2777 if (use_bool_and && clause.size() == 2) {
2778 const int a = clause[0].IsPositive()
2779 ? clause[0].Variable().value()
2781 const int b = clause[1].IsPositive()
2782 ? clause[1].Variable().value()
2789 ConstraintProto*
ct =
proto->add_constraints();
2790 for (
const Literal l : clause) {
2791 if (l.IsPositive()) {
2792 ct->mutable_bool_or()->add_literals(l.Variable().value());
2794 ct->mutable_bool_or()->add_literals(
NegatedRef(l.Variable().value()));
2802 bool CpModelPresolver::PresolveNoOverlap(ConstraintProto*
ct) {
2805 const NoOverlapConstraintProto&
proto =
ct->no_overlap();
2809 for (
int i = 0; i <
proto.intervals_size(); ++i) {
2810 const int interval_index =
proto.intervals(i);
2812 .constraint_case() ==
2813 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
2816 ct->mutable_no_overlap()->set_intervals(new_size++, interval_index);
2818 ct->mutable_no_overlap()->mutable_intervals()->Truncate(new_size);
2822 ct->mutable_no_overlap()->mutable_intervals()->begin(),
2823 ct->mutable_no_overlap()->mutable_intervals()->end(),
2824 [
this](
int i1,
int i2) {
2825 return context_->MinOf(context_->working_model->constraints(i1)
2829 context_->working_model->constraints(i2).interval().start());
2838 for (
int i = 0; i <
proto.intervals_size(); ++i) {
2839 const int interval_index =
proto.intervals(i);
2840 const IntervalConstraintProto&
interval =
2841 context_->
working_model->constraints(interval_index).interval();
2842 const int64 end_max_of_previous_intervals = end_max_so_far;
2844 if (context_->
MinOf(
interval.start()) >= end_max_of_previous_intervals &&
2845 (i + 1 ==
proto.intervals_size() ||
2854 ct->mutable_no_overlap()->set_intervals(new_size++, interval_index);
2856 ct->mutable_no_overlap()->mutable_intervals()->Truncate(new_size);
2858 if (
proto.intervals_size() == 1) {
2860 return RemoveConstraint(
ct);
2862 if (
proto.intervals().empty()) {
2864 return RemoveConstraint(
ct);
2869 bool CpModelPresolver::PresolveCumulative(ConstraintProto*
ct) {
2872 const CumulativeConstraintProto&
proto =
ct->cumulative();
2876 bool changed =
false;
2877 int num_zero_demand_removed = 0;
2878 for (
int i = 0; i <
proto.intervals_size(); ++i) {
2880 .constraint_case() ==
2881 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
2885 const int demand_ref =
proto.demands(i);
2886 const int64 demand_max = context_->
MaxOf(demand_ref);
2887 if (demand_max == 0) {
2888 num_zero_demand_removed++;
2892 ct->mutable_cumulative()->set_intervals(new_size,
proto.intervals(i));
2893 ct->mutable_cumulative()->set_demands(new_size,
proto.demands(i));
2896 if (new_size <
proto.intervals_size()) {
2898 ct->mutable_cumulative()->mutable_intervals()->Truncate(new_size);
2899 ct->mutable_cumulative()->mutable_demands()->Truncate(new_size);
2902 if (num_zero_demand_removed > 0) {
2903 context_->
UpdateRuleStats(
"cumulative: removed intervals with no demands");
2906 if (new_size == 0) {
2908 return RemoveConstraint(
ct);
2912 if (!context_->
IsFixed(
proto.capacity()))
return changed;
2915 const int size =
proto.intervals_size();
2916 std::vector<int> start_indices(size, -1);
2918 int num_duration_one = 0;
2919 int num_greater_half_capacity = 0;
2921 bool has_optional_interval =
false;
2922 for (
int i = 0; i < size; ++i) {
2924 const ConstraintProto&
ct =
2926 if (!
ct.enforcement_literal().empty()) has_optional_interval =
true;
2927 const IntervalConstraintProto&
interval =
ct.interval();
2928 start_indices[i] =
interval.start();
2929 const int duration_ref =
interval.size();
2930 const int demand_ref =
proto.demands(i);
2931 if (context_->
IsFixed(duration_ref) && context_->
MinOf(duration_ref) == 1) {
2934 if (context_->
MinOf(duration_ref) == 0) {
2939 const int64 demand_min = context_->
MinOf(demand_ref);
2940 const int64 demand_max = context_->
MaxOf(demand_ref);
2942 num_greater_half_capacity++;
2946 if (
ct.enforcement_literal().empty()) {
2949 CHECK_EQ(
ct.enforcement_literal().size(), 1);
2955 }
else if (demand_max >
capacity) {
2956 if (
ct.enforcement_literal().empty()) {
2957 context_->
UpdateRuleStats(
"cumulative: demand_max exceeds capacity.");
2966 "cumulative: demand_max of optional interval exceeds capacity.");
2972 if (num_greater_half_capacity == size) {
2973 if (num_duration_one == size && !has_optional_interval) {
2975 ConstraintProto* new_ct = context_->
working_model->add_constraints();
2976 auto* arg = new_ct->mutable_all_diff();
2977 for (
const int var : start_indices) {
2981 return RemoveConstraint(
ct);
2984 ConstraintProto* new_ct = context_->
working_model->add_constraints();
2985 auto* arg = new_ct->mutable_no_overlap();
2990 return RemoveConstraint(
ct);
2997 bool CpModelPresolver::PresolveRoutes(ConstraintProto*
ct) {
3000 RoutesConstraintProto&
proto = *
ct->mutable_routes();
3003 const int num_arcs =
proto.literals_size();
3004 for (
int i = 0; i < num_arcs; ++i) {
3005 const int ref =
proto.literals(i);
3012 proto.set_literals(new_size, ref);
3017 if (new_size < num_arcs) {
3018 proto.mutable_literals()->Truncate(new_size);
3019 proto.mutable_tails()->Truncate(new_size);
3020 proto.mutable_heads()->Truncate(new_size);
3026 bool CpModelPresolver::PresolveCircuit(ConstraintProto*
ct) {
3029 CircuitConstraintProto&
proto = *
ct->mutable_circuit();
3033 std::vector<std::vector<int>> incoming_arcs;
3034 std::vector<std::vector<int>> outgoing_arcs;
3036 const int num_arcs =
proto.literals_size();
3037 for (
int i = 0; i < num_arcs; ++i) {
3038 const int ref =
proto.literals(i);
3046 incoming_arcs[
head].push_back(ref);
3047 outgoing_arcs[
tail].push_back(ref);
3055 bool loop_again =
true;
3056 int num_fixed_at_true = 0;
3057 while (loop_again) {
3059 for (
const auto* node_to_refs : {&incoming_arcs, &outgoing_arcs}) {
3060 for (
const std::vector<int>& refs : *node_to_refs) {
3061 if (refs.size() == 1) {
3063 ++num_fixed_at_true;
3072 for (
const int ref : refs) {
3082 if (num_true == 1) {
3083 for (
const int ref : refs) {
3084 if (ref != true_ref) {
3085 if (!context_->
IsFixed(ref)) {
3096 if (num_fixed_at_true > 0) {
3103 int circuit_start = -1;
3104 std::vector<int>
next(num_nodes, -1);
3105 std::vector<int> new_in_degree(num_nodes, 0);
3106 std::vector<int> new_out_degree(num_nodes, 0);
3107 for (
int i = 0; i < num_arcs; ++i) {
3108 const int ref =
proto.literals(i);
3116 circuit_start =
proto.tails(i);
3120 ++new_out_degree[
proto.tails(i)];
3121 ++new_in_degree[
proto.heads(i)];
3124 proto.set_literals(new_size,
proto.literals(i));
3134 for (
int i = 0; i < num_nodes; ++i) {
3136 if (incoming_arcs[i].empty() && outgoing_arcs[i].empty())
continue;
3138 if (new_in_degree[i] == 0 || new_out_degree[i] == 0) {
3144 if (circuit_start != -1) {
3145 std::vector<bool> visited(num_nodes,
false);
3146 int current = circuit_start;
3147 while (current != -1 && !visited[current]) {
3148 visited[current] =
true;
3149 current =
next[current];
3151 if (current == circuit_start) {
3154 for (
int i = 0; i < num_arcs; ++i) {
3155 if (visited[
proto.tails(i)])
continue;
3163 return RemoveConstraint(
ct);
3167 if (num_true == new_size) {
3169 return RemoveConstraint(
ct);
3175 for (
int i = 0; i < num_nodes; ++i) {
3176 for (
const std::vector<int>* arc_literals :
3177 {&incoming_arcs[i], &outgoing_arcs[i]}) {
3178 std::vector<int> literals;
3179 for (
const int ref : *arc_literals) {
3185 literals.push_back(ref);
3187 if (literals.size() == 2 && literals[0] !=
NegatedRef(literals[1])) {
3196 if (new_size < num_arcs) {
3197 proto.mutable_tails()->Truncate(new_size);
3198 proto.mutable_heads()->Truncate(new_size);
3199 proto.mutable_literals()->Truncate(new_size);
3206 bool CpModelPresolver::PresolveAutomaton(ConstraintProto*
ct) {
3209 AutomatonConstraintProto&
proto = *
ct->mutable_automaton();
3210 if (
proto.vars_size() == 0 ||
proto.transition_label_size() == 0) {
3214 bool all_affine =
true;
3215 std::vector<AffineRelation::Relation> affine_relations;
3216 for (
int v = 0; v <
proto.vars_size(); ++v) {
3217 const int var =
ct->automaton().vars(v);
3219 affine_relations.push_back(r);
3220 if (r.representative ==
var) {
3224 if (v > 0 && (r.coeff != affine_relations[v - 1].coeff ||
3225 r.offset != affine_relations[v - 1].offset)) {
3232 for (
int v = 0; v <
proto.vars_size(); ++v) {
3235 const AffineRelation::Relation rep = affine_relations.front();
3237 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
3238 const int64 label =
proto.transition_label(t);
3239 int64 inverse_label = (label - rep.offset) / rep.coeff;
3240 if (inverse_label * rep.coeff + rep.offset == label) {
3241 if (new_size != t) {
3242 proto.set_transition_tail(new_size,
proto.transition_tail(t));
3243 proto.set_transition_head(new_size,
proto.transition_head(t));
3245 proto.set_transition_label(new_size, inverse_label);
3249 if (new_size <
proto.transition_tail_size()) {
3250 proto.mutable_transition_tail()->Truncate(new_size);
3251 proto.mutable_transition_label()->Truncate(new_size);
3252 proto.mutable_transition_head()->Truncate(new_size);
3260 for (
int v = 1; v <
proto.vars_size(); ++v) {
3265 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
3266 const int64 label =
proto.transition_label(t);
3267 if (hull.Contains(label)) {
3268 if (new_size != t) {
3269 proto.set_transition_tail(new_size,
proto.transition_tail(t));
3270 proto.set_transition_label(new_size, label);
3271 proto.set_transition_head(new_size,
proto.transition_head(t));
3276 if (new_size <
proto.transition_tail_size()) {
3277 proto.mutable_transition_tail()->Truncate(new_size);
3278 proto.mutable_transition_label()->Truncate(new_size);
3279 proto.mutable_transition_head()->Truncate(new_size);
3284 const int n =
proto.vars_size();
3285 const std::vector<int> vars = {
proto.vars().begin(),
proto.vars().end()};
3288 std::vector<std::set<int64>> reachable_states(n + 1);
3289 reachable_states[0].insert(
proto.starting_state());
3290 reachable_states[n] = {
proto.final_states().begin(),
3291 proto.final_states().end()};
3298 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
3300 const int64 label =
proto.transition_label(t);
3304 reachable_states[
time + 1].insert(
head);
3308 std::vector<std::set<int64>> reached_values(n);
3312 std::set<int64> new_set;
3313 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
3315 const int64 label =
proto.transition_label(t);
3321 new_set.insert(
tail);
3322 reached_values[
time].insert(label);
3324 reachable_states[
time].swap(new_set);
3327 bool removed_values =
false;
3332 {reached_values[time].begin(), reached_values[time].end()}),
3337 if (removed_values) {
3346 void CpModelPresolver::ExtractBoolAnd() {
3347 absl::flat_hash_map<int, int> ref_to_bool_and;
3348 const int num_constraints = context_->
working_model->constraints_size();
3349 std::vector<int> to_remove;
3350 for (
int c = 0; c < num_constraints; ++c) {
3354 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr &&
3355 ct.bool_or().literals().size() == 2) {
3359 to_remove.push_back(c);
3363 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne &&
3364 ct.at_most_one().literals().size() == 2) {
3365 AddImplication(
ct.at_most_one().literals(0),
3368 to_remove.push_back(c);
3374 for (
const int c : to_remove) {
3375 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
3381 void CpModelPresolver::Probe() {
3385 for (
int i = 0; i < context_->
working_model->variables_size(); ++i) {
3401 auto* local_param =
model.GetOrCreate<SatParameters>();
3403 local_param->set_use_implied_bounds(
false);
3406 auto* encoder =
model.GetOrCreate<IntegerEncoder>();
3407 encoder->DisableImplicationBetweenLiteral();
3408 auto* mapping =
model.GetOrCreate<CpModelMapping>();
3412 auto* sat_solver =
model.GetOrCreate<SatSolver>();
3413 for (
const ConstraintProto&
ct :
model_proto.constraints()) {
3414 if (mapping->ConstraintIsAlreadyLoaded(&
ct))
continue;
3416 if (sat_solver->IsModelUnsat()) {
3420 encoder->AddAllImplicationsBetweenAssociatedLiterals();
3421 if (!sat_solver->Propagate()) {
3429 auto* implication_graph =
model.GetOrCreate<BinaryImplicationGraph>();
3435 if (sat_solver->IsModelUnsat() || !implication_graph->DetectEquivalences()) {
3440 CHECK_EQ(sat_solver->CurrentDecisionLevel(), 0);
3441 for (
int i = 0; i < sat_solver->LiteralTrail().
Index(); ++i) {
3442 const Literal l = sat_solver->LiteralTrail()[i];
3443 const int var = mapping->GetProtoVariableFromBooleanVariable(l.Variable());
3450 const int num_variables = context_->
working_model->variables().size();
3451 auto* integer_trail =
model.GetOrCreate<IntegerTrail>();
3452 for (
int var = 0;
var < num_variables; ++
var) {
3455 if (!mapping->IsBoolean(
var)) {
3458 integer_trail->InitialVariableDomain(mapping->Integer(
var)))) {
3465 const Literal l = mapping->Literal(
var);
3466 const Literal r = implication_graph->RepresentativeOf(l);
3469 mapping->GetProtoVariableFromBooleanVariable(r.Variable());
3480 void CpModelPresolver::PresolvePureSatPart() {
3485 const int num_variables = context_->
working_model->variables_size();
3486 SatPostsolver sat_postsolver(num_variables);
3487 SatPresolver sat_presolver(&sat_postsolver);
3488 sat_presolver.SetNumVariables(num_variables);
3489 sat_presolver.SetTimeLimit(options_.
time_limit);
3498 if (params.cp_model_postsolve_with_full_solver()) {
3499 params.set_presolve_blocked_clause(
false);
3505 params.set_presolve_use_bva(
false);
3506 sat_presolver.SetParameters(params);
3509 absl::flat_hash_set<int> used_variables;
3510 auto convert = [&used_variables](
int ref) {
3512 if (
RefIsPositive(ref))
return Literal(BooleanVariable(ref),
true);
3513 return Literal(BooleanVariable(
NegatedRef(ref)),
false);
3521 for (
int c = 0; c < context_->
working_model->constraints_size(); ++c) {
3523 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr ||
3524 ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolAnd) {
3543 std::vector<Literal> clause;
3544 int num_removed_constraints = 0;
3545 for (
int i = 0; i < context_->
working_model->constraints_size(); ++i) {
3548 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr) {
3549 ++num_removed_constraints;
3551 for (
const int ref :
ct.bool_or().literals()) {
3552 clause.push_back(convert(ref));
3554 for (
const int ref :
ct.enforcement_literal()) {
3555 clause.push_back(convert(ref).Negated());
3557 sat_presolver.AddClause(clause);
3564 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolAnd) {
3565 ++num_removed_constraints;
3566 std::vector<Literal> clause;
3567 for (
const int ref :
ct.enforcement_literal()) {
3568 clause.push_back(convert(ref).Negated());
3571 for (
const int ref :
ct.bool_and().literals()) {
3572 clause.back() = convert(ref);
3573 sat_presolver.AddClause(clause);
3583 if (num_removed_constraints == 0)
return;
3593 std::vector<bool> can_be_removed(num_variables,
false);
3594 for (
int i = 0; i < num_variables; ++i) {
3596 can_be_removed[i] =
true;
3602 if (used_variables.contains(i) && context_->
IsFixed(i)) {
3604 sat_presolver.AddClause({convert(i)});
3606 sat_presolver.AddClause({convert(
NegatedRef(i))});
3614 const int num_passes = params.presolve_use_bva() ? 4 : 1;
3615 for (
int i = 0; i < num_passes; ++i) {
3616 const int old_num_clause = sat_postsolver.NumClauses();
3617 if (!sat_presolver.Presolve(can_be_removed, options_.
log_info)) {
3618 VLOG(1) <<
"UNSAT during SAT presolve.";
3621 if (old_num_clause == sat_postsolver.NumClauses())
break;
3625 const int new_num_variables = sat_presolver.NumVariables();
3626 if (new_num_variables > context_->
working_model->variables_size()) {
3627 VLOG(1) <<
"New variables added by the SAT presolver.";
3629 i < new_num_variables; ++i) {
3630 IntegerVariableProto* var_proto =
3632 var_proto->add_domain(0);
3633 var_proto->add_domain(1);
3639 ExtractClauses(
true, sat_presolver, context_->
working_model);
3647 ExtractClauses(
false, sat_postsolver,
3655 void CpModelPresolver::ExpandObjective() {
3674 int unique_expanded_constraint = -1;
3675 const bool objective_was_a_single_variable =
3680 const int num_variables = context_->
working_model->variables_size();
3681 const int num_constraints = context_->
working_model->constraints_size();
3682 absl::flat_hash_set<int> relevant_constraints;
3683 std::vector<int> var_to_num_relevant_constraints(num_variables, 0);
3684 for (
int ct_index = 0; ct_index < num_constraints; ++ct_index) {
3685 const ConstraintProto&
ct = context_->
working_model->constraints(ct_index);
3687 if (!
ct.enforcement_literal().empty() ||
3688 ct.constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
3689 ct.linear().domain().size() != 2 ||
3690 ct.linear().domain(0) !=
ct.linear().domain(1)) {
3694 relevant_constraints.insert(ct_index);
3695 const int num_terms =
ct.linear().vars_size();
3696 for (
int i = 0; i < num_terms; ++i) {
3697 var_to_num_relevant_constraints[
PositiveRef(
ct.linear().vars(i))]++;
3701 std::set<int> var_to_process;
3703 const int var = entry.first;
3705 if (var_to_num_relevant_constraints[
var] != 0) {
3706 var_to_process.insert(
var);
3711 int num_expansions = 0;
3712 absl::flat_hash_set<int> processed_vars;
3713 std::vector<int> new_vars_in_objective;
3714 while (!relevant_constraints.empty()) {
3716 int objective_var = -1;
3717 while (!var_to_process.empty()) {
3718 const int var = *var_to_process.begin();
3719 CHECK(!processed_vars.contains(
var));
3720 if (var_to_num_relevant_constraints[
var] == 0) {
3721 processed_vars.insert(
var);
3722 var_to_process.erase(
var);
3727 var_to_process.erase(
var);
3730 objective_var =
var;
3734 if (objective_var == -1)
break;
3736 processed_vars.insert(objective_var);
3737 var_to_process.erase(objective_var);
3739 int expanded_linear_index = -1;
3740 int64 objective_coeff_in_expanded_constraint;
3741 int64 size_of_expanded_constraint = 0;
3742 const auto& non_deterministic_list =
3744 std::vector<int> constraints_with_objective(non_deterministic_list.begin(),
3745 non_deterministic_list.end());
3746 std::sort(constraints_with_objective.begin(),
3747 constraints_with_objective.end());
3748 for (
const int ct_index : constraints_with_objective) {
3749 if (relevant_constraints.count(ct_index) == 0)
continue;
3750 const ConstraintProto&
ct =
3755 relevant_constraints.erase(ct_index);
3756 const int num_terms =
ct.linear().vars_size();
3757 for (
int i = 0; i < num_terms; ++i) {
3758 var_to_num_relevant_constraints[
PositiveRef(
ct.linear().vars(i))]--;
3770 bool is_present =
false;
3771 int64 objective_coeff;
3772 for (
int i = 0; i < num_terms; ++i) {
3773 const int ref =
ct.linear().vars(i);
3774 const int64 coeff =
ct.linear().coeffs(i);
3776 CHECK(!is_present) <<
"Duplicate variables not supported.";
3778 objective_coeff = (ref == objective_var) ? coeff : -coeff;
3791 if (std::abs(objective_coeff) == 1 &&
3792 num_terms > size_of_expanded_constraint) {
3793 expanded_linear_index = ct_index;
3794 size_of_expanded_constraint = num_terms;
3795 objective_coeff_in_expanded_constraint = objective_coeff;
3799 if (expanded_linear_index != -1) {
3800 context_->
UpdateRuleStats(
"objective: expanded objective constraint.");
3804 CHECK_EQ(std::abs(objective_coeff_in_expanded_constraint), 1);
3805 const ConstraintProto&
ct =
3806 context_->
working_model->constraints(expanded_linear_index);
3808 objective_var, objective_coeff_in_expanded_constraint,
ct,
3809 &new_vars_in_objective);
3812 for (
const int var : new_vars_in_objective) {
3813 if (!processed_vars.contains(
var)) var_to_process.insert(
var);
3826 for (
int i = 0; i < size_of_expanded_constraint; ++i) {
3827 const int ref =
ct.linear().vars(i);
3832 -
ct.linear().coeffs(i)))
3833 .RelaxIfTooComplex();
3835 implied_domain = implied_domain.InverseMultiplicationBy(
3836 objective_coeff_in_expanded_constraint);
3840 if (implied_domain.IsIncludedIn(context_->
DomainOf(objective_var))) {
3841 context_->
UpdateRuleStats(
"objective: removed objective constraint.");
3843 context_->
working_model->mutable_constraints(expanded_linear_index)
3847 unique_expanded_constraint = expanded_linear_index;
3857 if (num_expansions == 1 && objective_was_a_single_variable &&
3858 unique_expanded_constraint != -1) {
3860 "objective: removed unique objective constraint.");
3861 ConstraintProto* mutable_ct = context_->
working_model->mutable_constraints(
3862 unique_expanded_constraint);
3863 *(context_->
mapping_model->add_constraints()) = *mutable_ct;
3864 mutable_ct->Clear();
3876 void CpModelPresolver::MergeNoOverlapConstraints() {
3879 const int num_constraints = context_->
working_model->constraints_size();
3880 int old_num_no_overlaps = 0;
3881 int old_num_intervals = 0;
3884 std::vector<int> disjunctive_index;
3885 std::vector<std::vector<Literal>> cliques;
3886 for (
int c = 0; c < num_constraints; ++c) {
3888 if (
ct.constraint_case() != ConstraintProto::ConstraintCase::kNoOverlap) {
3891 std::vector<Literal> clique;
3892 for (
const int i :
ct.no_overlap().intervals()) {
3893 clique.push_back(Literal(BooleanVariable(i),
true));
3895 cliques.push_back(clique);
3896 disjunctive_index.push_back(c);
3898 old_num_no_overlaps++;
3899 old_num_intervals += clique.size();
3901 if (old_num_no_overlaps == 0)
return;
3905 local_model.GetOrCreate<Trail>()->Resize(num_constraints);
3906 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
3907 graph->Resize(num_constraints);
3908 for (
const std::vector<Literal>& clique : cliques) {
3911 CHECK(graph->AddAtMostOne(clique));
3913 CHECK(graph->DetectEquivalences());
3914 graph->TransformIntoMaxCliques(
3915 &cliques, options_.
parameters.merge_no_overlap_work_limit());
3918 int new_num_no_overlaps = 0;
3919 int new_num_intervals = 0;
3920 for (
int i = 0; i < cliques.size(); ++i) {
3921 const int ct_index = disjunctive_index[i];
3922 ConstraintProto*
ct =
3925 if (cliques[i].empty())
continue;
3926 for (
const Literal l : cliques[i]) {
3927 CHECK(l.IsPositive());
3928 ct->mutable_no_overlap()->add_intervals(l.Variable().value());
3930 new_num_no_overlaps++;
3931 new_num_intervals += cliques[i].size();
3933 if (old_num_intervals != new_num_intervals ||
3934 old_num_no_overlaps != new_num_no_overlaps) {
3935 VLOG(1) << absl::StrCat(
"Merged ", old_num_no_overlaps,
" no-overlaps (",
3936 old_num_intervals,
" intervals) into ",
3937 new_num_no_overlaps,
" no-overlaps (",
3938 new_num_intervals,
" intervals).");
3943 void CpModelPresolver::TransformIntoMaxCliques() {
3946 auto convert = [](
int ref) {
3947 if (
RefIsPositive(ref))
return Literal(BooleanVariable(ref),
true);
3948 return Literal(BooleanVariable(
NegatedRef(ref)),
false);
3950 const int num_constraints = context_->
working_model->constraints_size();
3953 std::vector<std::vector<Literal>> cliques;
3955 for (
int c = 0; c < num_constraints; ++c) {
3956 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
3957 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne) {
3958 std::vector<Literal> clique;
3959 for (
const int ref :
ct->at_most_one().literals()) {
3960 clique.push_back(convert(ref));
3962 cliques.push_back(clique);
3963 if (RemoveConstraint(
ct)) {
3966 }
else if (
ct->constraint_case() ==
3967 ConstraintProto::ConstraintCase::kBoolAnd) {
3968 if (
ct->enforcement_literal().size() != 1)
continue;
3969 const Literal enforcement = convert(
ct->enforcement_literal(0));
3970 for (
const int ref :
ct->bool_and().literals()) {
3971 cliques.push_back({enforcement, convert(ref).Negated()});
3973 if (RemoveConstraint(
ct)) {
3979 const int num_old_cliques = cliques.size();
3983 const int num_variables = context_->
working_model->variables().size();
3984 local_model.GetOrCreate<Trail>()->Resize(num_variables);
3985 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
3986 graph->Resize(num_variables);
3987 for (
const std::vector<Literal>& clique : cliques) {
3988 if (!graph->AddAtMostOne(clique)) {
3992 if (!graph->DetectEquivalences()) {
3995 graph->TransformIntoMaxCliques(
3996 &cliques, options_.
parameters.merge_at_most_one_work_limit());
4001 for (
int var = 0;
var < num_variables; ++
var) {
4002 const Literal l = Literal(BooleanVariable(
var),
true);
4003 if (graph->RepresentativeOf(l) != l) {
4004 const Literal r = graph->RepresentativeOf(l);
4006 var, r.IsPositive() ? r.Variable().value()
4011 int num_new_cliques = 0;
4012 for (
const std::vector<Literal>& clique : cliques) {
4013 if (clique.empty())
continue;
4016 for (
const Literal
literal : clique) {
4018 ct->mutable_at_most_one()->add_literals(
literal.Variable().value());
4020 ct->mutable_at_most_one()->add_literals(
4026 if (num_new_cliques != num_old_cliques) {
4027 context_->
UpdateRuleStats(
"at_most_one: transformed into max clique.");
4031 LOG(
INFO) <<
"Merged " << num_old_cliques <<
" into " << num_new_cliques
4038 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
4041 if (ExploitEquivalenceRelations(c,
ct)) {
4046 if (PresolveEnforcementLiteral(
ct)) {
4051 switch (
ct->constraint_case()) {
4052 case ConstraintProto::ConstraintCase::kBoolOr:
4053 return PresolveBoolOr(
ct);
4054 case ConstraintProto::ConstraintCase::kBoolAnd:
4055 return PresolveBoolAnd(
ct);
4056 case ConstraintProto::ConstraintCase::kAtMostOne:
4057 return PresolveAtMostOne(
ct);
4058 case ConstraintProto::ConstraintCase::kBoolXor:
4059 return PresolveBoolXor(
ct);
4060 case ConstraintProto::ConstraintCase::kIntMax:
4061 if (
ct->int_max().vars_size() == 2 &&
4063 return PresolveIntAbs(
ct);
4065 return PresolveIntMax(
ct);
4067 case ConstraintProto::ConstraintCase::kIntMin:
4068 return PresolveIntMin(
ct);
4069 case ConstraintProto::ConstraintCase::kLinMax:
4070 return PresolveLinMax(
ct);
4071 case ConstraintProto::ConstraintCase::kLinMin:
4072 return PresolveLinMin(
ct);
4073 case ConstraintProto::ConstraintCase::kIntProd:
4074 return PresolveIntProd(
ct);
4075 case ConstraintProto::ConstraintCase::kIntDiv:
4076 return PresolveIntDiv(
ct);
4077 case ConstraintProto::ConstraintCase::kLinear: {
4078 if (CanonicalizeLinear(
ct)) {
4081 if (PresolveSmallLinear(
ct)) {
4084 if (PropagateDomainsInLinear(c,
ct)) {
4087 if (PresolveSmallLinear(
ct)) {
4091 if (RemoveSingletonInLinear(
ct)) {
4096 if (PresolveSmallLinear(
ct)) {
4100 if (PresolveLinearOnBooleans(
ct)) {
4103 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kLinear) {
4104 const int old_num_enforcement_literals =
ct->enforcement_literal_size();
4105 ExtractEnforcementLiteralFromLinearConstraint(c,
ct);
4106 if (
ct->constraint_case() ==
4107 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
4111 if (
ct->enforcement_literal_size() > old_num_enforcement_literals &&
4112 PresolveSmallLinear(
ct)) {
4115 PresolveLinearEqualityModuloTwo(
ct);
4119 case ConstraintProto::ConstraintCase::kInterval:
4120 return PresolveInterval(c,
ct);
4121 case ConstraintProto::ConstraintCase::kElement:
4122 return PresolveElement(
ct);
4123 case ConstraintProto::ConstraintCase::kTable:
4124 return PresolveTable(
ct);
4125 case ConstraintProto::ConstraintCase::kAllDiff:
4126 return PresolveAllDiff(
ct);
4127 case ConstraintProto::ConstraintCase::kNoOverlap:
4128 return PresolveNoOverlap(
ct);
4129 case ConstraintProto::ConstraintCase::kCumulative:
4130 return PresolveCumulative(
ct);
4131 case ConstraintProto::ConstraintCase::kCircuit:
4132 return PresolveCircuit(
ct);
4133 case ConstraintProto::ConstraintCase::kRoutes:
4134 return PresolveRoutes(
ct);
4135 case ConstraintProto::ConstraintCase::kAutomaton:
4136 return PresolveAutomaton(
ct);
4142 bool CpModelPresolver::ProcessSetPPCSubset(
4143 int c1,
int c2,
const std::vector<int>& c2_minus_c1,
4144 const std::vector<int>& original_constraint_index,
4145 std::vector<bool>* marked_for_removal) {
4147 CHECK(!(*marked_for_removal)[c1]);
4148 CHECK(!(*marked_for_removal)[c2]);
4149 ConstraintProto* ct1 = context_->
working_model->mutable_constraints(
4150 original_constraint_index[c1]);
4151 ConstraintProto* ct2 = context_->
working_model->mutable_constraints(
4152 original_constraint_index[c2]);
4153 if (ct1->constraint_case() == ConstraintProto::ConstraintCase::kBoolOr &&
4154 ct2->constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne) {
4156 for (
const int literal : c2_minus_c1) {
4162 if (ct1->constraint_case() == ct2->constraint_case()) {
4163 if (ct1->constraint_case() == ConstraintProto::ConstraintCase::kBoolOr) {
4164 (*marked_for_removal)[c2] =
true;
4169 ConstraintProto::ConstraintCase::kAtMostOne);
4170 (*marked_for_removal)[c1] =
true;
4180 bool CpModelPresolver::ProcessSetPPC() {
4181 bool changed =
false;
4182 const int num_constraints = context_->
working_model->constraints_size();
4186 std::vector<uint64> signatures;
4190 std::vector<std::vector<int>> constraint_literals;
4194 std::vector<std::vector<int>> literals_to_constraints;
4199 std::vector<bool> marked_for_removal;
4203 std::vector<int> original_constraint_index;
4207 int num_setppc_constraints = 0;
4208 for (
int c = 0; c < num_constraints; ++c) {
4209 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
4210 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kBoolOr ||
4211 ct->constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne) {
4220 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kBoolOr ||
4221 ct->constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne) {
4222 constraint_literals.push_back(GetLiteralsFromSetPPCConstraint(
ct));
4225 for (
const int literal : constraint_literals.back()) {
4227 signature |= (
int64{1} << (positive_literal % 64));
4229 if (positive_literal >= literals_to_constraints.size()) {
4230 literals_to_constraints.resize(positive_literal + 1);
4232 literals_to_constraints[positive_literal].push_back(
4233 num_setppc_constraints);
4235 signatures.push_back(signature);
4236 marked_for_removal.push_back(
false);
4237 original_constraint_index.push_back(c);
4238 num_setppc_constraints++;
4241 VLOG(1) <<
"#setppc constraints: " << num_setppc_constraints;
4244 absl::flat_hash_set<std::pair<int, int>> compared_constraints;
4245 for (
const std::vector<int>& literal_to_constraints :
4246 literals_to_constraints) {
4247 for (
int index1 = 0; index1 < literal_to_constraints.size(); ++index1) {
4252 const int c1 = literal_to_constraints[index1];
4253 if (marked_for_removal[c1])
continue;
4254 const std::vector<int>& c1_literals = constraint_literals[c1];
4255 ConstraintProto* ct1 = context_->
working_model->mutable_constraints(
4256 original_constraint_index[c1]);
4257 for (
int index2 = index1 + 1; index2 < literal_to_constraints.size();
4259 const int c2 = literal_to_constraints[index2];
4260 if (marked_for_removal[c2])
continue;
4261 if (marked_for_removal[c1])
break;
4263 if (c1 == c2)
continue;
4267 std::pair<int, int>(c1, c2))) {
4270 compared_constraints.insert({c1, c2});
4274 if (compared_constraints.size() >= 50000)
return changed;
4276 const bool smaller = (signatures[c1] & ~signatures[c2]) == 0;
4277 const bool larger = (signatures[c2] & ~signatures[c1]) == 0;
4279 if (!(smaller || larger)) {
4284 const std::vector<int>& c2_literals = constraint_literals[c2];
4285 ConstraintProto* ct2 = context_->
working_model->mutable_constraints(
4286 original_constraint_index[c2]);
4289 std::vector<int> c1_minus_c2;
4291 std::vector<int> c2_minus_c1;
4294 if (c1_minus_c2.empty() && c2_minus_c1.empty()) {
4295 if (ct1->constraint_case() == ct2->constraint_case()) {
4296 marked_for_removal[c2] =
true;
4299 }
else if (c1_minus_c2.empty()) {
4300 if (ProcessSetPPCSubset(c1, c2, c2_minus_c1,
4301 original_constraint_index,
4302 &marked_for_removal)) {
4304 original_constraint_index[c1]);
4306 original_constraint_index[c2]);
4308 }
else if (c2_minus_c1.empty()) {
4309 if (ProcessSetPPCSubset(c2, c1, c1_minus_c2,
4310 original_constraint_index,
4311 &marked_for_removal)) {
4313 original_constraint_index[c1]);
4315 original_constraint_index[c2]);
4321 for (
int c = 0; c < num_setppc_constraints; ++c) {
4322 if (marked_for_removal[c]) {
4324 original_constraint_index[c]);
4325 changed = RemoveConstraint(
ct);
4333 void CpModelPresolver::TryToSimplifyDomain(
int var) {
4341 if (r.representative !=
var)
return;
4356 if (domain.Size() == 2 && (domain.Min() != 0 || domain.Max() != 1)) {
4361 if (domain.NumIntervals() != domain.Size())
return;
4363 const int64 var_min = domain.Min();
4364 int64 gcd = domain[1].start - var_min;
4366 const ClosedInterval& i = domain[
index];
4368 const int64 shifted_value = i.start - var_min;
4372 if (gcd == 1)
break;
4374 if (gcd == 1)
return;
4378 std::vector<int64> scaled_values;
4380 const ClosedInterval& i = domain[
index];
4382 const int64 shifted_value = i.start - var_min;
4383 scaled_values.push_back(shifted_value / gcd);
4395 void CpModelPresolver::EncodeAllAffineRelations() {
4396 int64 num_added = 0;
4401 if (r.representative ==
var)
continue;
4408 if (!PresolveAffineRelationIfAny(
var))
break;
4415 auto* arg =
ct->mutable_linear();
4418 arg->add_vars(r.representative);
4419 arg->add_coeffs(-r.coeff);
4420 arg->add_domain(r.offset);
4421 arg->add_domain(r.offset);
4429 if (options_.
log_info && num_added > 0) {
4430 LOG(
INFO) << num_added <<
" affine relations still in the model.";
4435 bool CpModelPresolver::PresolveAffineRelationIfAny(
int var) {
4439 if (r.representative ==
var)
return true;
4458 auto* arg =
ct->mutable_linear();
4461 arg->add_vars(r.representative);
4462 arg->add_coeffs(-r.coeff);
4463 arg->add_domain(r.offset);
4464 arg->add_domain(r.offset);
4470 void CpModelPresolver::PresolveToFixPoint() {
4474 const int64 max_num_operations =
4475 options_.
parameters.cp_model_max_num_presolve_operations() > 0
4476 ? options_.
parameters.cp_model_max_num_presolve_operations()
4482 absl::flat_hash_set<std::pair<int, int>> var_constraint_pair_already_called;
4487 std::vector<bool> in_queue(context_->
working_model->constraints_size(),
4489 std::deque<int> queue;
4490 for (
int c = 0; c < in_queue.size(); ++c) {
4491 if (context_->
working_model->constraints(c).constraint_case() !=
4492 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
4502 std::sort(queue.begin(), queue.end(), [
this](
int a,
int b) {
4503 const int score_a = context_->ConstraintToVars(a).size();
4504 const int score_b = context_->ConstraintToVars(b).size();
4505 return score_a < score_b || (score_a == score_b && a < b);
4514 const int c = queue.front();
4515 in_queue[c] =
false;
4518 const int old_num_constraint =
4522 LOG(
INFO) <<
"Unsat after presolving constraint #" << c
4523 <<
" (warning, dump might be inconsistent): "
4524 << context_->
working_model->constraints(c).ShortDebugString();
4528 const int new_num_constraints =
4530 if (new_num_constraints > old_num_constraint) {
4532 in_queue.resize(new_num_constraints,
true);
4533 for (
int c = old_num_constraint; c < new_num_constraints; ++c) {
4550 const int current_num_variables = context_->
working_model->variables_size();
4551 for (
int v = 0; v < current_num_variables; ++v) {
4553 if (!PresolveAffineRelationIfAny(v))
return;
4558 TryToSimplifyDomain(v);
4567 in_queue.resize(context_->
working_model->constraints_size(),
false);
4572 if (c >= 0 && !in_queue[c]) {
4582 const int num_vars = context_->
working_model->variables_size();
4583 for (
int v = 0; v < num_vars; ++v) {
4585 if (constraints.size() != 1)
continue;
4586 const int c = *constraints.begin();
4587 if (c < 0)
continue;
4593 std::pair<int, int>(v, c))) {
4596 var_constraint_pair_already_called.insert({v, c});
4606 std::sort(queue.begin(), queue.end());
4619 VarDomination var_dom;
4620 DualBoundStrengthening dual_bound_strengthening;
4622 if (!dual_bound_strengthening.Strengthen(context_))
return;
4634 const int num_constraints = context_->
working_model->constraints_size();
4635 for (
int c = 0; c < num_constraints; ++c) {
4636 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
4637 switch (
ct->constraint_case()) {
4638 case ConstraintProto::ConstraintCase::kNoOverlap:
4640 if (PresolveNoOverlap(
ct)) {
4644 case ConstraintProto::ConstraintCase::kNoOverlap2D:
4648 case ConstraintProto::ConstraintCase::kCumulative:
4650 if (PresolveCumulative(
ct)) {
4654 case ConstraintProto::ConstraintCase::kBoolOr: {
4657 for (
const auto& pair :
4659 bool modified =
false;
4680 <<
" affine relations were detected.";
4682 <<
" variable equivalence relations were detected.";
4683 std::map<std::string, int> sorted_rules(
context->stats_by_rule_name.begin(),
4684 context->stats_by_rule_name.end());
4685 for (
const auto& entry : sorted_rules) {
4686 if (entry.second == 1) {
4687 LOG(
INFO) <<
"- rule '" << entry.first <<
"' was applied 1 time.";
4689 LOG(
INFO) <<
"- rule '" << entry.first <<
"' was applied " << entry.second
4700 std::vector<int>* postsolve_mapping) {
4707 std::vector<int>* postsolve_mapping)
4708 : options_(options),
4709 postsolve_mapping_(postsolve_mapping),
4712 options.
parameters.keep_all_feasible_solutions_in_presolve() ||
4713 options.
parameters.enumerate_all_solutions() ||
4714 options.
parameters.fill_tightened_domains_in_response() ||
4718 for (
const auto& decision_strategy :
4720 *(context_->
mapping_model->add_search_strategy()) = decision_strategy;
4755 if (!options_.
parameters.cp_model_presolve()) {
4767 for (
int c = 0; c < context_->
working_model->constraints_size(); ++c) {
4768 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
4769 PresolveEnforcementLiteral(
ct);
4770 switch (
ct->constraint_case()) {
4771 case ConstraintProto::ConstraintCase::kBoolOr:
4774 case ConstraintProto::ConstraintCase::kBoolAnd:
4775 PresolveBoolAnd(
ct);
4777 case ConstraintProto::ConstraintCase::kAtMostOne:
4778 PresolveAtMostOne(
ct);
4780 case ConstraintProto::ConstraintCase::kLinear:
4781 CanonicalizeLinear(
ct);
4793 for (
int iter = 0; iter < options_.
parameters.max_presolve_iterations();
4798 int old_num_non_empty_constraints = 0;
4799 for (
int c = 0; c < context_->
working_model->constraints_size(); ++c) {
4802 if (type == ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET)
continue;
4803 old_num_non_empty_constraints++;
4810 PresolveToFixPoint();
4817 if (options_.
parameters.cp_model_probing_level() > 0) {
4821 PresolveToFixPoint();
4828 if (options_.
parameters.cp_model_use_sat_presolve()) {
4831 PresolvePureSatPart();
4844 const int old_size = context_->
working_model->constraints_size();
4845 for (
int c = 0; c < old_size; ++c) {
4846 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
4847 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear) {
4850 ExtractAtMostOneFromLinear(
ct);
4855 if (iter == 0) TransformIntoMaxCliques();
4865 PresolveToFixPoint();
4871 old_num_non_empty_constraints)) {
4878 MergeNoOverlapConstraints();
4888 EncodeAllAffineRelations();
4895 const std::vector<int> duplicates =
4897 if (!duplicates.empty()) {
4898 for (
const int c : duplicates) {
4901 if (type == ConstraintProto::ConstraintCase::kInterval) {
4918 context_->
working_model->add_constraints()->mutable_bool_or();
4930 absl::flat_hash_set<int> used_variables;
4931 for (DecisionStrategyProto& strategy :
4933 DecisionStrategyProto copy = strategy;
4934 strategy.clear_variables();
4935 for (
const int ref : copy.variables()) {
4944 used_variables.insert(
var);
4952 strategy.add_variables(rep);
4953 if (strategy.variable_selection_strategy() !=
4954 DecisionStrategyProto::CHOOSE_FIRST) {
4955 DecisionStrategyProto::AffineTransformation* t =
4956 strategy.add_transformations();
4959 t->set_positive_coeff(std::abs(r.
coeff));
4967 strategy.add_variables(ref);
4973 for (
int i = 0; i < context_->
working_model->variables_size(); ++i) {
4984 postsolve_mapping_->clear();
4985 std::vector<int> mapping(context_->
working_model->variables_size(), -1);
4986 for (
int i = 0; i < context_->
working_model->variables_size(); ++i) {
4991 mapping[i] = postsolve_mapping_->size();
4992 postsolve_mapping_->push_back(i);
5016 if (!error.empty()) {
5018 LOG(
INFO) <<
"Error while validating postsolved model: " << error;
5025 if (!error.empty()) {
5027 LOG(
INFO) <<
"Error while validating mapping_model model: " << error;
5041 auto mapping_function = [&mapping](
int* ref) {
5046 for (ConstraintProto& ct_ref : *
proto->mutable_constraints()) {
5052 if (
proto->has_objective()) {
5053 for (
int& mutable_ref : *
proto->mutable_objective()->mutable_vars()) {
5054 mapping_function(&mutable_ref);
5059 for (
int& mutable_ref : *
proto->mutable_assumptions()) {
5060 mapping_function(&mutable_ref);
5065 for (DecisionStrategyProto& strategy : *
proto->mutable_search_strategy()) {
5066 DecisionStrategyProto copy = strategy;
5067 strategy.clear_variables();
5068 for (
const int ref : copy.variables()) {
5074 strategy.clear_transformations();
5075 for (
const auto& transform : copy.transformations()) {
5076 const int ref = transform.var();
5079 auto* new_transform = strategy.add_transformations();
5080 *new_transform = transform;
5087 if (
proto->has_solution_hint()) {
5088 auto* mutable_hint =
proto->mutable_solution_hint();
5090 for (
int i = 0; i < mutable_hint->vars_size(); ++i) {
5091 const int old_ref = mutable_hint->vars(i);
5092 const int64 old_value = mutable_hint->values(i);
5100 const int image = mapping[
var];
5102 mutable_hint->set_vars(new_size, image);
5103 mutable_hint->set_values(new_size,
value);
5108 mutable_hint->mutable_vars()->Truncate(new_size);
5109 mutable_hint->mutable_values()->Truncate(new_size);
5111 proto->clear_solution_hint();
5116 std::vector<IntegerVariableProto> new_variables;
5117 for (
int i = 0; i < mapping.size(); ++i) {
5118 const int image = mapping[i];
5119 if (image < 0)
continue;
5120 if (image >= new_variables.size()) {
5121 new_variables.resize(image + 1, IntegerVariableProto());
5123 new_variables[image].Swap(
proto->mutable_variables(i));
5125 proto->clear_variables();
5126 for (IntegerVariableProto& proto_ref : new_variables) {
5127 proto->add_variables()->Swap(&proto_ref);
5131 for (
const IntegerVariableProto& v :
proto->variables()) {
5137 std::vector<int> result;
5140 absl::flat_hash_map<int64, int> equiv_constraints;
5143 const int num_constraints =
model_proto.constraints().size();
5144 for (
int c = 0; c < num_constraints; ++c) {
5145 if (
model_proto.constraints(c).constraint_case() ==
5146 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
5149 s =
model_proto.constraints(c).SerializeAsString();
5150 const int64 hash = std::hash<std::string>()(s);
5151 const auto insert = equiv_constraints.insert({
hash, c});
5152 if (!insert.second) {
5154 const int other_c_with_same_hash = insert.first->second;
5156 model_proto.constraints(other_c_with_same_hash).SerializeAsString()) {
5157 result.push_back(c);