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"
55 bool CpModelPresolver::RemoveConstraint(ConstraintProto*
ct) {
63 std::vector<int> interval_mapping(context_->
working_model->constraints_size(),
65 int new_num_constraints = 0;
66 const int old_num_non_empty_constraints =
68 for (
int c = 0; c < old_num_non_empty_constraints; ++c) {
69 const auto type = context_->
working_model->constraints(c).constraint_case();
70 if (type == ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET)
continue;
71 if (type == ConstraintProto::ConstraintCase::kInterval) {
72 interval_mapping[c] = new_num_constraints;
74 context_->
working_model->mutable_constraints(new_num_constraints++)
77 context_->
working_model->mutable_constraints()->DeleteSubrange(
78 new_num_constraints, old_num_non_empty_constraints - new_num_constraints);
79 for (ConstraintProto& ct_ref :
82 [&interval_mapping](
int* ref) {
83 *ref = interval_mapping[*ref];
90 bool CpModelPresolver::PresolveEnforcementLiteral(ConstraintProto*
ct) {
95 const int old_size =
ct->enforcement_literal().size();
96 for (
const int literal :
ct->enforcement_literal()) {
105 return RemoveConstraint(
ct);
112 return RemoveConstraint(
ct);
119 const int64 obj_coeff =
123 context_->
UpdateRuleStats(
"enforcement literal with unique direction");
125 return RemoveConstraint(
ct);
129 ct->set_enforcement_literal(new_size++,
literal);
131 ct->mutable_enforcement_literal()->Truncate(new_size);
132 return new_size != old_size;
135 bool CpModelPresolver::PresolveBoolXor(ConstraintProto*
ct) {
140 bool changed =
false;
141 int num_true_literals = 0;
143 for (
const int literal :
ct->bool_xor().literals()) {
164 ct->mutable_bool_xor()->set_literals(new_size++,
literal);
168 }
else if (new_size == 2) {
171 if (num_true_literals % 2 == 1) {
173 ct->mutable_bool_xor()->set_literals(new_size++, true_literal);
175 if (num_true_literals > 1) {
176 context_->
UpdateRuleStats(
"bool_xor: remove even number of true literals");
179 ct->mutable_bool_xor()->mutable_literals()->Truncate(new_size);
183 bool CpModelPresolver::PresolveBoolOr(ConstraintProto*
ct) {
190 for (
const int literal :
ct->enforcement_literal()) {
193 ct->clear_enforcement_literal();
197 bool changed =
false;
200 for (
const int literal :
ct->bool_or().literals()) {
207 return RemoveConstraint(
ct);
215 return RemoveConstraint(
ct);
219 return RemoveConstraint(
ct);
238 return RemoveConstraint(
ct);
251 ct->mutable_bool_or()->mutable_literals()->Clear();
253 ct->mutable_bool_or()->add_literals(lit);
259 ABSL_MUST_USE_RESULT
bool CpModelPresolver::MarkConstraintAsFalse(
260 ConstraintProto*
ct) {
263 ct->mutable_bool_or()->clear_literals();
264 for (
const int lit :
ct->enforcement_literal()) {
267 ct->clear_enforcement_literal();
275 bool CpModelPresolver::PresolveBoolAnd(ConstraintProto*
ct) {
280 for (
const int literal :
ct->bool_and().literals()) {
283 return RemoveConstraint(
ct);
286 bool changed =
false;
288 for (
const int literal :
ct->bool_and().literals()) {
291 return MarkConstraintAsFalse(
ct);
311 ct->mutable_bool_and()->mutable_literals()->Clear();
313 ct->mutable_bool_and()->add_literals(lit);
320 bool CpModelPresolver::PresolveAtMostOne(ConstraintProto*
ct) {
325 std::sort(
ct->mutable_at_most_one()->mutable_literals()->begin(),
326 ct->mutable_at_most_one()->mutable_literals()->end());
328 for (
const int literal :
ct->at_most_one().literals()) {
336 bool changed =
false;
338 for (
const int literal :
ct->at_most_one().literals()) {
341 for (
const int other :
ct->at_most_one().literals()) {
346 return RemoveConstraint(
ct);
358 return RemoveConstraint(
ct);
362 ct->mutable_at_most_one()->mutable_literals()->Clear();
364 ct->mutable_at_most_one()->add_literals(lit);
371 bool CpModelPresolver::PresolveIntMax(ConstraintProto*
ct) {
373 if (
ct->int_max().vars().empty()) {
375 return MarkConstraintAsFalse(
ct);
377 const int target_ref =
ct->int_max().target();
382 bool contains_target_ref =
false;
383 std::set<int> used_ref;
385 for (
const int ref :
ct->int_max().vars()) {
386 if (ref == target_ref) contains_target_ref =
true;
392 used_ref.insert(ref);
393 ct->mutable_int_max()->set_vars(new_size++, ref);
397 if (new_size < ct->int_max().vars_size()) {
400 ct->mutable_int_max()->mutable_vars()->Truncate(new_size);
401 if (contains_target_ref) {
403 for (
const int ref :
ct->int_max().vars()) {
404 if (ref == target_ref)
continue;
405 ConstraintProto* new_ct = context_->
working_model->add_constraints();
406 *new_ct->mutable_enforcement_literal() =
ct->enforcement_literal();
407 auto* arg = new_ct->mutable_linear();
408 arg->add_vars(target_ref);
415 return RemoveConstraint(
ct);
419 Domain infered_domain;
420 for (
const int ref :
ct->int_max().vars()) {
421 infered_domain = infered_domain.UnionWith(
426 bool domain_reduced =
false;
438 const Domain& target_domain = context_->
DomainOf(target_ref);
439 if (infered_domain.IntersectionWith(Domain(
kint64min, target_domain.Max()))
440 .IsIncludedIn(target_domain)) {
441 if (infered_domain.Max() <= target_domain.Max()) {
444 }
else if (
ct->enforcement_literal().empty()) {
446 for (
const int ref :
ct->int_max().vars()) {
449 ref, Domain(
kint64min, target_domain.Max()))) {
457 for (
const int ref :
ct->int_max().vars()) {
458 ConstraintProto* new_ct = context_->
working_model->add_constraints();
459 *(new_ct->mutable_enforcement_literal()) =
ct->enforcement_literal();
460 ct->mutable_linear()->add_vars(ref);
461 ct->mutable_linear()->add_coeffs(1);
463 ct->mutable_linear()->add_domain(target_domain.Max());
470 return RemoveConstraint(
ct);
476 const int size =
ct->int_max().vars_size();
477 const int64 target_max = context_->
MaxOf(target_ref);
478 for (
const int ref :
ct->int_max().vars()) {
485 if (context_->
MaxOf(ref) >= infered_min) {
486 ct->mutable_int_max()->set_vars(new_size++, ref);
489 if (domain_reduced) {
493 bool modified =
false;
494 if (new_size < size) {
496 ct->mutable_int_max()->mutable_vars()->Truncate(new_size);
502 return MarkConstraintAsFalse(
ct);
508 ConstraintProto* new_ct = context_->
working_model->add_constraints();
510 auto* arg = new_ct->mutable_linear();
511 arg->add_vars(target_ref);
513 arg->add_vars(
ct->int_max().vars(0));
518 return RemoveConstraint(
ct);
523 bool CpModelPresolver::PresolveLinMin(ConstraintProto*
ct) {
526 const auto copy =
ct->lin_min();
528 ct->mutable_lin_max()->mutable_target());
529 for (
const LinearExpressionProto& expr : copy.exprs()) {
530 LinearExpressionProto*
const new_expr =
ct->mutable_lin_max()->add_exprs();
533 return PresolveLinMax(
ct);
536 bool CpModelPresolver::PresolveLinMax(ConstraintProto*
ct) {
538 if (
ct->lin_max().exprs().empty()) {
540 return MarkConstraintAsFalse(
ct);
546 int64 infered_min = context_->
MinOf(
ct->lin_max().target());
547 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
558 for (
int i = 0; i <
ct->lin_max().exprs_size(); ++i) {
559 const LinearExpressionProto& expr =
ct->lin_max().exprs(i);
560 if (context_->
MaxOf(expr) >= infered_min) {
561 *
ct->mutable_lin_max()->mutable_exprs(new_size) = expr;
566 if (new_size < ct->lin_max().exprs_size()) {
568 ct->mutable_lin_max()->mutable_exprs()->DeleteSubrange(
569 new_size,
ct->lin_max().exprs_size() - new_size);
576 bool CpModelPresolver::PresolveIntAbs(ConstraintProto*
ct) {
577 CHECK_EQ(
ct->enforcement_literal_size(), 0);
579 const int target_ref =
ct->int_max().target();
584 const Domain new_target_domain = var_domain.
UnionWith(var_domain.Negation())
594 const Domain target_domain = context_->
DomainOf(target_ref);
595 const Domain new_var_domain =
596 target_domain.
UnionWith(target_domain.Negation());
606 ConstraintProto* new_ct = context_->
working_model->add_constraints();
607 new_ct->set_name(
ct->name());
608 auto* arg = new_ct->mutable_linear();
609 arg->add_vars(target_ref);
616 return RemoveConstraint(
ct);
621 ConstraintProto* new_ct = context_->
working_model->add_constraints();
622 new_ct->set_name(
ct->name());
623 auto* arg = new_ct->mutable_linear();
624 arg->add_vars(target_ref);
631 return RemoveConstraint(
ct);
637 context_->
IsFixed(target_ref)) {
638 if (!context_->
IsFixed(target_ref)) {
643 return RemoveConstraint(
ct);
653 bool CpModelPresolver::PresolveIntMin(ConstraintProto*
ct) {
656 const auto copy =
ct->int_min();
657 ct->mutable_int_max()->set_target(
NegatedRef(copy.target()));
658 for (
const int ref : copy.vars()) {
661 return PresolveIntMax(
ct);
664 bool CpModelPresolver::PresolveIntProd(ConstraintProto*
ct) {
668 bool changed =
false;
673 for (
int i = 0; i <
ct->int_prod().vars().size(); ++i) {
674 const int ref =
ct->int_prod().vars(i);
676 if (r.representative != ref && r.offset == 0) {
678 ct->mutable_int_prod()->set_vars(i, r.representative);
693 const int old_target =
ct->int_prod().target();
694 const int new_target = context_->
working_model->variables_size();
696 IntegerVariableProto* var_proto = context_->
working_model->add_variables();
703 ct->mutable_int_prod()->set_target(new_target);
704 if (context_->
IsFixed(new_target)) {
716 ConstraintProto* new_ct = context_->
working_model->add_constraints();
717 LinearConstraintProto* lin = new_ct->mutable_linear();
718 lin->add_vars(old_target);
720 lin->add_vars(new_target);
721 lin->add_coeffs(-constant);
731 for (
const int ref :
ct->int_prod().vars()) {
732 implied = implied.ContinuousMultiplicationBy(context_->
DomainOf(ref));
734 bool modified =
false;
743 if (
ct->int_prod().vars_size() == 2) {
744 int a =
ct->int_prod().vars(0);
745 int b =
ct->int_prod().vars(1);
746 const int product =
ct->int_prod().target();
751 ConstraintProto*
const lin = context_->
working_model->add_constraints();
752 lin->mutable_linear()->add_vars(
b);
753 lin->mutable_linear()->add_coeffs(context_->
MinOf(
a));
754 lin->mutable_linear()->add_vars(product);
755 lin->mutable_linear()->add_coeffs(-1);
756 lin->mutable_linear()->add_domain(0);
757 lin->mutable_linear()->add_domain(0);
760 return RemoveConstraint(
ct);
761 }
else if (context_->
MinOf(
a) != 1) {
762 bool domain_modified =
false;
768 return RemoveConstraint(
ct);
771 return RemoveConstraint(
ct);
773 }
else if (
a ==
b &&
a == product) {
778 return RemoveConstraint(
ct);
783 const int target_ref =
ct->int_prod().target();
785 for (
const int var :
ct->int_prod().vars()) {
787 if (context_->
MinOf(
var) < 0)
return changed;
788 if (context_->
MaxOf(
var) > 1)
return changed;
797 ConstraintProto* new_ct = context_->
working_model->add_constraints();
798 new_ct->add_enforcement_literal(target_ref);
799 auto* arg = new_ct->mutable_bool_and();
800 for (
const int var :
ct->int_prod().vars()) {
801 arg->add_literals(
var);
805 ConstraintProto* new_ct = context_->
working_model->add_constraints();
806 auto* arg = new_ct->mutable_bool_or();
807 arg->add_literals(target_ref);
808 for (
const int var :
ct->int_prod().vars()) {
813 return RemoveConstraint(
ct);
816 bool CpModelPresolver::PresolveIntDiv(ConstraintProto*
ct) {
820 const int target =
ct->int_div().target();
821 const int ref_x =
ct->int_div().vars(0);
822 const int ref_div =
ct->int_div().vars(1);
829 const int64 divisor = context_->
MinOf(ref_div);
831 LinearConstraintProto*
const lin =
832 context_->
working_model->add_constraints()->mutable_linear();
833 lin->add_vars(ref_x);
835 lin->add_vars(target);
841 return RemoveConstraint(
ct);
843 bool domain_modified =
false;
847 if (domain_modified) {
849 "int_div: updated domain of target in target = X / cte");
860 if (context_->
MinOf(target) >= 0 && context_->
MinOf(ref_x) >= 0 &&
862 LinearConstraintProto*
const lin =
863 context_->
working_model->add_constraints()->mutable_linear();
864 lin->add_vars(ref_x);
866 lin->add_vars(target);
867 lin->add_coeffs(-divisor);
869 lin->add_domain(divisor - 1);
872 "int_div: linearize positive division with a constant divisor");
873 return RemoveConstraint(
ct);
881 bool CpModelPresolver::ExploitEquivalenceRelations(
int c, ConstraintProto*
ct) {
882 bool changed =
false;
887 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kLinear) {
888 for (
int& ref : *
ct->mutable_enforcement_literal()) {
900 bool work_to_do =
false;
903 if (r.representative !=
var) {
908 if (!work_to_do)
return false;
912 [&changed,
this](
int* ref) {
923 [&changed,
this](
int* ref) {
934 void CpModelPresolver::DivideLinearByGcd(ConstraintProto*
ct) {
939 const int num_vars =
ct->linear().vars().size();
940 for (
int i = 0; i < num_vars; ++i) {
941 const int64 magnitude = std::abs(
ct->linear().coeffs(i));
947 for (
int i = 0; i < num_vars; ++i) {
948 ct->mutable_linear()->set_coeffs(i,
ct->linear().coeffs(i) / gcd);
952 if (
ct->linear().domain_size() == 0) {
953 return (
void)MarkConstraintAsFalse(
ct);
958 bool CpModelPresolver::CanonicalizeLinear(ConstraintProto*
ct) {
959 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
971 int64 sum_of_fixed_terms = 0;
972 bool remapped =
false;
973 const int num_vars =
ct->linear().vars().size();
974 DCHECK_EQ(num_vars,
ct->linear().coeffs().size());
975 for (
int i = 0; i < num_vars; ++i) {
976 const int ref =
ct->linear().vars(i);
980 if (coeff == 0)
continue;
983 sum_of_fixed_terms += coeff * context_->
MinOf(
var);
989 bool removed =
false;
990 for (
const int enf :
ct->enforcement_literal()) {
994 sum_of_fixed_terms += coeff;
1003 context_->
UpdateRuleStats(
"linear: enforcement literal in constraint");
1008 if (r.representative !=
var) {
1010 sum_of_fixed_terms += coeff * r.
offset;
1012 tmp_terms_.push_back({r.representative, coeff * r.coeff});
1015 if (sum_of_fixed_terms != 0) {
1017 rhs = rhs.AdditionWith({-sum_of_fixed_terms, -sum_of_fixed_terms});
1021 ct->mutable_linear()->clear_vars();
1022 ct->mutable_linear()->clear_coeffs();
1023 std::sort(tmp_terms_.begin(), tmp_terms_.end());
1024 int current_var = 0;
1025 int64 current_coeff = 0;
1026 for (
const auto entry : tmp_terms_) {
1028 if (entry.first == current_var) {
1029 current_coeff += entry.second;
1031 if (current_coeff != 0) {
1032 ct->mutable_linear()->add_vars(current_var);
1033 ct->mutable_linear()->add_coeffs(current_coeff);
1035 current_var = entry.first;
1036 current_coeff = entry.second;
1039 if (current_coeff != 0) {
1040 ct->mutable_linear()->add_vars(current_var);
1041 ct->mutable_linear()->add_coeffs(current_coeff);
1043 DivideLinearByGcd(
ct);
1045 bool var_constraint_graph_changed =
false;
1048 var_constraint_graph_changed =
true;
1050 if (
ct->linear().vars().size() < num_vars) {
1052 var_constraint_graph_changed =
true;
1054 return var_constraint_graph_changed;
1057 bool CpModelPresolver::RemoveSingletonInLinear(ConstraintProto*
ct) {
1058 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1063 std::set<int> index_to_erase;
1064 const int num_vars =
ct->linear().vars().size();
1068 for (
int i = 0; i < num_vars; ++i) {
1069 const int var =
ct->linear().vars(i);
1070 const int64 coeff =
ct->linear().coeffs(i);
1074 const auto term_domain =
1076 if (!exact)
continue;
1080 if (new_rhs.NumIntervals() > 100)
continue;
1087 index_to_erase.insert(i);
1094 if (index_to_erase.empty()) {
1096 if (options_.
parameters.presolve_substitution_level() <= 0)
return false;
1097 if (!
ct->enforcement_literal().empty())
return false;
1101 if (rhs.Min() != rhs.Max())
return false;
1103 for (
int i = 0; i < num_vars; ++i) {
1104 const int var =
ct->linear().vars(i);
1105 const int64 coeff =
ct->linear().coeffs(i);
1123 const int64 objective_coeff =
1126 if (objective_coeff % coeff != 0)
continue;
1130 const auto term_domain =
1132 if (!exact)
continue;
1134 if (new_rhs.NumIntervals() > 100)
continue;
1142 objective_coeff))) {
1159 LOG(WARNING) <<
"This was not supposed to happen and the presolve "
1160 "could be improved.";
1163 context_->
UpdateRuleStats(
"linear: singleton column define objective.");
1167 return RemoveConstraint(
ct);
1173 "linear: singleton column in equality and in objective.");
1176 index_to_erase.insert(i);
1180 if (index_to_erase.empty())
return false;
1189 for (
int i = 0; i < num_vars; ++i) {
1190 if (index_to_erase.count(i)) {
1194 ct->mutable_linear()->set_coeffs(new_size,
ct->linear().coeffs(i));
1195 ct->mutable_linear()->set_vars(new_size,
ct->linear().vars(i));
1198 ct->mutable_linear()->mutable_vars()->Truncate(new_size);
1199 ct->mutable_linear()->mutable_coeffs()->Truncate(new_size);
1201 DivideLinearByGcd(
ct);
1205 bool CpModelPresolver::PresolveSmallLinear(ConstraintProto*
ct) {
1206 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1212 if (
ct->linear().vars().empty()) {
1215 if (rhs.Contains(0)) {
1216 return RemoveConstraint(
ct);
1218 return MarkConstraintAsFalse(
ct);
1225 if (
ct->linear().vars_size() == 1 &&
ct->enforcement_literal_size() > 0 &&
1226 ct->linear().coeffs(0) == 1 &&
1229 context_->
UpdateRuleStats(
"linear: remove abs from abs(x) in domain");
1230 const Domain implied_abs_target_domain =
1233 .IntersectionWith(context_->
DomainOf(
ct->linear().vars(0)));
1235 if (implied_abs_target_domain.IsEmpty()) {
1236 return MarkConstraintAsFalse(
ct);
1239 const Domain new_abs_var_domain =
1240 implied_abs_target_domain
1241 .UnionWith(implied_abs_target_domain.Negation())
1242 .IntersectionWith(context_->
DomainOf(abs_arg));
1244 if (new_abs_var_domain.IsEmpty()) {
1245 return MarkConstraintAsFalse(
ct);
1248 ConstraintProto* new_ct = context_->
working_model->add_constraints();
1249 new_ct->set_name(
ct->name());
1250 for (
const int literal :
ct->enforcement_literal()) {
1251 new_ct->add_enforcement_literal(
literal);
1253 auto* arg = new_ct->mutable_linear();
1254 arg->add_vars(abs_arg);
1258 return RemoveConstraint(
ct);
1262 if (
ct->enforcement_literal_size() != 1 ||
ct->linear().vars_size() != 1 ||
1263 (
ct->linear().coeffs(0) != 1 &&
ct->linear().coeffs(0) == -1)) {
1267 const int literal =
ct->enforcement_literal(0);
1268 const LinearConstraintProto& linear =
ct->linear();
1269 const int ref = linear.vars(0);
1274 if (linear.domain_size() == 2 && linear.domain(0) == linear.domain(1)) {
1276 : -linear.domain(0) * coeff;
1285 if (complement.Size() != 1)
return false;
1287 : -complement.Min() * coeff;
1302 if (
ct->linear().vars().size() == 1) {
1304 ?
ct->linear().coeffs(0)
1305 : -
ct->linear().coeffs(0);
1310 rhs.InverseMultiplicationBy(coeff))) {
1313 return RemoveConstraint(
ct);
1320 const LinearConstraintProto& arg =
ct->linear();
1321 if (arg.vars_size() == 2) {
1323 const int64 rhs_min = rhs.Min();
1324 const int64 rhs_max = rhs.Max();
1325 if (rhs_min == rhs_max) {
1326 const int v1 = arg.vars(0);
1327 const int v2 = arg.vars(1);
1328 const int64 coeff1 = arg.coeffs(0);
1329 const int64 coeff2 = arg.coeffs(1);
1333 }
else if (coeff2 == 1) {
1335 }
else if (coeff1 == -1) {
1337 }
else if (coeff2 == -1) {
1340 if (added)
return RemoveConstraint(
ct);
1350 bool IsLeConstraint(
const Domain& domain,
const Domain& all_values) {
1351 return all_values.IntersectionWith(Domain(
kint64min, domain.Max()))
1352 .IsIncludedIn(domain);
1356 bool IsGeConstraint(
const Domain& domain,
const Domain& all_values) {
1357 return all_values.IntersectionWith(Domain(domain.Min(),
kint64max))
1358 .IsIncludedIn(domain);
1364 bool RhsCanBeFixedToMin(
int64 coeff,
const Domain& var_domain,
1365 const Domain& terms,
const Domain& rhs) {
1366 if (var_domain.NumIntervals() != 1)
return false;
1367 if (std::abs(coeff) != 1)
return false;
1375 if (coeff == 1 && terms.Max() + var_domain.Min() <= rhs.Min()) {
1378 if (coeff == -1 && terms.Max() - var_domain.Max() <= rhs.Min()) {
1384 bool RhsCanBeFixedToMax(
int64 coeff,
const Domain& var_domain,
1385 const Domain& terms,
const Domain& rhs) {
1386 if (var_domain.NumIntervals() != 1)
return false;
1387 if (std::abs(coeff) != 1)
return false;
1389 if (coeff == 1 && terms.Min() + var_domain.Max() >= rhs.Max()) {
1392 if (coeff == -1 && terms.Min() - var_domain.Min() >= rhs.Max()) {
1399 void TakeIntersectionWith(
const absl::flat_hash_set<int>& current,
1400 absl::flat_hash_set<int>* to_clear) {
1401 std::vector<int> new_set;
1402 for (
const int c : *to_clear) {
1403 if (current.contains(c)) new_set.push_back(c);
1406 for (
const int c : new_set) to_clear->insert(c);
1411 bool CpModelPresolver::PropagateDomainsInLinear(
int c, ConstraintProto*
ct) {
1412 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1420 const int num_vars =
ct->linear().vars_size();
1421 term_domains.resize(num_vars + 1);
1422 left_domains.resize(num_vars + 1);
1423 left_domains[0] = Domain(0);
1424 for (
int i = 0; i < num_vars; ++i) {
1425 const int var =
ct->linear().vars(i);
1426 const int64 coeff =
ct->linear().coeffs(i);
1429 left_domains[i + 1] =
1432 const Domain& implied_rhs = left_domains[num_vars];
1436 if (implied_rhs.IsIncludedIn(old_rhs)) {
1438 return RemoveConstraint(
ct);
1442 Domain rhs = old_rhs.SimplifyUsingImpliedDomain(implied_rhs);
1443 if (rhs.IsEmpty()) {
1445 return MarkConstraintAsFalse(
ct);
1447 if (rhs != old_rhs) {
1455 bool is_le_constraint = IsLeConstraint(rhs, implied_rhs);
1456 bool is_ge_constraint = IsGeConstraint(rhs, implied_rhs);
1459 if (
ct->enforcement_literal().size() > 1)
return false;
1461 bool new_bounds =
false;
1462 bool recanonicalize =
false;
1463 Domain negated_rhs = rhs.Negation();
1464 Domain right_domain(0);
1466 Domain implied_term_domain;
1467 term_domains[num_vars] = Domain(0);
1468 for (
int i = num_vars - 1; i >= 0; --i) {
1469 const int var =
ct->linear().vars(i);
1470 const int64 var_coeff =
ct->linear().coeffs(i);
1472 right_domain.AdditionWith(term_domains[i + 1]).RelaxIfTooComplex();
1473 implied_term_domain = left_domains[i].AdditionWith(right_domain);
1474 new_domain = implied_term_domain.AdditionWith(negated_rhs)
1475 .InverseMultiplicationBy(-var_coeff);
1477 if (
ct->enforcement_literal().empty()) {
1482 }
else if (
ct->enforcement_literal().size() == 1) {
1493 recanonicalize =
true;
1497 if (is_le_constraint || is_ge_constraint) {
1498 CHECK_NE(is_le_constraint, is_ge_constraint);
1499 if ((var_coeff > 0) == is_ge_constraint) {
1514 const bool is_in_objective =
1518 const int64 obj_coeff =
1527 if (obj_coeff <= 0 &&
1537 recanonicalize =
true;
1541 if (obj_coeff >= 0 &&
1551 recanonicalize =
true;
1559 if (!
ct->enforcement_literal().empty())
continue;
1571 if (rhs.Min() != rhs.Max() &&
1575 if ((var_coeff > 0 == obj_coeff > 0) &&
1576 RhsCanBeFixedToMin(var_coeff, context_->
DomainOf(
var),
1577 implied_term_domain, rhs)) {
1578 rhs = Domain(rhs.Min());
1581 if ((var_coeff > 0 != obj_coeff > 0) &&
1582 RhsCanBeFixedToMax(var_coeff, context_->
DomainOf(
var),
1583 implied_term_domain, rhs)) {
1584 rhs = Domain(rhs.Max());
1590 negated_rhs = rhs.Negation();
1594 right_domain = Domain(0);
1598 is_le_constraint =
false;
1599 is_ge_constraint =
false;
1600 for (
const int var :
ct->linear().vars()) {
1617 if (
ct->linear().vars().size() <= 2)
continue;
1622 if (rhs.Min() != rhs.Max())
continue;
1628 if (context_->
DomainOf(
var) != new_domain)
continue;
1629 if (std::abs(var_coeff) != 1)
continue;
1630 if (options_.
parameters.presolve_substitution_level() <= 0)
continue;
1636 bool is_in_objective =
false;
1638 is_in_objective =
true;
1644 if (is_in_objective) col_size--;
1645 const int row_size =
ct->linear().vars_size();
1649 const int num_entries_added = (row_size - 1) * (col_size - 1);
1650 const int num_entries_removed = col_size + row_size - 1;
1652 if (num_entries_added > num_entries_removed) {
1658 std::vector<int> others;
1667 if (context_->
working_model->constraints(c).constraint_case() !=
1668 ConstraintProto::ConstraintCase::kLinear) {
1672 for (
const int ref :
1673 context_->
working_model->constraints(c).enforcement_literal()) {
1679 others.push_back(c);
1681 if (abort)
continue;
1684 for (
const int c : others) {
1697 if (is_in_objective) {
1702 absl::StrCat(
"linear: variable substitution ", others.size()));
1709 return RemoveConstraint(
ct);
1714 if (recanonicalize)
return CanonicalizeLinear(
ct);
1726 void CpModelPresolver::ExtractEnforcementLiteralFromLinearConstraint(
1727 ConstraintProto*
ct) {
1728 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1733 const LinearConstraintProto& arg =
ct->linear();
1734 const int num_vars = arg.vars_size();
1738 if (num_vars <= 1)
return;
1742 int64 max_coeff_magnitude = 0;
1743 for (
int i = 0; i < num_vars; ++i) {
1744 const int ref = arg.vars(i);
1745 const int64 coeff = arg.coeffs(i);
1746 const int64 term_a = coeff * context_->
MinOf(ref);
1747 const int64 term_b = coeff * context_->
MaxOf(ref);
1748 max_coeff_magnitude =
std::max(max_coeff_magnitude, std::abs(coeff));
1749 min_sum +=
std::min(term_a, term_b);
1750 max_sum +=
std::max(term_a, term_b);
1757 if (max_coeff_magnitude <
1758 std::max(max_sum - rhs_domain.Max(), rhs_domain.Min() - min_sum)) {
1779 const bool lower_bounded = min_sum < rhs_domain.Min();
1780 const bool upper_bounded = max_sum > rhs_domain.Max();
1781 if (!lower_bounded && !upper_bounded)
return;
1782 if (lower_bounded && upper_bounded) {
1784 ConstraintProto* new_ct1 = context_->
working_model->add_constraints();
1786 if (!
ct->name().empty()) {
1787 new_ct1->set_name(absl::StrCat(
ct->name(),
" (part 1)"));
1790 new_ct1->mutable_linear());
1792 ConstraintProto* new_ct2 = context_->
working_model->add_constraints();
1794 if (!
ct->name().empty()) {
1795 new_ct2->set_name(absl::StrCat(
ct->name(),
" (part 2)"));
1798 new_ct2->mutable_linear());
1801 return (
void)RemoveConstraint(
ct);
1807 LinearConstraintProto* mutable_arg =
ct->mutable_linear();
1808 for (
int i = 0; i < arg.vars_size(); ++i) {
1810 const int ref = arg.vars(i);
1811 if (context_->
MinOf(ref) == 0 && context_->
MaxOf(ref) == 1) {
1812 const int64 coeff = arg.coeffs(i);
1813 if (!lower_bounded) {
1814 if (max_sum - std::abs(coeff) <= rhs_domain.front().end) {
1818 rhs_domain = rhs_domain.AdditionWith(Domain(-coeff));
1819 ct->add_enforcement_literal(ref);
1830 "linear: extracted enforcement literal from constraint");
1834 DCHECK(!upper_bounded);
1835 if (min_sum + std::abs(coeff) >= rhs_domain.back().start) {
1845 rhs_domain = rhs_domain.AdditionWith(Domain(-coeff));
1846 ct->add_enforcement_literal(ref);
1851 "linear: extracted enforcement literal from constraint");
1858 mutable_arg->set_vars(new_size, mutable_arg->vars(i));
1859 mutable_arg->set_coeffs(new_size, mutable_arg->coeffs(i));
1863 mutable_arg->mutable_vars()->Truncate(new_size);
1864 mutable_arg->mutable_coeffs()->Truncate(new_size);
1868 void CpModelPresolver::ExtractAtMostOneFromLinear(ConstraintProto*
ct) {
1873 const LinearConstraintProto& arg =
ct->linear();
1874 const int num_vars = arg.vars_size();
1877 for (
int i = 0; i < num_vars; ++i) {
1878 const int ref = arg.vars(i);
1879 const int64 coeff = arg.coeffs(i);
1880 const int64 term_a = coeff * context_->
MinOf(ref);
1881 const int64 term_b = coeff * context_->
MaxOf(ref);
1882 min_sum +=
std::min(term_a, term_b);
1883 max_sum +=
std::max(term_a, term_b);
1885 for (
const int type : {0, 1}) {
1886 std::vector<int> at_most_one;
1887 for (
int i = 0; i < num_vars; ++i) {
1888 const int ref = arg.vars(i);
1889 const int64 coeff = arg.coeffs(i);
1890 if (context_->
MinOf(ref) != 0)
continue;
1891 if (context_->
MaxOf(ref) != 1)
continue;
1896 if (min_sum + 2 * std::abs(coeff) > rhs.Max()) {
1897 at_most_one.push_back(coeff > 0 ? ref :
NegatedRef(ref));
1900 if (max_sum - 2 * std::abs(coeff) < rhs.Min()) {
1901 at_most_one.push_back(coeff > 0 ?
NegatedRef(ref) : ref);
1905 if (at_most_one.size() > 1) {
1911 ConstraintProto* new_ct = context_->
working_model->add_constraints();
1912 new_ct->set_name(
ct->name());
1913 for (
const int ref : at_most_one) {
1914 new_ct->mutable_at_most_one()->add_literals(ref);
1923 bool CpModelPresolver::PresolveLinearOnBooleans(ConstraintProto*
ct) {
1924 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1929 const LinearConstraintProto& arg =
ct->linear();
1930 const int num_vars = arg.vars_size();
1932 int64 max_coeff = 0;
1935 for (
int i = 0; i < num_vars; ++i) {
1937 const int var = arg.vars(i);
1938 const int64 coeff = arg.coeffs(i);
1941 if (context_->
MinOf(
var) != 0)
return false;
1942 if (context_->
MaxOf(
var) != 1)
return false;
1946 min_coeff =
std::min(min_coeff, coeff);
1947 max_coeff =
std::max(max_coeff, coeff);
1951 min_coeff =
std::min(min_coeff, -coeff);
1952 max_coeff =
std::max(max_coeff, -coeff);
1955 CHECK_LE(min_coeff, max_coeff);
1964 if ((!rhs_domain.Contains(min_sum) &&
1965 min_sum + min_coeff > rhs_domain.Max()) ||
1966 (!rhs_domain.Contains(max_sum) &&
1967 max_sum - min_coeff < rhs_domain.Min())) {
1968 context_->
UpdateRuleStats(
"linear: all booleans and trivially false");
1969 return MarkConstraintAsFalse(
ct);
1971 if (Domain(min_sum, max_sum).IsIncludedIn(rhs_domain)) {
1973 return RemoveConstraint(
ct);
1980 DCHECK(!rhs_domain.IsEmpty());
1981 if (min_sum + min_coeff > rhs_domain.Max()) {
1984 const auto copy = arg;
1985 ct->mutable_bool_and()->clear_literals();
1986 for (
int i = 0; i < num_vars; ++i) {
1987 ct->mutable_bool_and()->add_literals(
1988 copy.coeffs(i) > 0 ?
NegatedRef(copy.vars(i)) : copy.vars(i));
1990 return PresolveBoolAnd(
ct);
1991 }
else if (max_sum - min_coeff < rhs_domain.Min()) {
1994 const auto copy = arg;
1995 ct->mutable_bool_and()->clear_literals();
1996 for (
int i = 0; i < num_vars; ++i) {
1997 ct->mutable_bool_and()->add_literals(
1998 copy.coeffs(i) > 0 ? copy.vars(i) :
NegatedRef(copy.vars(i)));
2000 return PresolveBoolAnd(
ct);
2001 }
else if (min_sum + min_coeff >= rhs_domain.Min() &&
2002 rhs_domain.front().end >= max_sum) {
2005 const auto copy = arg;
2006 ct->mutable_bool_or()->clear_literals();
2007 for (
int i = 0; i < num_vars; ++i) {
2008 ct->mutable_bool_or()->add_literals(
2009 copy.coeffs(i) > 0 ? copy.vars(i) :
NegatedRef(copy.vars(i)));
2011 return PresolveBoolOr(
ct);
2012 }
else if (max_sum - min_coeff <= rhs_domain.Max() &&
2013 rhs_domain.back().start <= min_sum) {
2016 const auto copy = arg;
2017 ct->mutable_bool_or()->clear_literals();
2018 for (
int i = 0; i < num_vars; ++i) {
2019 ct->mutable_bool_or()->add_literals(
2020 copy.coeffs(i) > 0 ?
NegatedRef(copy.vars(i)) : copy.vars(i));
2022 return PresolveBoolOr(
ct);
2024 min_sum + max_coeff <= rhs_domain.Max() &&
2025 min_sum + 2 * min_coeff > rhs_domain.Max() &&
2026 rhs_domain.back().start <= min_sum) {
2029 const auto copy = arg;
2030 ct->mutable_at_most_one()->clear_literals();
2031 for (
int i = 0; i < num_vars; ++i) {
2032 ct->mutable_at_most_one()->add_literals(
2033 copy.coeffs(i) > 0 ? copy.vars(i) :
NegatedRef(copy.vars(i)));
2037 max_sum - max_coeff >= rhs_domain.Min() &&
2038 max_sum - 2 * min_coeff < rhs_domain.Min() &&
2039 rhs_domain.front().end >= max_sum) {
2042 const auto copy = arg;
2043 ct->mutable_at_most_one()->clear_literals();
2044 for (
int i = 0; i < num_vars; ++i) {
2045 ct->mutable_at_most_one()->add_literals(
2046 copy.coeffs(i) > 0 ?
NegatedRef(copy.vars(i)) : copy.vars(i));
2050 min_sum < rhs_domain.Min() &&
2051 min_sum + min_coeff >= rhs_domain.Min() &&
2052 min_sum + 2 * min_coeff > rhs_domain.Max() &&
2053 min_sum + max_coeff <= rhs_domain.Max()) {
2055 ConstraintProto* at_least_one = context_->
working_model->add_constraints();
2056 ConstraintProto* at_most_one = context_->
working_model->add_constraints();
2057 at_least_one->set_name(
ct->name());
2058 at_most_one->set_name(
ct->name());
2059 for (
int i = 0; i < num_vars; ++i) {
2060 at_least_one->mutable_bool_or()->add_literals(
2061 arg.coeffs(i) > 0 ? arg.vars(i) :
NegatedRef(arg.vars(i)));
2062 at_most_one->mutable_at_most_one()->add_literals(
2063 arg.coeffs(i) > 0 ? arg.vars(i) :
NegatedRef(arg.vars(i)));
2066 return RemoveConstraint(
ct);
2068 max_sum > rhs_domain.Max() &&
2069 max_sum - min_coeff <= rhs_domain.Max() &&
2070 max_sum - 2 * min_coeff < rhs_domain.Min() &&
2071 max_sum - max_coeff >= rhs_domain.Min()) {
2073 ConstraintProto* at_least_one = context_->
working_model->add_constraints();
2074 ConstraintProto* at_most_one = context_->
working_model->add_constraints();
2075 at_least_one->set_name(
ct->name());
2076 at_most_one->set_name(
ct->name());
2077 for (
int i = 0; i < num_vars; ++i) {
2078 at_least_one->mutable_bool_or()->add_literals(
2079 arg.coeffs(i) > 0 ?
NegatedRef(arg.vars(i)) : arg.vars(i));
2080 at_most_one->mutable_at_most_one()->add_literals(
2081 arg.coeffs(i) > 0 ?
NegatedRef(arg.vars(i)) : arg.vars(i));
2084 return RemoveConstraint(
ct);
2091 if (num_vars > 3)
return false;
2096 const int max_mask = (1 << arg.vars_size());
2097 for (
int mask = 0; mask < max_mask; ++mask) {
2099 for (
int i = 0; i < num_vars; ++i) {
2100 if ((mask >> i) & 1)
value += arg.coeffs(i);
2102 if (rhs_domain.Contains(
value))
continue;
2105 ConstraintProto* new_ct = context_->
working_model->add_constraints();
2106 auto* new_arg = new_ct->mutable_bool_or();
2108 *new_ct->mutable_enforcement_literal() =
ct->enforcement_literal();
2110 for (
int i = 0; i < num_vars; ++i) {
2111 new_arg->add_literals(((mask >> i) & 1) ?
NegatedRef(arg.vars(i))
2117 return RemoveConstraint(
ct);
2120 bool CpModelPresolver::PresolveInterval(
int c, ConstraintProto*
ct) {
2123 const int start =
ct->interval().start();
2124 const int end =
ct->interval().end();
2125 const int size =
ct->interval().size();
2127 if (
ct->enforcement_literal().empty()) {
2128 bool changed =
false;
2129 const Domain start_domain = context_->
DomainOf(start);
2130 const Domain end_domain = context_->
DomainOf(end);
2131 const Domain size_domain = context_->
DomainOf(size);
2138 end, start_domain.AdditionWith(size_domain), &changed)) {
2142 start, end_domain.AdditionWith(size_domain.Negation()), &changed)) {
2146 size, end_domain.AdditionWith(start_domain.Negation()), &changed)) {
2156 ConstraintProto* new_ct = context_->
working_model->add_constraints();
2157 *(new_ct->mutable_enforcement_literal()) =
ct->enforcement_literal();
2158 new_ct->mutable_linear()->add_domain(0);
2159 new_ct->mutable_linear()->add_domain(0);
2160 new_ct->mutable_linear()->add_vars(start);
2161 new_ct->mutable_linear()->add_coeffs(1);
2162 new_ct->mutable_linear()->add_vars(size);
2163 new_ct->mutable_linear()->add_coeffs(1);
2164 new_ct->mutable_linear()->add_vars(end);
2165 new_ct->mutable_linear()->add_coeffs(-1);
2169 return RemoveConstraint(
ct);
2177 if ( (
false) &&
ct->enforcement_literal().empty() &&
2180 1, context_->
MinOf(size));
2187 bool CpModelPresolver::PresolveElement(ConstraintProto*
ct) {
2190 const int index_ref =
ct->element().index();
2191 const int target_ref =
ct->element().target();
2197 bool all_constants =
true;
2198 absl::flat_hash_set<int64> constant_set;
2199 bool all_included_in_target_domain =
true;
2202 bool reduced_index_domain =
false;
2204 Domain(0,
ct->element().vars_size() - 1),
2205 &reduced_index_domain)) {
2211 Domain infered_domain;
2212 const Domain& initial_index_domain = context_->
DomainOf(index_ref);
2213 const Domain& target_domain = context_->
DomainOf(target_ref);
2214 for (
const ClosedInterval
interval : initial_index_domain) {
2217 CHECK_LT(
value,
ct->element().vars_size());
2218 const int ref =
ct->element().vars(
value);
2219 const Domain& domain = context_->
DomainOf(ref);
2220 if (domain.IntersectionWith(target_domain).IsEmpty()) {
2221 bool domain_modified =
false;
2223 index_ref, Domain(
value).Complement(), &domain_modified)) {
2226 reduced_index_domain =
true;
2229 if (domain.IsFixed()) {
2230 constant_set.insert(domain.Min());
2232 all_constants =
false;
2234 if (!domain.IsIncludedIn(target_domain)) {
2235 all_included_in_target_domain =
false;
2237 infered_domain = infered_domain.
UnionWith(domain);
2241 if (reduced_index_domain) {
2244 bool domain_modified =
false;
2246 &domain_modified)) {
2249 if (domain_modified) {
2255 if (context_->
IsFixed(index_ref)) {
2256 const int var =
ct->element().vars(context_->
MinOf(index_ref));
2257 if (
var != target_ref) {
2258 LinearConstraintProto*
const lin =
2259 context_->
working_model->add_constraints()->mutable_linear();
2261 lin->add_coeffs(-1);
2262 lin->add_vars(target_ref);
2269 return RemoveConstraint(
ct);
2275 if (all_constants && constant_set.size() == 1) {
2276 CHECK(context_->
IsFixed(target_ref));
2278 return RemoveConstraint(
ct);
2283 if (context_->
MinOf(index_ref) == 0 && context_->
MaxOf(index_ref) == 1 &&
2285 const int64 v0 = context_->
MinOf(
ct->element().vars(0));
2286 const int64 v1 = context_->
MinOf(
ct->element().vars(1));
2288 LinearConstraintProto*
const lin =
2289 context_->
working_model->add_constraints()->mutable_linear();
2290 lin->add_vars(target_ref);
2292 lin->add_vars(index_ref);
2293 lin->add_coeffs(v0 - v1);
2294 lin->add_domain(v0);
2295 lin->add_domain(v0);
2297 context_->
UpdateRuleStats(
"element: linearize constant element of size 2");
2298 return RemoveConstraint(
ct);
2302 const AffineRelation::Relation r_index =
2304 if (r_index.representative != index_ref) {
2306 if (context_->
DomainOf(r_index.representative).
Size() >
2314 const int array_size =
ct->element().vars_size();
2316 context_->
UpdateRuleStats(
"TODO element: representative has bad domain");
2317 }
else if (r_index.offset >= 0 && r_index.offset < array_size &&
2318 r_index.offset + r_max * r_index.coeff >= 0 &&
2319 r_index.offset + r_max * r_index.coeff < array_size) {
2321 ElementConstraintProto*
const element =
2322 context_->
working_model->add_constraints()->mutable_element();
2323 for (
int64 v = 0; v <= r_max; ++v) {
2324 const int64 scaled_index = v * r_index.coeff + r_index.offset;
2325 CHECK_GE(scaled_index, 0);
2326 CHECK_LT(scaled_index, array_size);
2327 element->add_vars(
ct->element().vars(scaled_index));
2329 element->set_index(r_ref);
2330 element->set_target(target_ref);
2332 if (r_index.coeff == 1) {
2338 return RemoveConstraint(
ct);
2344 if (all_constants && unique_index) {
2348 context_->
UpdateRuleStats(
"element: trivial target domain reduction");
2351 return RemoveConstraint(
ct);
2354 const bool unique_target =
2356 context_->
IsFixed(target_ref);
2357 if (all_included_in_target_domain && unique_target) {
2361 return RemoveConstraint(
ct);
2364 if (target_ref == index_ref) {
2366 std::vector<int64> possible_indices;
2367 const Domain& index_domain = context_->
DomainOf(index_ref);
2368 for (
const ClosedInterval&
interval : index_domain) {
2370 const int ref =
ct->element().vars(
value);
2372 possible_indices.push_back(
value);
2376 if (possible_indices.size() < index_domain.Size()) {
2383 "element: reduce index domain when target equals index");
2388 if (unique_target && !context_->
IsFixed(target_ref)) {
2398 bool CpModelPresolver::PresolveTable(ConstraintProto*
ct) {
2401 if (
ct->table().vars().empty()) {
2403 return RemoveConstraint(
ct);
2409 const int num_vars =
ct->table().vars_size();
2410 const int num_tuples =
ct->table().values_size() / num_vars;
2411 std::vector<int64> tuple(num_vars);
2412 std::vector<std::vector<int64>> new_tuples;
2413 new_tuples.reserve(num_tuples);
2414 std::vector<absl::flat_hash_set<int64>> new_domains(num_vars);
2415 std::vector<AffineRelation::Relation> affine_relations;
2417 absl::flat_hash_set<int> visited;
2418 for (
const int ref :
ct->table().vars()) {
2426 bool modified_variables =
false;
2427 for (
int v = 0; v < num_vars; ++v) {
2428 const int ref =
ct->table().vars(v);
2430 affine_relations.push_back(r);
2431 if (r.representative != ref) {
2432 modified_variables =
true;
2436 for (
int i = 0; i < num_tuples; ++i) {
2437 bool delete_row =
false;
2439 for (
int j = 0; j < num_vars; ++j) {
2440 const int ref =
ct->table().vars(j);
2441 int64 v =
ct->table().values(i * num_vars + j);
2442 const AffineRelation::Relation& r = affine_relations[j];
2443 if (r.representative != ref) {
2444 const int64 inverse_value = (v - r.offset) / r.coeff;
2445 if (inverse_value * r.coeff + r.offset != v) {
2458 if (delete_row)
continue;
2459 new_tuples.push_back(tuple);
2460 for (
int j = 0; j < num_vars; ++j) {
2461 const int64 v = tuple[j];
2462 new_domains[j].insert(v);
2468 if (new_tuples.size() < num_tuples || modified_variables) {
2469 ct->mutable_table()->clear_values();
2470 for (
const std::vector<int64>& t : new_tuples) {
2471 for (
const int64 v : t) {
2472 ct->mutable_table()->add_values(v);
2475 if (new_tuples.size() < num_tuples) {
2480 if (modified_variables) {
2481 for (
int j = 0; j < num_vars; ++j) {
2482 const AffineRelation::Relation& r = affine_relations[j];
2483 if (r.representative !=
ct->table().vars(j)) {
2484 ct->mutable_table()->set_vars(j, r.representative);
2488 "table: replace variable by canonical affine one");
2492 if (
ct->table().negated())
return modified_variables;
2495 bool changed =
false;
2496 for (
int j = 0; j < num_vars; ++j) {
2497 const int ref =
ct->table().vars(j);
2501 new_domains[j].end())),
2509 if (num_vars == 1) {
2512 return RemoveConstraint(
ct);
2517 for (
int j = 0; j < num_vars; ++j) prod *= new_domains[j].size();
2518 if (prod == new_tuples.size()) {
2520 return RemoveConstraint(
ct);
2526 if (new_tuples.size() > 0.7 * prod) {
2528 std::vector<std::vector<int64>> var_to_values(num_vars);
2529 for (
int j = 0; j < num_vars; ++j) {
2530 var_to_values[j].assign(new_domains[j].begin(), new_domains[j].end());
2532 std::vector<std::vector<int64>> all_tuples(prod);
2533 for (
int i = 0; i < prod; ++i) {
2534 all_tuples[i].resize(num_vars);
2536 for (
int j = 0; j < num_vars; ++j) {
2537 all_tuples[i][j] = var_to_values[j][
index % var_to_values[j].size()];
2538 index /= var_to_values[j].size();
2544 std::vector<std::vector<int64>> diff(prod - new_tuples.size());
2545 std::set_difference(all_tuples.begin(), all_tuples.end(),
2546 new_tuples.begin(), new_tuples.end(), diff.begin());
2549 ct->mutable_table()->set_negated(!
ct->table().negated());
2550 ct->mutable_table()->clear_values();
2551 for (
const std::vector<int64>& t : diff) {
2552 for (
const int64 v : t)
ct->mutable_table()->add_values(v);
2556 return modified_variables;
2559 bool CpModelPresolver::PresolveAllDiff(ConstraintProto*
ct) {
2563 AllDifferentConstraintProto& all_diff = *
ct->mutable_all_diff();
2565 bool constraint_has_changed =
false;
2567 const int size = all_diff.vars_size();
2570 return RemoveConstraint(
ct);
2574 return RemoveConstraint(
ct);
2577 bool something_was_propagated =
false;
2578 std::vector<int> new_variables;
2579 for (
int i = 0; i < size; ++i) {
2580 if (!context_->
IsFixed(all_diff.vars(i))) {
2581 new_variables.push_back(all_diff.vars(i));
2586 bool propagated =
false;
2587 for (
int j = 0; j < size; ++j) {
2588 if (i == j)
continue;
2591 Domain(
value).Complement())) {
2599 something_was_propagated =
true;
2603 std::sort(new_variables.begin(), new_variables.end());
2604 for (
int i = 1; i < new_variables.size(); ++i) {
2605 if (new_variables[i] == new_variables[i - 1]) {
2607 "Duplicate variable in all_diff");
2611 if (new_variables.size() < all_diff.vars_size()) {
2612 all_diff.mutable_vars()->Clear();
2613 for (
const int var : new_variables) {
2614 all_diff.add_vars(
var);
2617 something_was_propagated =
true;
2618 constraint_has_changed =
true;
2619 if (new_variables.size() <= 1)
continue;
2623 CHECK_GE(all_diff.vars_size(), 2);
2624 Domain domain = context_->
DomainOf(all_diff.vars(0));
2625 for (
int i = 1; i < all_diff.vars_size(); ++i) {
2628 if (all_diff.vars_size() == domain.Size()) {
2629 absl::flat_hash_map<int64, std::vector<int>> value_to_refs;
2630 for (
const int ref : all_diff.vars()) {
2633 value_to_refs[v].push_back(ref);
2637 bool propagated =
false;
2638 for (
const auto& it : value_to_refs) {
2639 if (it.second.size() == 1 &&
2641 const int ref = it.second.
front();
2650 "all_diff: propagated mandatory values in permutation");
2651 something_was_propagated =
true;
2654 if (!something_was_propagated)
break;
2657 return constraint_has_changed;
2664 std::vector<int> GetLiteralsFromSetPPCConstraint(ConstraintProto*
ct) {
2665 std::vector<int> sorted_literals;
2666 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne) {
2667 for (
const int literal :
ct->at_most_one().literals()) {
2668 sorted_literals.push_back(
literal);
2670 }
else if (
ct->constraint_case() ==
2671 ConstraintProto::ConstraintCase::kBoolOr) {
2672 for (
const int literal :
ct->bool_or().literals()) {
2673 sorted_literals.push_back(
literal);
2676 std::sort(sorted_literals.begin(), sorted_literals.end());
2677 return sorted_literals;
2682 void AddImplication(
int lhs,
int rhs, CpModelProto*
proto,
2683 absl::flat_hash_map<int, int>* ref_to_bool_and) {
2684 if (ref_to_bool_and->contains(lhs)) {
2685 const int ct_index = (*ref_to_bool_and)[lhs];
2686 proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(rhs);
2687 }
else if (ref_to_bool_and->contains(
NegatedRef(rhs))) {
2688 const int ct_index = (*ref_to_bool_and)[
NegatedRef(rhs)];
2689 proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(
2692 (*ref_to_bool_and)[lhs] =
proto->constraints_size();
2693 ConstraintProto*
ct =
proto->add_constraints();
2694 ct->add_enforcement_literal(lhs);
2695 ct->mutable_bool_and()->add_literals(rhs);
2699 template <
typename ClauseContainer>
2700 void ExtractClauses(
bool use_bool_and,
const ClauseContainer& container,
2701 CpModelProto*
proto) {
2708 absl::flat_hash_map<int, int> ref_to_bool_and;
2709 for (
int i = 0; i < container.NumClauses(); ++i) {
2710 const std::vector<Literal>& clause = container.Clause(i);
2711 if (clause.empty())
continue;
2714 if (use_bool_and && clause.size() == 2) {
2715 const int a = clause[0].IsPositive()
2716 ? clause[0].Variable().value()
2718 const int b = clause[1].IsPositive()
2719 ? clause[1].Variable().value()
2726 ConstraintProto*
ct =
proto->add_constraints();
2727 for (
const Literal l : clause) {
2728 if (l.IsPositive()) {
2729 ct->mutable_bool_or()->add_literals(l.Variable().value());
2731 ct->mutable_bool_or()->add_literals(
NegatedRef(l.Variable().value()));
2739 bool CpModelPresolver::PresolveNoOverlap(ConstraintProto*
ct) {
2742 const NoOverlapConstraintProto&
proto =
ct->no_overlap();
2746 for (
int i = 0; i <
proto.intervals_size(); ++i) {
2747 const int interval_index =
proto.intervals(i);
2749 .constraint_case() ==
2750 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
2753 ct->mutable_no_overlap()->set_intervals(new_size++, interval_index);
2755 ct->mutable_no_overlap()->mutable_intervals()->Truncate(new_size);
2759 ct->mutable_no_overlap()->mutable_intervals()->begin(),
2760 ct->mutable_no_overlap()->mutable_intervals()->end(),
2761 [
this](
int i1,
int i2) {
2762 return context_->MinOf(context_->working_model->constraints(i1)
2766 context_->working_model->constraints(i2).interval().start());
2775 for (
int i = 0; i <
proto.intervals_size(); ++i) {
2776 const int interval_index =
proto.intervals(i);
2777 const IntervalConstraintProto&
interval =
2778 context_->
working_model->constraints(interval_index).interval();
2779 const int64 end_max_of_previous_intervals = end_max_so_far;
2781 if (context_->
MinOf(
interval.start()) >= end_max_of_previous_intervals &&
2782 (i + 1 ==
proto.intervals_size() ||
2791 ct->mutable_no_overlap()->set_intervals(new_size++, interval_index);
2793 ct->mutable_no_overlap()->mutable_intervals()->Truncate(new_size);
2795 if (
proto.intervals_size() == 1) {
2797 return RemoveConstraint(
ct);
2799 if (
proto.intervals().empty()) {
2801 return RemoveConstraint(
ct);
2806 bool CpModelPresolver::PresolveCumulative(ConstraintProto*
ct) {
2809 const CumulativeConstraintProto&
proto =
ct->cumulative();
2813 bool changed =
false;
2814 int num_zero_demand_removed = 0;
2815 for (
int i = 0; i <
proto.intervals_size(); ++i) {
2817 .constraint_case() ==
2818 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
2822 const int demand_ref =
proto.demands(i);
2823 const int64 demand_max = context_->
MaxOf(demand_ref);
2824 if (demand_max == 0) {
2825 num_zero_demand_removed++;
2829 ct->mutable_cumulative()->set_intervals(new_size,
proto.intervals(i));
2830 ct->mutable_cumulative()->set_demands(new_size,
proto.demands(i));
2833 if (new_size <
proto.intervals_size()) {
2835 ct->mutable_cumulative()->mutable_intervals()->Truncate(new_size);
2836 ct->mutable_cumulative()->mutable_demands()->Truncate(new_size);
2839 if (num_zero_demand_removed > 0) {
2840 context_->
UpdateRuleStats(
"cumulative: removed intervals with no demands");
2843 if (new_size == 0) {
2845 return RemoveConstraint(
ct);
2849 if (!context_->
IsFixed(
proto.capacity()))
return changed;
2852 const int size =
proto.intervals_size();
2853 std::vector<int> start_indices(size, -1);
2855 int num_duration_one = 0;
2856 int num_greater_half_capacity = 0;
2858 bool has_optional_interval =
false;
2859 for (
int i = 0; i < size; ++i) {
2861 const ConstraintProto&
ct =
2863 if (!
ct.enforcement_literal().empty()) has_optional_interval =
true;
2864 const IntervalConstraintProto&
interval =
ct.interval();
2865 start_indices[i] =
interval.start();
2866 const int duration_ref =
interval.size();
2867 const int demand_ref =
proto.demands(i);
2868 if (context_->
IsFixed(duration_ref) && context_->
MinOf(duration_ref) == 1) {
2871 if (context_->
MinOf(duration_ref) == 0) {
2876 const int64 demand_min = context_->
MinOf(demand_ref);
2877 const int64 demand_max = context_->
MaxOf(demand_ref);
2879 num_greater_half_capacity++;
2883 if (
ct.enforcement_literal().empty()) {
2886 CHECK_EQ(
ct.enforcement_literal().size(), 1);
2892 }
else if (demand_max >
capacity) {
2893 if (
ct.enforcement_literal().empty()) {
2894 context_->
UpdateRuleStats(
"cumulative: demand_max exceeds capacity.");
2903 "cumulative: demand_max of optional interval exceeds capacity.");
2909 if (num_greater_half_capacity == size) {
2910 if (num_duration_one == size && !has_optional_interval) {
2912 ConstraintProto* new_ct = context_->
working_model->add_constraints();
2913 auto* arg = new_ct->mutable_all_diff();
2914 for (
const int var : start_indices) {
2918 return RemoveConstraint(
ct);
2921 ConstraintProto* new_ct = context_->
working_model->add_constraints();
2922 auto* arg = new_ct->mutable_no_overlap();
2927 return RemoveConstraint(
ct);
2934 bool CpModelPresolver::PresolveRoutes(ConstraintProto*
ct) {
2937 RoutesConstraintProto&
proto = *
ct->mutable_routes();
2940 const int num_arcs =
proto.literals_size();
2941 for (
int i = 0; i < num_arcs; ++i) {
2942 const int ref =
proto.literals(i);
2949 proto.set_literals(new_size, ref);
2954 if (new_size < num_arcs) {
2955 proto.mutable_literals()->Truncate(new_size);
2956 proto.mutable_tails()->Truncate(new_size);
2957 proto.mutable_heads()->Truncate(new_size);
2963 bool CpModelPresolver::PresolveCircuit(ConstraintProto*
ct) {
2966 CircuitConstraintProto&
proto = *
ct->mutable_circuit();
2970 std::vector<std::vector<int>> incoming_arcs;
2971 std::vector<std::vector<int>> outgoing_arcs;
2973 const int num_arcs =
proto.literals_size();
2974 for (
int i = 0; i < num_arcs; ++i) {
2975 const int ref =
proto.literals(i);
2983 incoming_arcs[
head].push_back(ref);
2984 outgoing_arcs[
tail].push_back(ref);
2992 bool loop_again =
true;
2993 int num_fixed_at_true = 0;
2994 while (loop_again) {
2996 for (
const auto* node_to_refs : {&incoming_arcs, &outgoing_arcs}) {
2997 for (
const std::vector<int>& refs : *node_to_refs) {
2998 if (refs.size() == 1) {
3000 ++num_fixed_at_true;
3009 for (
const int ref : refs) {
3019 if (num_true == 1) {
3020 for (
const int ref : refs) {
3021 if (ref != true_ref) {
3022 if (!context_->
IsFixed(ref)) {
3033 if (num_fixed_at_true > 0) {
3040 int circuit_start = -1;
3041 std::vector<int>
next(num_nodes, -1);
3042 std::vector<int> new_in_degree(num_nodes, 0);
3043 std::vector<int> new_out_degree(num_nodes, 0);
3044 for (
int i = 0; i < num_arcs; ++i) {
3045 const int ref =
proto.literals(i);
3053 circuit_start =
proto.tails(i);
3057 ++new_out_degree[
proto.tails(i)];
3058 ++new_in_degree[
proto.heads(i)];
3061 proto.set_literals(new_size,
proto.literals(i));
3071 for (
int i = 0; i < num_nodes; ++i) {
3073 if (incoming_arcs[i].empty() && outgoing_arcs[i].empty())
continue;
3075 if (new_in_degree[i] == 0 || new_out_degree[i] == 0) {
3081 if (circuit_start != -1) {
3082 std::vector<bool> visited(num_nodes,
false);
3083 int current = circuit_start;
3084 while (current != -1 && !visited[current]) {
3085 visited[current] =
true;
3086 current =
next[current];
3088 if (current == circuit_start) {
3091 for (
int i = 0; i < num_arcs; ++i) {
3092 if (visited[
proto.tails(i)])
continue;
3100 return RemoveConstraint(
ct);
3104 if (num_true == new_size) {
3106 return RemoveConstraint(
ct);
3112 for (
int i = 0; i < num_nodes; ++i) {
3113 for (
const std::vector<int>* arc_literals :
3114 {&incoming_arcs[i], &outgoing_arcs[i]}) {
3115 std::vector<int> literals;
3116 for (
const int ref : *arc_literals) {
3122 literals.push_back(ref);
3124 if (literals.size() == 2 && literals[0] !=
NegatedRef(literals[1])) {
3133 if (new_size < num_arcs) {
3134 proto.mutable_tails()->Truncate(new_size);
3135 proto.mutable_heads()->Truncate(new_size);
3136 proto.mutable_literals()->Truncate(new_size);
3143 bool CpModelPresolver::PresolveAutomaton(ConstraintProto*
ct) {
3146 AutomatonConstraintProto&
proto = *
ct->mutable_automaton();
3147 if (
proto.vars_size() == 0 ||
proto.transition_label_size() == 0) {
3151 bool all_affine =
true;
3152 std::vector<AffineRelation::Relation> affine_relations;
3153 for (
int v = 0; v <
proto.vars_size(); ++v) {
3154 const int var =
ct->automaton().vars(v);
3156 affine_relations.push_back(r);
3157 if (r.representative ==
var) {
3161 if (v > 0 && (r.coeff != affine_relations[v - 1].coeff ||
3162 r.offset != affine_relations[v - 1].offset)) {
3169 for (
int v = 0; v <
proto.vars_size(); ++v) {
3172 const AffineRelation::Relation rep = affine_relations.front();
3174 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
3175 const int64 label =
proto.transition_label(t);
3176 int64 inverse_label = (label - rep.offset) / rep.coeff;
3177 if (inverse_label * rep.coeff + rep.offset == label) {
3178 if (new_size != t) {
3179 proto.set_transition_tail(new_size,
proto.transition_tail(t));
3180 proto.set_transition_head(new_size,
proto.transition_head(t));
3182 proto.set_transition_label(new_size, inverse_label);
3186 if (new_size <
proto.transition_tail_size()) {
3187 proto.mutable_transition_tail()->Truncate(new_size);
3188 proto.mutable_transition_label()->Truncate(new_size);
3189 proto.mutable_transition_head()->Truncate(new_size);
3197 for (
int v = 1; v <
proto.vars_size(); ++v) {
3202 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
3203 const int64 label =
proto.transition_label(t);
3204 if (hull.Contains(label)) {
3205 if (new_size != t) {
3206 proto.set_transition_tail(new_size,
proto.transition_tail(t));
3207 proto.set_transition_label(new_size, label);
3208 proto.set_transition_head(new_size,
proto.transition_head(t));
3213 if (new_size <
proto.transition_tail_size()) {
3214 proto.mutable_transition_tail()->Truncate(new_size);
3215 proto.mutable_transition_label()->Truncate(new_size);
3216 proto.mutable_transition_head()->Truncate(new_size);
3221 const int n =
proto.vars_size();
3222 const std::vector<int> vars = {
proto.vars().begin(),
proto.vars().end()};
3225 std::vector<std::set<int64>> reachable_states(n + 1);
3226 reachable_states[0].insert(
proto.starting_state());
3227 reachable_states[n] = {
proto.final_states().begin(),
3228 proto.final_states().end()};
3235 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
3237 const int64 label =
proto.transition_label(t);
3241 reachable_states[
time + 1].insert(
head);
3245 std::vector<std::set<int64>> reached_values(n);
3249 std::set<int64> new_set;
3250 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
3252 const int64 label =
proto.transition_label(t);
3258 new_set.insert(
tail);
3259 reached_values[
time].insert(label);
3261 reachable_states[
time].swap(new_set);
3264 bool removed_values =
false;
3269 {reached_values[time].begin(), reached_values[time].end()}),
3274 if (removed_values) {
3283 void CpModelPresolver::ExtractBoolAnd() {
3284 absl::flat_hash_map<int, int> ref_to_bool_and;
3285 const int num_constraints = context_->
working_model->constraints_size();
3286 std::vector<int> to_remove;
3287 for (
int c = 0; c < num_constraints; ++c) {
3291 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr &&
3292 ct.bool_or().literals().size() == 2) {
3296 to_remove.push_back(c);
3300 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne &&
3301 ct.at_most_one().literals().size() == 2) {
3302 AddImplication(
ct.at_most_one().literals(0),
3305 to_remove.push_back(c);
3311 for (
const int c : to_remove) {
3312 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
3313 CHECK(RemoveConstraint(
ct));
3318 void CpModelPresolver::Probe() {
3322 for (
int i = 0; i < context_->
working_model->variables_size(); ++i) {
3338 auto* local_param =
model.GetOrCreate<SatParameters>();
3340 local_param->set_use_implied_bounds(
false);
3342 model.GetOrCreate<TimeLimit>()->MergeWithGlobalTimeLimit(options_.
time_limit);
3343 auto* encoder =
model.GetOrCreate<IntegerEncoder>();
3344 encoder->DisableImplicationBetweenLiteral();
3345 auto* mapping =
model.GetOrCreate<CpModelMapping>();
3349 auto* sat_solver =
model.GetOrCreate<SatSolver>();
3350 for (
const ConstraintProto&
ct :
model_proto.constraints()) {
3351 if (mapping->ConstraintIsAlreadyLoaded(&
ct))
continue;
3353 if (sat_solver->IsModelUnsat()) {
3357 encoder->AddAllImplicationsBetweenAssociatedLiterals();
3358 if (!sat_solver->Propagate()) {
3366 auto* implication_graph =
model.GetOrCreate<BinaryImplicationGraph>();
3370 model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
3372 if (sat_solver->IsModelUnsat() || !implication_graph->DetectEquivalences()) {
3377 CHECK_EQ(sat_solver->CurrentDecisionLevel(), 0);
3378 for (
int i = 0; i < sat_solver->LiteralTrail().
Index(); ++i) {
3379 const Literal l = sat_solver->LiteralTrail()[i];
3380 const int var = mapping->GetProtoVariableFromBooleanVariable(l.Variable());
3387 const int num_variables = context_->
working_model->variables().size();
3388 auto* integer_trail =
model.GetOrCreate<IntegerTrail>();
3389 for (
int var = 0;
var < num_variables; ++
var) {
3392 if (!mapping->IsBoolean(
var)) {
3395 integer_trail->InitialVariableDomain(mapping->Integer(
var)))) {
3402 const Literal l = mapping->Literal(
var);
3403 const Literal r = implication_graph->RepresentativeOf(l);
3406 mapping->GetProtoVariableFromBooleanVariable(r.Variable());
3417 void CpModelPresolver::PresolvePureSatPart() {
3422 const int num_variables = context_->
working_model->variables_size();
3423 SatPostsolver sat_postsolver(num_variables);
3424 SatPresolver sat_presolver(&sat_postsolver);
3425 sat_presolver.SetNumVariables(num_variables);
3426 sat_presolver.SetTimeLimit(options_.
time_limit);
3435 if (params.cp_model_postsolve_with_full_solver()) {
3436 params.set_presolve_blocked_clause(
false);
3442 params.set_presolve_use_bva(
false);
3443 sat_presolver.SetParameters(params);
3446 absl::flat_hash_set<int> used_variables;
3447 auto convert = [&used_variables](
int ref) {
3449 if (
RefIsPositive(ref))
return Literal(BooleanVariable(ref),
true);
3450 return Literal(BooleanVariable(
NegatedRef(ref)),
false);
3458 for (
int c = 0; c < context_->
working_model->constraints_size(); ++c) {
3460 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr ||
3461 ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolAnd) {
3480 std::vector<Literal> clause;
3481 int num_removed_constraints = 0;
3482 for (
int i = 0; i < context_->
working_model->constraints_size(); ++i) {
3485 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr) {
3486 ++num_removed_constraints;
3488 for (
const int ref :
ct.bool_or().literals()) {
3489 clause.push_back(convert(ref));
3491 for (
const int ref :
ct.enforcement_literal()) {
3492 clause.push_back(convert(ref).Negated());
3494 sat_presolver.AddClause(clause);
3501 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolAnd) {
3502 ++num_removed_constraints;
3503 std::vector<Literal> clause;
3504 for (
const int ref :
ct.enforcement_literal()) {
3505 clause.push_back(convert(ref).Negated());
3508 for (
const int ref :
ct.bool_and().literals()) {
3509 clause.back() = convert(ref);
3510 sat_presolver.AddClause(clause);
3520 if (num_removed_constraints == 0)
return;
3530 std::vector<bool> can_be_removed(num_variables,
false);
3531 for (
int i = 0; i < num_variables; ++i) {
3533 can_be_removed[i] =
true;
3539 if (used_variables.contains(i) && context_->
IsFixed(i)) {
3541 sat_presolver.AddClause({convert(i)});
3543 sat_presolver.AddClause({convert(
NegatedRef(i))});
3551 const int num_passes = params.presolve_use_bva() ? 4 : 1;
3552 for (
int i = 0; i < num_passes; ++i) {
3553 const int old_num_clause = sat_postsolver.NumClauses();
3554 if (!sat_presolver.Presolve(can_be_removed, options_.
log_info)) {
3555 VLOG(1) <<
"UNSAT during SAT presolve.";
3558 if (old_num_clause == sat_postsolver.NumClauses())
break;
3562 const int new_num_variables = sat_presolver.NumVariables();
3563 if (new_num_variables > context_->
working_model->variables_size()) {
3564 VLOG(1) <<
"New variables added by the SAT presolver.";
3566 i < new_num_variables; ++i) {
3567 IntegerVariableProto* var_proto =
3569 var_proto->add_domain(0);
3570 var_proto->add_domain(1);
3576 ExtractClauses(
true, sat_presolver, context_->
working_model);
3584 ExtractClauses(
false, sat_postsolver,
3592 void CpModelPresolver::ExpandObjective() {
3611 int unique_expanded_constraint = -1;
3612 const bool objective_was_a_single_variable =
3617 const int num_variables = context_->
working_model->variables_size();
3618 const int num_constraints = context_->
working_model->constraints_size();
3619 absl::flat_hash_set<int> relevant_constraints;
3620 std::vector<int> var_to_num_relevant_constraints(num_variables, 0);
3621 for (
int ct_index = 0; ct_index < num_constraints; ++ct_index) {
3622 const ConstraintProto&
ct = context_->
working_model->constraints(ct_index);
3624 if (!
ct.enforcement_literal().empty() ||
3625 ct.constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
3626 ct.linear().domain().size() != 2 ||
3627 ct.linear().domain(0) !=
ct.linear().domain(1)) {
3631 relevant_constraints.insert(ct_index);
3632 const int num_terms =
ct.linear().vars_size();
3633 for (
int i = 0; i < num_terms; ++i) {
3634 var_to_num_relevant_constraints[
PositiveRef(
ct.linear().vars(i))]++;
3638 std::set<int> var_to_process;
3640 const int var = entry.first;
3642 if (var_to_num_relevant_constraints[
var] != 0) {
3643 var_to_process.insert(
var);
3648 int num_expansions = 0;
3649 absl::flat_hash_set<int> processed_vars;
3650 std::vector<int> new_vars_in_objective;
3651 while (!relevant_constraints.empty()) {
3653 int objective_var = -1;
3654 while (!var_to_process.empty()) {
3655 const int var = *var_to_process.begin();
3656 CHECK(!processed_vars.contains(
var));
3657 if (var_to_num_relevant_constraints[
var] == 0) {
3658 processed_vars.insert(
var);
3659 var_to_process.erase(
var);
3664 var_to_process.erase(
var);
3667 objective_var =
var;
3671 if (objective_var == -1)
break;
3673 processed_vars.insert(objective_var);
3674 var_to_process.erase(objective_var);
3676 int expanded_linear_index = -1;
3677 int64 objective_coeff_in_expanded_constraint;
3678 int64 size_of_expanded_constraint = 0;
3679 const auto& non_deterministic_list =
3681 std::vector<int> constraints_with_objective(non_deterministic_list.begin(),
3682 non_deterministic_list.end());
3683 std::sort(constraints_with_objective.begin(),
3684 constraints_with_objective.end());
3685 for (
const int ct_index : constraints_with_objective) {
3686 if (relevant_constraints.count(ct_index) == 0)
continue;
3687 const ConstraintProto&
ct =
3692 relevant_constraints.erase(ct_index);
3693 const int num_terms =
ct.linear().vars_size();
3694 for (
int i = 0; i < num_terms; ++i) {
3695 var_to_num_relevant_constraints[
PositiveRef(
ct.linear().vars(i))]--;
3707 bool is_present =
false;
3708 int64 objective_coeff;
3709 for (
int i = 0; i < num_terms; ++i) {
3710 const int ref =
ct.linear().vars(i);
3711 const int64 coeff =
ct.linear().coeffs(i);
3713 CHECK(!is_present) <<
"Duplicate variables not supported.";
3715 objective_coeff = (ref == objective_var) ? coeff : -coeff;
3718 CHECK(!processed_vars.contains(
PositiveRef(ref)));
3728 if (std::abs(objective_coeff) == 1 &&
3729 num_terms > size_of_expanded_constraint) {
3730 expanded_linear_index = ct_index;
3731 size_of_expanded_constraint = num_terms;
3732 objective_coeff_in_expanded_constraint = objective_coeff;
3736 if (expanded_linear_index != -1) {
3737 context_->
UpdateRuleStats(
"objective: expanded objective constraint.");
3741 CHECK_EQ(std::abs(objective_coeff_in_expanded_constraint), 1);
3742 const ConstraintProto&
ct =
3743 context_->
working_model->constraints(expanded_linear_index);
3745 objective_var, objective_coeff_in_expanded_constraint,
ct,
3746 &new_vars_in_objective);
3749 for (
const int var : new_vars_in_objective) {
3750 if (!processed_vars.contains(
var)) var_to_process.insert(
var);
3763 for (
int i = 0; i < size_of_expanded_constraint; ++i) {
3764 const int ref =
ct.linear().vars(i);
3769 -
ct.linear().coeffs(i)))
3770 .RelaxIfTooComplex();
3772 implied_domain = implied_domain.InverseMultiplicationBy(
3773 objective_coeff_in_expanded_constraint);
3777 if (implied_domain.IsIncludedIn(context_->
DomainOf(objective_var))) {
3778 context_->
UpdateRuleStats(
"objective: removed objective constraint.");
3780 context_->
working_model->mutable_constraints(expanded_linear_index)
3784 unique_expanded_constraint = expanded_linear_index;
3794 if (num_expansions == 1 && objective_was_a_single_variable &&
3795 unique_expanded_constraint != -1) {
3797 "objective: removed unique objective constraint.");
3798 ConstraintProto* mutable_ct = context_->
working_model->mutable_constraints(
3799 unique_expanded_constraint);
3800 *(context_->
mapping_model->add_constraints()) = *mutable_ct;
3801 mutable_ct->Clear();
3813 void CpModelPresolver::MergeNoOverlapConstraints() {
3816 const int num_constraints = context_->
working_model->constraints_size();
3817 int old_num_no_overlaps = 0;
3818 int old_num_intervals = 0;
3821 std::vector<int> disjunctive_index;
3822 std::vector<std::vector<Literal>> cliques;
3823 for (
int c = 0; c < num_constraints; ++c) {
3825 if (
ct.constraint_case() != ConstraintProto::ConstraintCase::kNoOverlap) {
3828 std::vector<Literal> clique;
3829 for (
const int i :
ct.no_overlap().intervals()) {
3830 clique.push_back(Literal(BooleanVariable(i),
true));
3832 cliques.push_back(clique);
3833 disjunctive_index.push_back(c);
3835 old_num_no_overlaps++;
3836 old_num_intervals += clique.size();
3838 if (old_num_no_overlaps == 0)
return;
3842 local_model.GetOrCreate<Trail>()->Resize(num_constraints);
3843 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
3844 graph->Resize(num_constraints);
3845 for (
const std::vector<Literal>& clique : cliques) {
3848 CHECK(graph->AddAtMostOne(clique));
3850 CHECK(graph->DetectEquivalences());
3851 graph->TransformIntoMaxCliques(
3852 &cliques, options_.
parameters.merge_no_overlap_work_limit());
3855 int new_num_no_overlaps = 0;
3856 int new_num_intervals = 0;
3857 for (
int i = 0; i < cliques.size(); ++i) {
3858 const int ct_index = disjunctive_index[i];
3859 ConstraintProto*
ct =
3862 if (cliques[i].empty())
continue;
3863 for (
const Literal l : cliques[i]) {
3864 CHECK(l.IsPositive());
3865 ct->mutable_no_overlap()->add_intervals(l.Variable().value());
3867 new_num_no_overlaps++;
3868 new_num_intervals += cliques[i].size();
3870 if (old_num_intervals != new_num_intervals ||
3871 old_num_no_overlaps != new_num_no_overlaps) {
3872 VLOG(1) << absl::StrCat(
"Merged ", old_num_no_overlaps,
" no-overlaps (",
3873 old_num_intervals,
" intervals) into ",
3874 new_num_no_overlaps,
" no-overlaps (",
3875 new_num_intervals,
" intervals).");
3880 void CpModelPresolver::TransformIntoMaxCliques() {
3883 auto convert = [](
int ref) {
3884 if (
RefIsPositive(ref))
return Literal(BooleanVariable(ref),
true);
3885 return Literal(BooleanVariable(
NegatedRef(ref)),
false);
3887 const int num_constraints = context_->
working_model->constraints_size();
3890 std::vector<std::vector<Literal>> cliques;
3892 for (
int c = 0; c < num_constraints; ++c) {
3893 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
3894 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne) {
3895 std::vector<Literal> clique;
3896 for (
const int ref :
ct->at_most_one().literals()) {
3897 clique.push_back(convert(ref));
3899 cliques.push_back(clique);
3900 if (RemoveConstraint(
ct)) {
3903 }
else if (
ct->constraint_case() ==
3904 ConstraintProto::ConstraintCase::kBoolAnd) {
3905 if (
ct->enforcement_literal().size() != 1)
continue;
3906 const Literal enforcement = convert(
ct->enforcement_literal(0));
3907 for (
const int ref :
ct->bool_and().literals()) {
3908 cliques.push_back({enforcement, convert(ref).Negated()});
3910 if (RemoveConstraint(
ct)) {
3916 const int num_old_cliques = cliques.size();
3920 const int num_variables = context_->
working_model->variables().size();
3921 local_model.GetOrCreate<Trail>()->Resize(num_variables);
3922 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
3923 graph->Resize(num_variables);
3924 for (
const std::vector<Literal>& clique : cliques) {
3925 if (!graph->AddAtMostOne(clique)) {
3929 if (!graph->DetectEquivalences()) {
3932 graph->TransformIntoMaxCliques(
3933 &cliques, options_.
parameters.merge_at_most_one_work_limit());
3938 for (
int var = 0;
var < num_variables; ++
var) {
3939 const Literal l = Literal(BooleanVariable(
var),
true);
3940 if (graph->RepresentativeOf(l) != l) {
3941 const Literal r = graph->RepresentativeOf(l);
3943 var, r.IsPositive() ? r.Variable().value()
3948 int num_new_cliques = 0;
3949 for (
const std::vector<Literal>& clique : cliques) {
3950 if (clique.empty())
continue;
3953 for (
const Literal
literal : clique) {
3955 ct->mutable_at_most_one()->add_literals(
literal.Variable().value());
3957 ct->mutable_at_most_one()->add_literals(
3963 if (num_new_cliques != num_old_cliques) {
3964 context_->
UpdateRuleStats(
"at_most_one: transformed into max clique.");
3968 LOG(INFO) <<
"Merged " << num_old_cliques <<
" into " << num_new_cliques
3975 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
3978 if (ExploitEquivalenceRelations(c,
ct)) {
3983 if (PresolveEnforcementLiteral(
ct)) {
3988 switch (
ct->constraint_case()) {
3989 case ConstraintProto::ConstraintCase::kBoolOr:
3990 return PresolveBoolOr(
ct);
3991 case ConstraintProto::ConstraintCase::kBoolAnd:
3992 return PresolveBoolAnd(
ct);
3993 case ConstraintProto::ConstraintCase::kAtMostOne:
3994 return PresolveAtMostOne(
ct);
3995 case ConstraintProto::ConstraintCase::kBoolXor:
3996 return PresolveBoolXor(
ct);
3997 case ConstraintProto::ConstraintCase::kIntMax:
3998 if (
ct->int_max().vars_size() == 2 &&
4000 return PresolveIntAbs(
ct);
4002 return PresolveIntMax(
ct);
4004 case ConstraintProto::ConstraintCase::kIntMin:
4005 return PresolveIntMin(
ct);
4006 case ConstraintProto::ConstraintCase::kLinMax:
4007 return PresolveLinMax(
ct);
4008 case ConstraintProto::ConstraintCase::kLinMin:
4009 return PresolveLinMin(
ct);
4010 case ConstraintProto::ConstraintCase::kIntProd:
4011 return PresolveIntProd(
ct);
4012 case ConstraintProto::ConstraintCase::kIntDiv:
4013 return PresolveIntDiv(
ct);
4014 case ConstraintProto::ConstraintCase::kLinear: {
4015 if (CanonicalizeLinear(
ct)) {
4018 if (PresolveSmallLinear(
ct)) {
4021 if (PropagateDomainsInLinear(c,
ct)) {
4024 if (PresolveSmallLinear(
ct)) {
4028 if (RemoveSingletonInLinear(
ct)) {
4033 if (PresolveSmallLinear(
ct)) {
4037 if (PresolveLinearOnBooleans(
ct)) {
4040 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kLinear) {
4041 const int old_num_enforcement_literals =
ct->enforcement_literal_size();
4042 ExtractEnforcementLiteralFromLinearConstraint(
ct);
4043 if (
ct->constraint_case() ==
4044 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
4048 if (
ct->enforcement_literal_size() > old_num_enforcement_literals &&
4049 PresolveSmallLinear(
ct)) {
4055 case ConstraintProto::ConstraintCase::kInterval:
4056 return PresolveInterval(c,
ct);
4057 case ConstraintProto::ConstraintCase::kElement:
4058 return PresolveElement(
ct);
4059 case ConstraintProto::ConstraintCase::kTable:
4060 return PresolveTable(
ct);
4061 case ConstraintProto::ConstraintCase::kAllDiff:
4062 return PresolveAllDiff(
ct);
4063 case ConstraintProto::ConstraintCase::kNoOverlap:
4064 return PresolveNoOverlap(
ct);
4065 case ConstraintProto::ConstraintCase::kCumulative:
4066 return PresolveCumulative(
ct);
4067 case ConstraintProto::ConstraintCase::kCircuit:
4068 return PresolveCircuit(
ct);
4069 case ConstraintProto::ConstraintCase::kRoutes:
4070 return PresolveRoutes(
ct);
4071 case ConstraintProto::ConstraintCase::kAutomaton:
4072 return PresolveAutomaton(
ct);
4078 bool CpModelPresolver::ProcessSetPPCSubset(
4079 int c1,
int c2,
const std::vector<int>& c2_minus_c1,
4080 const std::vector<int>& original_constraint_index,
4081 std::vector<bool>* marked_for_removal) {
4083 CHECK(!(*marked_for_removal)[c1]);
4084 CHECK(!(*marked_for_removal)[c2]);
4085 ConstraintProto* ct1 = context_->
working_model->mutable_constraints(
4086 original_constraint_index[c1]);
4087 ConstraintProto* ct2 = context_->
working_model->mutable_constraints(
4088 original_constraint_index[c2]);
4089 if (ct1->constraint_case() == ConstraintProto::ConstraintCase::kBoolOr &&
4090 ct2->constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne) {
4092 for (
const int literal : c2_minus_c1) {
4098 if (ct1->constraint_case() == ct2->constraint_case()) {
4099 if (ct1->constraint_case() == ConstraintProto::ConstraintCase::kBoolOr) {
4100 (*marked_for_removal)[c2] =
true;
4104 CHECK_EQ(ct1->constraint_case(),
4105 ConstraintProto::ConstraintCase::kAtMostOne);
4106 (*marked_for_removal)[c1] =
true;
4116 bool CpModelPresolver::ProcessSetPPC() {
4117 bool changed =
false;
4118 const int num_constraints = context_->
working_model->constraints_size();
4122 std::vector<uint64> signatures;
4126 std::vector<std::vector<int>> constraint_literals;
4130 std::vector<std::vector<int>> literals_to_constraints;
4135 std::vector<bool> marked_for_removal;
4139 std::vector<int> original_constraint_index;
4143 int num_setppc_constraints = 0;
4144 for (
int c = 0; c < num_constraints; ++c) {
4145 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
4146 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kBoolOr ||
4147 ct->constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne) {
4156 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kBoolOr ||
4157 ct->constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne) {
4158 constraint_literals.push_back(GetLiteralsFromSetPPCConstraint(
ct));
4161 for (
const int literal : constraint_literals.back()) {
4163 signature |= (
int64{1} << (positive_literal % 64));
4164 DCHECK_GE(positive_literal, 0);
4165 if (positive_literal >= literals_to_constraints.size()) {
4166 literals_to_constraints.resize(positive_literal + 1);
4168 literals_to_constraints[positive_literal].push_back(
4169 num_setppc_constraints);
4171 signatures.push_back(signature);
4172 marked_for_removal.push_back(
false);
4173 original_constraint_index.push_back(c);
4174 num_setppc_constraints++;
4177 VLOG(1) <<
"#setppc constraints: " << num_setppc_constraints;
4180 absl::flat_hash_set<std::pair<int, int>> compared_constraints;
4181 for (
const std::vector<int>& literal_to_constraints :
4182 literals_to_constraints) {
4183 for (
int index1 = 0; index1 < literal_to_constraints.size(); ++index1) {
4188 const int c1 = literal_to_constraints[index1];
4189 if (marked_for_removal[c1])
continue;
4190 const std::vector<int>& c1_literals = constraint_literals[c1];
4191 ConstraintProto* ct1 = context_->
working_model->mutable_constraints(
4192 original_constraint_index[c1]);
4193 for (
int index2 = index1 + 1; index2 < literal_to_constraints.size();
4195 const int c2 = literal_to_constraints[index2];
4196 if (marked_for_removal[c2])
continue;
4197 if (marked_for_removal[c1])
break;
4199 if (c1 == c2)
continue;
4203 std::pair<int, int>(c1, c2))) {
4206 compared_constraints.insert({c1, c2});
4210 if (compared_constraints.size() >= 50000)
return changed;
4212 const bool smaller = (signatures[c1] & ~signatures[c2]) == 0;
4213 const bool larger = (signatures[c2] & ~signatures[c1]) == 0;
4215 if (!(smaller || larger)) {
4220 const std::vector<int>& c2_literals = constraint_literals[c2];
4221 ConstraintProto* ct2 = context_->
working_model->mutable_constraints(
4222 original_constraint_index[c2]);
4225 std::vector<int> c1_minus_c2;
4227 std::vector<int> c2_minus_c1;
4230 if (c1_minus_c2.empty() && c2_minus_c1.empty()) {
4231 if (ct1->constraint_case() == ct2->constraint_case()) {
4232 marked_for_removal[c2] =
true;
4235 }
else if (c1_minus_c2.empty()) {
4236 if (ProcessSetPPCSubset(c1, c2, c2_minus_c1,
4237 original_constraint_index,
4238 &marked_for_removal)) {
4240 original_constraint_index[c1]);
4242 original_constraint_index[c2]);
4244 }
else if (c2_minus_c1.empty()) {
4245 if (ProcessSetPPCSubset(c2, c1, c1_minus_c2,
4246 original_constraint_index,
4247 &marked_for_removal)) {
4249 original_constraint_index[c1]);
4251 original_constraint_index[c2]);
4257 for (
int c = 0; c < num_setppc_constraints; ++c) {
4258 if (marked_for_removal[c]) {
4260 original_constraint_index[c]);
4261 changed = RemoveConstraint(
ct);
4269 void CpModelPresolver::TryToSimplifyDomain(
int var) {
4277 if (r.representative !=
var)
return;
4292 if (domain.Size() == 2 && (domain.Min() != 0 || domain.Max() != 1)) {
4297 if (domain.NumIntervals() != domain.Size())
return;
4299 const int64 var_min = domain.Min();
4300 int64 gcd = domain[1].start - var_min;
4302 const ClosedInterval& i = domain[
index];
4303 CHECK_EQ(i.start, i.end);
4304 const int64 shifted_value = i.start - var_min;
4305 CHECK_GE(shifted_value, 0);
4308 if (gcd == 1)
break;
4310 if (gcd == 1)
return;
4314 std::vector<int64> scaled_values;
4316 const ClosedInterval& i = domain[
index];
4317 CHECK_EQ(i.start, i.end);
4318 const int64 shifted_value = i.start - var_min;
4319 scaled_values.push_back(shifted_value / gcd);
4331 void CpModelPresolver::EncodeAllAffineRelations() {
4332 int64 num_added = 0;
4337 if (r.representative ==
var)
continue;
4344 if (!PresolveAffineRelationIfAny(
var))
break;
4351 auto* arg =
ct->mutable_linear();
4354 arg->add_vars(r.representative);
4355 arg->add_coeffs(-r.coeff);
4356 arg->add_domain(r.offset);
4357 arg->add_domain(r.offset);
4365 if (options_.
log_info && num_added > 0) {
4366 LOG(INFO) << num_added <<
" affine relations still in the model.";
4371 bool CpModelPresolver::PresolveAffineRelationIfAny(
int var) {
4375 if (r.representative ==
var)
return true;
4394 auto* arg =
ct->mutable_linear();
4397 arg->add_vars(r.representative);
4398 arg->add_coeffs(-r.coeff);
4399 arg->add_domain(r.offset);
4400 arg->add_domain(r.offset);
4406 void CpModelPresolver::PresolveToFixPoint() {
4410 const int64 max_num_operations =
4411 options_.
parameters.cp_model_max_num_presolve_operations() > 0
4412 ? options_.
parameters.cp_model_max_num_presolve_operations()
4418 absl::flat_hash_set<std::pair<int, int>> var_constraint_pair_already_called;
4423 std::vector<bool> in_queue(context_->
working_model->constraints_size(),
4425 std::deque<int> queue;
4426 for (
int c = 0; c < in_queue.size(); ++c) {
4427 if (context_->
working_model->constraints(c).constraint_case() !=
4428 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
4438 std::sort(queue.begin(), queue.end(), [
this](
int a,
int b) {
4439 const int score_a = context_->ConstraintToVars(a).size();
4440 const int score_b = context_->ConstraintToVars(b).size();
4441 return score_a < score_b || (score_a == score_b && a < b);
4450 const int c = queue.front();
4451 in_queue[c] =
false;
4454 const int old_num_constraint =
4458 LOG(INFO) <<
"Unsat after presolving constraint #" << c
4459 <<
" (warning, dump might be inconsistent): "
4460 << context_->
working_model->constraints(c).ShortDebugString();
4464 const int new_num_constraints =
4466 if (new_num_constraints > old_num_constraint) {
4468 in_queue.resize(new_num_constraints,
true);
4469 for (
int c = old_num_constraint; c < new_num_constraints; ++c) {
4486 const int current_num_variables = context_->
working_model->variables_size();
4487 for (
int v = 0; v < current_num_variables; ++v) {
4489 if (!PresolveAffineRelationIfAny(v))
return;
4494 TryToSimplifyDomain(v);
4503 in_queue.resize(context_->
working_model->constraints_size(),
false);
4508 if (c >= 0 && !in_queue[c]) {
4518 const int num_vars = context_->
working_model->variables_size();
4519 for (
int v = 0; v < num_vars; ++v) {
4521 if (constraints.size() != 1)
continue;
4522 const int c = *constraints.begin();
4523 if (c < 0)
continue;
4529 std::pair<int, int>(v, c))) {
4532 var_constraint_pair_already_called.insert({v, c});
4542 std::sort(queue.begin(), queue.end());
4556 const int num_constraints = context_->
working_model->constraints_size();
4557 for (
int c = 0; c < num_constraints; ++c) {
4558 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
4559 switch (
ct->constraint_case()) {
4560 case ConstraintProto::ConstraintCase::kNoOverlap:
4562 if (PresolveNoOverlap(
ct)) {
4566 case ConstraintProto::ConstraintCase::kNoOverlap2D:
4570 case ConstraintProto::ConstraintCase::kCumulative:
4572 if (PresolveCumulative(
ct)) {
4576 case ConstraintProto::ConstraintCase::kBoolOr: {
4579 for (
const auto& pair :
4581 bool modified =
false;
4601 LOG(INFO) <<
"- " <<
context->NumAffineRelations()
4602 <<
" affine relations were detected.";
4603 LOG(INFO) <<
"- " <<
context->NumEquivRelations()
4604 <<
" variable equivalence relations were detected.";
4605 std::map<std::string, int> sorted_rules(
context->stats_by_rule_name.begin(),
4606 context->stats_by_rule_name.end());
4607 for (
const auto& entry : sorted_rules) {
4608 if (entry.second == 1) {
4609 LOG(INFO) <<
"- rule '" << entry.first <<
"' was applied 1 time.";
4611 LOG(INFO) <<
"- rule '" << entry.first <<
"' was applied " << entry.second
4622 std::vector<int>* postsolve_mapping) {
4629 std::vector<int>* postsolve_mapping)
4630 : options_(options),
4631 postsolve_mapping_(postsolve_mapping),
4634 options.
parameters.enumerate_all_solutions() ||
4635 options.
parameters.fill_tightened_domains_in_response() ||
4639 for (
const auto& decision_strategy :
4641 *(context_->
mapping_model->add_search_strategy()) = decision_strategy;
4676 if (!options_.
parameters.cp_model_presolve()) {
4688 for (
int c = 0; c < context_->
working_model->constraints_size(); ++c) {
4689 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
4690 PresolveEnforcementLiteral(
ct);
4691 switch (
ct->constraint_case()) {
4692 case ConstraintProto::ConstraintCase::kBoolOr:
4695 case ConstraintProto::ConstraintCase::kBoolAnd:
4696 PresolveBoolAnd(
ct);
4698 case ConstraintProto::ConstraintCase::kAtMostOne:
4699 PresolveAtMostOne(
ct);
4701 case ConstraintProto::ConstraintCase::kLinear:
4702 CanonicalizeLinear(
ct);
4714 for (
int iter = 0; iter < options_.
parameters.max_presolve_iterations();
4719 int old_num_non_empty_constraints = 0;
4720 for (
int c = 0; c < context_->
working_model->constraints_size(); ++c) {
4723 if (type == ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET)
continue;
4724 old_num_non_empty_constraints++;
4731 PresolveToFixPoint();
4738 if (options_.
parameters.cp_model_probing_level() > 0) {
4742 PresolveToFixPoint();
4749 if (options_.
parameters.cp_model_use_sat_presolve()) {
4752 PresolvePureSatPart();
4765 const int old_size = context_->
working_model->constraints_size();
4766 for (
int c = 0; c < old_size; ++c) {
4767 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
4768 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear) {
4771 ExtractAtMostOneFromLinear(
ct);
4776 if (iter == 0) TransformIntoMaxCliques();
4786 PresolveToFixPoint();
4792 old_num_non_empty_constraints)) {
4799 MergeNoOverlapConstraints();
4809 EncodeAllAffineRelations();
4816 const std::vector<int> duplicates =
4818 if (!duplicates.empty()) {
4819 for (
const int c : duplicates) {
4822 if (type == ConstraintProto::ConstraintCase::kInterval) {
4839 context_->
working_model->add_constraints()->mutable_bool_or();
4851 absl::flat_hash_set<int> used_variables;
4852 for (DecisionStrategyProto& strategy :
4854 DecisionStrategyProto copy = strategy;
4855 strategy.clear_variables();
4856 for (
const int ref : copy.variables()) {
4865 used_variables.insert(
var);
4873 strategy.add_variables(rep);
4874 if (strategy.variable_selection_strategy() !=
4875 DecisionStrategyProto::CHOOSE_FIRST) {
4876 DecisionStrategyProto::AffineTransformation* t =
4877 strategy.add_transformations();
4880 t->set_positive_coeff(std::abs(r.
coeff));
4888 strategy.add_variables(ref);
4894 for (
int i = 0; i < context_->
working_model->variables_size(); ++i) {
4897 DCHECK_GT(context_->
working_model->variables(i).domain_size(), 0);
4905 postsolve_mapping_->clear();
4906 std::vector<int> mapping(context_->
working_model->variables_size(), -1);
4907 for (
int i = 0; i < context_->
working_model->variables_size(); ++i) {
4912 mapping[i] = postsolve_mapping_->size();
4913 postsolve_mapping_->push_back(i);
4937 if (!error.empty()) {
4939 LOG(INFO) <<
"Error while validating postsolved model: " << error;
4946 if (!error.empty()) {
4948 LOG(INFO) <<
"Error while validating mapping_model model: " << error;
4962 auto mapping_function = [&mapping](
int* ref) {
4967 for (ConstraintProto& ct_ref : *
proto->mutable_constraints()) {
4973 if (
proto->has_objective()) {
4974 for (
int& mutable_ref : *
proto->mutable_objective()->mutable_vars()) {
4975 mapping_function(&mutable_ref);
4980 for (
int& mutable_ref : *
proto->mutable_assumptions()) {
4981 mapping_function(&mutable_ref);
4986 for (DecisionStrategyProto& strategy : *
proto->mutable_search_strategy()) {
4987 DecisionStrategyProto copy = strategy;
4988 strategy.clear_variables();
4989 for (
const int ref : copy.variables()) {
4995 strategy.clear_transformations();
4996 for (
const auto& transform : copy.transformations()) {
4997 const int ref = transform.var();
5000 auto* new_transform = strategy.add_transformations();
5001 *new_transform = transform;
5008 if (
proto->has_solution_hint()) {
5009 auto* mutable_hint =
proto->mutable_solution_hint();
5011 for (
int i = 0; i < mutable_hint->vars_size(); ++i) {
5012 const int old_ref = mutable_hint->vars(i);
5013 const int64 old_value = mutable_hint->values(i);
5021 const int image = mapping[
var];
5023 mutable_hint->set_vars(new_size, image);
5024 mutable_hint->set_values(new_size,
value);
5029 mutable_hint->mutable_vars()->Truncate(new_size);
5030 mutable_hint->mutable_values()->Truncate(new_size);
5032 proto->clear_solution_hint();
5037 std::vector<IntegerVariableProto> new_variables;
5038 for (
int i = 0; i < mapping.size(); ++i) {
5039 const int image = mapping[i];
5040 if (image < 0)
continue;
5041 if (image >= new_variables.size()) {
5042 new_variables.resize(image + 1, IntegerVariableProto());
5044 new_variables[image].Swap(
proto->mutable_variables(i));
5046 proto->clear_variables();
5047 for (IntegerVariableProto& proto_ref : new_variables) {
5048 proto->add_variables()->Swap(&proto_ref);
5052 for (
const IntegerVariableProto& v :
proto->variables()) {
5053 CHECK_GT(v.domain_size(), 0);
5058 std::vector<int> result;
5061 absl::flat_hash_map<int64, int> equiv_constraints;
5064 const int num_constraints =
model_proto.constraints().size();
5065 for (
int c = 0; c < num_constraints; ++c) {
5066 if (
model_proto.constraints(c).constraint_case() ==
5067 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
5070 s =
model_proto.constraints(c).SerializeAsString();
5071 const int64 hash = std::hash<std::string>()(s);
5072 const auto insert = equiv_constraints.insert({
hash, c});
5073 if (!insert.second) {
5075 const int other_c_with_same_hash = insert.first->second;
5077 model_proto.constraints(other_c_with_same_hash).SerializeAsString()) {
5078 result.push_back(c);