31 #include "absl/container/flat_hash_map.h"
32 #include "absl/container/flat_hash_set.h"
33 #include "absl/random/random.h"
34 #include "absl/strings/str_join.h"
60 bool CpModelPresolver::RemoveConstraint(ConstraintProto*
ct) {
68 std::vector<int> interval_mapping(context_->
working_model->constraints_size(),
70 int new_num_constraints = 0;
71 const int old_num_non_empty_constraints =
73 for (
int c = 0; c < old_num_non_empty_constraints; ++c) {
74 const auto type = context_->
working_model->constraints(c).constraint_case();
75 if (type == ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET)
continue;
76 if (type == ConstraintProto::ConstraintCase::kInterval) {
77 interval_mapping[c] = new_num_constraints;
79 context_->
working_model->mutable_constraints(new_num_constraints++)
82 context_->
working_model->mutable_constraints()->DeleteSubrange(
83 new_num_constraints, old_num_non_empty_constraints - new_num_constraints);
84 for (ConstraintProto& ct_ref :
87 [&interval_mapping](
int* ref) {
88 *ref = interval_mapping[*ref];
95 bool CpModelPresolver::PresolveEnforcementLiteral(ConstraintProto*
ct) {
100 const int old_size =
ct->enforcement_literal().size();
101 for (
const int literal :
ct->enforcement_literal()) {
110 return RemoveConstraint(
ct);
117 return RemoveConstraint(
ct);
124 const int64_t obj_coeff =
128 context_->
UpdateRuleStats(
"enforcement literal with unique direction");
130 return RemoveConstraint(
ct);
134 ct->set_enforcement_literal(new_size++,
literal);
136 ct->mutable_enforcement_literal()->Truncate(new_size);
137 return new_size != old_size;
140 bool CpModelPresolver::PresolveBoolXor(ConstraintProto*
ct) {
145 bool changed =
false;
146 int num_true_literals = 0;
148 for (
const int literal :
ct->bool_xor().literals()) {
169 ct->mutable_bool_xor()->set_literals(new_size++,
literal);
173 }
else if (new_size == 2) {
176 if (num_true_literals % 2 == 1) {
178 ct->mutable_bool_xor()->set_literals(new_size++, true_literal);
180 if (num_true_literals > 1) {
181 context_->
UpdateRuleStats(
"bool_xor: remove even number of true literals");
184 ct->mutable_bool_xor()->mutable_literals()->Truncate(new_size);
188 bool CpModelPresolver::PresolveBoolOr(ConstraintProto*
ct) {
195 for (
const int literal :
ct->enforcement_literal()) {
198 ct->clear_enforcement_literal();
202 bool changed =
false;
205 for (
const int literal :
ct->bool_or().literals()) {
212 return RemoveConstraint(
ct);
220 return RemoveConstraint(
ct);
224 return RemoveConstraint(
ct);
243 return RemoveConstraint(
ct);
256 ct->mutable_bool_or()->mutable_literals()->Clear();
258 ct->mutable_bool_or()->add_literals(lit);
264 ABSL_MUST_USE_RESULT
bool CpModelPresolver::MarkConstraintAsFalse(
265 ConstraintProto*
ct) {
268 ct->mutable_bool_or()->clear_literals();
269 for (
const int lit :
ct->enforcement_literal()) {
272 ct->clear_enforcement_literal();
280 bool CpModelPresolver::PresolveBoolAnd(ConstraintProto*
ct) {
285 for (
const int literal :
ct->bool_and().literals()) {
288 return RemoveConstraint(
ct);
291 bool changed =
false;
293 for (
const int literal :
ct->bool_and().literals()) {
296 return MarkConstraintAsFalse(
ct);
316 ct->mutable_bool_and()->mutable_literals()->Clear();
318 ct->mutable_bool_and()->add_literals(lit);
327 if (
ct->enforcement_literal().size() == 1 &&
328 ct->bool_and().literals().size() == 1) {
329 const int enforcement =
ct->enforcement_literal(0);
339 ct->bool_and().literals(0));
347 bool CpModelPresolver::PresolveAtMostOrExactlyOne(ConstraintProto*
ct) {
348 bool is_at_most_one =
ct->constraint_case() == ConstraintProto::kAtMostOne;
349 const std::string
name = is_at_most_one ?
"at_most_one: " :
"exactly_one: ";
350 auto* literals = is_at_most_one
351 ?
ct->mutable_at_most_one()->mutable_literals()
352 :
ct->mutable_exactly_one()->mutable_literals();
356 for (
const int literal : *literals) {
362 int num_positive = 0;
363 int num_negative = 0;
364 for (
const int other : *literals) {
385 return RemoveConstraint(
ct);
391 bool changed =
false;
392 bool transform_to_at_most_one =
false;
394 for (
const int literal : *literals) {
397 for (
const int other : *literals) {
402 return RemoveConstraint(
ct);
415 if (is_at_most_one && !is_removable &&
419 const int64_t coeff = it->second;
426 if (is_at_most_one) {
433 is_at_most_one =
true;
434 transform_to_at_most_one =
true;
444 if (transform_to_at_most_one) {
447 literals =
ct->mutable_at_most_one()->mutable_literals();
459 bool CpModelPresolver::PresolveAtMostOne(ConstraintProto*
ct) {
462 const bool changed = PresolveAtMostOrExactlyOne(
ct);
463 if (
ct->constraint_case() != ConstraintProto::kAtMostOne)
return changed;
466 const auto& literals =
ct->at_most_one().literals();
467 if (literals.empty()) {
469 return RemoveConstraint(
ct);
473 if (literals.size() == 1) {
475 return RemoveConstraint(
ct);
481 bool CpModelPresolver::PresolveExactlyOne(ConstraintProto*
ct) {
484 const bool changed = PresolveAtMostOrExactlyOne(
ct);
485 if (
ct->constraint_case() != ConstraintProto::kExactlyOne)
return changed;
488 const auto& literals =
ct->exactly_one().literals();
489 if (literals.empty()) {
494 if (literals.size() == 1) {
497 return RemoveConstraint(
ct);
501 if (literals.size() == 2) {
505 return RemoveConstraint(
ct);
511 bool CpModelPresolver::PresolveIntMax(ConstraintProto*
ct) {
513 if (
ct->int_max().vars().empty()) {
515 return MarkConstraintAsFalse(
ct);
517 const int target_ref =
ct->int_max().target();
522 bool contains_target_ref =
false;
523 std::set<int> used_ref;
525 for (
const int ref :
ct->int_max().vars()) {
526 if (ref == target_ref) contains_target_ref =
true;
530 infered_min =
std::max(infered_min, int64_t{0});
532 used_ref.insert(ref);
533 ct->mutable_int_max()->set_vars(new_size++, ref);
537 if (new_size < ct->int_max().vars_size()) {
540 ct->mutable_int_max()->mutable_vars()->Truncate(new_size);
541 if (contains_target_ref) {
543 for (
const int ref :
ct->int_max().vars()) {
544 if (ref == target_ref)
continue;
545 ConstraintProto* new_ct = context_->
working_model->add_constraints();
546 *new_ct->mutable_enforcement_literal() =
ct->enforcement_literal();
547 auto* arg = new_ct->mutable_linear();
548 arg->add_vars(target_ref);
555 return RemoveConstraint(
ct);
559 Domain infered_domain;
560 for (
const int ref :
ct->int_max().vars()) {
561 infered_domain = infered_domain.UnionWith(
566 bool domain_reduced =
false;
578 const Domain& target_domain = context_->
DomainOf(target_ref);
581 target_domain.Max()))
582 .IsIncludedIn(target_domain)) {
583 if (infered_domain.Max() <= target_domain.Max()) {
586 }
else if (
ct->enforcement_literal().empty()) {
588 for (
const int ref :
ct->int_max().vars()) {
592 target_domain.Max()))) {
600 for (
const int ref :
ct->int_max().vars()) {
601 ConstraintProto* new_ct = context_->
working_model->add_constraints();
602 *(new_ct->mutable_enforcement_literal()) =
ct->enforcement_literal();
603 ct->mutable_linear()->add_vars(ref);
604 ct->mutable_linear()->add_coeffs(1);
606 ct->mutable_linear()->add_domain(target_domain.Max());
613 return RemoveConstraint(
ct);
619 const int size =
ct->int_max().vars_size();
620 const int64_t target_max = context_->
MaxOf(target_ref);
621 for (
const int ref :
ct->int_max().vars()) {
629 if (context_->
MaxOf(ref) >= infered_min) {
630 ct->mutable_int_max()->set_vars(new_size++, ref);
633 if (domain_reduced) {
637 bool modified =
false;
638 if (new_size < size) {
640 ct->mutable_int_max()->mutable_vars()->Truncate(new_size);
646 return MarkConstraintAsFalse(
ct);
652 ConstraintProto* new_ct = context_->
working_model->add_constraints();
654 auto* arg = new_ct->mutable_linear();
655 arg->add_vars(target_ref);
657 arg->add_vars(
ct->int_max().vars(0));
662 return RemoveConstraint(
ct);
667 bool CpModelPresolver::PresolveLinMin(ConstraintProto*
ct) {
670 const auto copy =
ct->lin_min();
672 ct->mutable_lin_max()->mutable_target());
673 for (
const LinearExpressionProto& expr : copy.exprs()) {
674 LinearExpressionProto*
const new_expr =
ct->mutable_lin_max()->add_exprs();
677 return PresolveLinMax(
ct);
680 bool CpModelPresolver::PresolveLinMax(ConstraintProto*
ct) {
682 if (
ct->lin_max().exprs().empty()) {
684 return MarkConstraintAsFalse(
ct);
691 bool changed = CanonicalizeLinearExpression(
692 *
ct,
ct->mutable_lin_max()->mutable_target());
693 for (LinearExpressionProto& exp : *(
ct->mutable_lin_max()->mutable_exprs())) {
694 changed |= CanonicalizeLinearExpression(*
ct, &exp);
700 int64_t infered_min = context_->
MinOf(
ct->lin_max().target());
701 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
712 for (
int i = 0; i <
ct->lin_max().exprs_size(); ++i) {
713 const LinearExpressionProto& expr =
ct->lin_max().exprs(i);
714 if (context_->
MaxOf(expr) >= infered_min) {
715 *
ct->mutable_lin_max()->mutable_exprs(new_size) = expr;
720 if (new_size < ct->lin_max().exprs_size()) {
722 ct->mutable_lin_max()->mutable_exprs()->DeleteSubrange(
723 new_size,
ct->lin_max().exprs_size() - new_size);
730 bool CpModelPresolver::PresolveIntAbs(ConstraintProto*
ct) {
733 const int target_ref =
ct->int_max().target();
738 const Domain new_target_domain =
739 var_domain.
UnionWith(var_domain.Negation())
749 const Domain target_domain = context_->
DomainOf(target_ref);
750 const Domain new_var_domain =
751 target_domain.
UnionWith(target_domain.Negation());
761 ConstraintProto* new_ct = context_->
working_model->add_constraints();
762 new_ct->set_name(
ct->name());
763 auto* arg = new_ct->mutable_linear();
764 arg->add_vars(target_ref);
771 return RemoveConstraint(
ct);
776 ConstraintProto* new_ct = context_->
working_model->add_constraints();
777 new_ct->set_name(
ct->name());
778 auto* arg = new_ct->mutable_linear();
779 arg->add_vars(target_ref);
786 return RemoveConstraint(
ct);
792 context_->
IsFixed(target_ref)) {
793 if (!context_->
IsFixed(target_ref)) {
798 return RemoveConstraint(
ct);
808 bool CpModelPresolver::PresolveIntMin(ConstraintProto*
ct) {
811 const auto copy =
ct->int_min();
812 ct->mutable_int_max()->set_target(
NegatedRef(copy.target()));
813 for (
const int ref : copy.vars()) {
816 return PresolveIntMax(
ct);
819 bool CpModelPresolver::PresolveIntProd(ConstraintProto*
ct) {
823 if (
ct->int_prod().vars().empty()) {
828 return RemoveConstraint(
ct);
830 bool changed =
false;
834 int64_t constant = 1;
835 for (
int i = 0; i <
ct->int_prod().vars().size(); ++i) {
836 const int ref =
ct->int_prod().vars(i);
838 if (r.representative != ref && r.offset == 0) {
840 ct->mutable_int_prod()->set_vars(i, r.representative);
855 const int old_target =
ct->int_prod().target();
856 const int new_target = context_->
working_model->variables_size();
858 IntegerVariableProto* var_proto = context_->
working_model->add_variables();
865 ct->mutable_int_prod()->set_target(new_target);
866 if (context_->
IsFixed(new_target)) {
878 ConstraintProto* new_ct = context_->
working_model->add_constraints();
879 LinearConstraintProto* lin = new_ct->mutable_linear();
880 lin->add_vars(old_target);
882 lin->add_vars(new_target);
883 lin->add_coeffs(-constant);
893 for (
const int ref :
ct->int_prod().vars()) {
894 implied = implied.ContinuousMultiplicationBy(context_->
DomainOf(ref));
896 bool modified =
false;
905 if (
ct->int_prod().vars_size() == 2) {
906 int a =
ct->int_prod().vars(0);
907 int b =
ct->int_prod().vars(1);
908 const int product =
ct->int_prod().target();
912 const int64_t value_a = context_->
MinOf(
a);
918 return RemoveConstraint(
ct);
919 }
else if (
b != product) {
920 ConstraintProto*
const lin = context_->
working_model->add_constraints();
921 lin->mutable_linear()->add_vars(
b);
922 lin->mutable_linear()->add_coeffs(value_a);
923 lin->mutable_linear()->add_vars(product);
924 lin->mutable_linear()->add_coeffs(-1);
925 lin->mutable_linear()->add_domain(0);
926 lin->mutable_linear()->add_domain(0);
929 return RemoveConstraint(
ct);
930 }
else if (value_a != 1) {
935 return RemoveConstraint(
ct);
938 return RemoveConstraint(
ct);
940 }
else if (
a ==
b &&
a == product) {
945 return RemoveConstraint(
ct);
950 const int target_ref =
ct->int_prod().target();
952 for (
const int var :
ct->int_prod().vars()) {
954 if (context_->
MinOf(
var) < 0)
return changed;
955 if (context_->
MaxOf(
var) > 1)
return changed;
964 ConstraintProto* new_ct = context_->
working_model->add_constraints();
965 new_ct->add_enforcement_literal(target_ref);
966 auto* arg = new_ct->mutable_bool_and();
967 for (
const int var :
ct->int_prod().vars()) {
968 arg->add_literals(
var);
972 ConstraintProto* new_ct = context_->
working_model->add_constraints();
973 auto* arg = new_ct->mutable_bool_or();
974 arg->add_literals(target_ref);
975 for (
const int var :
ct->int_prod().vars()) {
980 return RemoveConstraint(
ct);
983 bool CpModelPresolver::PresolveIntDiv(ConstraintProto*
ct) {
987 const int target =
ct->int_div().target();
988 const int ref_x =
ct->int_div().vars(0);
989 const int ref_div =
ct->int_div().vars(1);
996 const int64_t divisor = context_->
MinOf(ref_div);
998 LinearConstraintProto*
const lin =
999 context_->
working_model->add_constraints()->mutable_linear();
1000 lin->add_vars(ref_x);
1002 lin->add_vars(target);
1003 lin->add_coeffs(-1);
1008 return RemoveConstraint(
ct);
1010 bool domain_modified =
false;
1013 &domain_modified)) {
1014 if (domain_modified) {
1016 "int_div: updated domain of target in target = X / cte");
1027 if (context_->
MinOf(target) >= 0 && context_->
MinOf(ref_x) >= 0 &&
1029 LinearConstraintProto*
const lin =
1030 context_->
working_model->add_constraints()->mutable_linear();
1031 lin->add_vars(ref_x);
1033 lin->add_vars(target);
1034 lin->add_coeffs(-divisor);
1036 lin->add_domain(divisor - 1);
1039 "int_div: linearize positive division with a constant divisor");
1040 return RemoveConstraint(
ct);
1048 bool CpModelPresolver::ExploitEquivalenceRelations(
int c, ConstraintProto*
ct) {
1049 bool changed =
false;
1054 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kLinear) {
1055 for (
int& ref : *
ct->mutable_enforcement_literal()) {
1067 bool work_to_do =
false;
1070 if (r.representative !=
var) {
1075 if (!work_to_do)
return false;
1079 [&changed,
this](
int* ref) {
1090 [&changed,
this](
int* ref) {
1101 void CpModelPresolver::DivideLinearByGcd(ConstraintProto*
ct) {
1106 const int num_vars =
ct->linear().vars().size();
1107 for (
int i = 0; i < num_vars; ++i) {
1108 const int64_t magnitude = std::abs(
ct->linear().coeffs(i));
1110 if (gcd == 1)
break;
1114 for (
int i = 0; i < num_vars; ++i) {
1115 ct->mutable_linear()->set_coeffs(i,
ct->linear().coeffs(i) / gcd);
1119 if (
ct->linear().domain_size() == 0) {
1120 return (
void)MarkConstraintAsFalse(
ct);
1125 void CpModelPresolver::PresolveLinearEqualityModuloTwo(ConstraintProto*
ct) {
1126 if (!
ct->enforcement_literal().empty())
return;
1127 if (
ct->linear().domain().size() != 2)
return;
1128 if (
ct->linear().domain(0) !=
ct->linear().domain(1))
return;
1133 std::vector<int> literals;
1134 for (
int i = 0; i <
ct->linear().vars().size(); ++i) {
1135 const int64_t coeff =
ct->linear().coeffs(i);
1136 const int ref =
ct->linear().vars(i);
1137 if (coeff % 2 == 0)
continue;
1140 if (literals.size() > 2)
return;
1142 if (literals.size() == 1) {
1143 const int64_t rhs = std::abs(
ct->linear().domain(0));
1144 context_->
UpdateRuleStats(
"linear: only one odd Boolean in equality");
1146 }
else if (literals.size() == 2) {
1147 const int64_t rhs = std::abs(
ct->linear().domain(0));
1148 context_->
UpdateRuleStats(
"linear: only two odd Booleans in equality");
1158 template <
typename ProtoWithVarsAndCoeffs>
1159 bool CpModelPresolver::CanonicalizeLinearExpressionInternal(
1160 const ConstraintProto&
ct, ProtoWithVarsAndCoeffs*
proto, int64_t* offset) {
1166 int64_t sum_of_fixed_terms = 0;
1167 bool remapped =
false;
1168 const int old_size =
proto->vars().size();
1170 for (
int i = 0; i < old_size; ++i) {
1171 const int ref =
proto->vars(i);
1173 const int64_t coeff =
1175 if (coeff == 0)
continue;
1178 sum_of_fixed_terms += coeff * context_->
MinOf(
var);
1184 bool removed =
false;
1185 for (
const int enf :
ct.enforcement_literal()) {
1189 sum_of_fixed_terms += coeff;
1198 context_->
UpdateRuleStats(
"linear: enforcement literal in expression");
1203 if (r.representative !=
var) {
1205 sum_of_fixed_terms += coeff * r.
offset;
1207 tmp_terms_.push_back({r.representative, coeff * r.coeff});
1209 proto->clear_vars();
1210 proto->clear_coeffs();
1211 std::sort(tmp_terms_.begin(), tmp_terms_.end());
1212 int current_var = 0;
1213 int64_t current_coeff = 0;
1214 for (
const auto entry : tmp_terms_) {
1216 if (entry.first == current_var) {
1217 current_coeff += entry.second;
1219 if (current_coeff != 0) {
1220 proto->add_vars(current_var);
1221 proto->add_coeffs(current_coeff);
1223 current_var = entry.first;
1224 current_coeff = entry.second;
1227 if (current_coeff != 0) {
1228 proto->add_vars(current_var);
1229 proto->add_coeffs(current_coeff);
1234 if (
proto->vars().size() < old_size) {
1237 *offset = sum_of_fixed_terms;
1238 return remapped ||
proto->vars().size() < old_size;
1241 bool CpModelPresolver::CanonicalizeLinearExpression(
1242 const ConstraintProto&
ct, LinearExpressionProto* exp) {
1244 const bool result = CanonicalizeLinearExpressionInternal(
ct, exp, &offset);
1245 exp->set_offset(exp->offset() + offset);
1249 bool CpModelPresolver::CanonicalizeLinear(ConstraintProto*
ct) {
1250 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1256 CanonicalizeLinearExpressionInternal(*
ct,
ct->mutable_linear(), &offset);
1260 ct->mutable_linear());
1262 DivideLinearByGcd(
ct);
1266 bool CpModelPresolver::RemoveSingletonInLinear(ConstraintProto*
ct) {
1267 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1272 std::set<int> index_to_erase;
1273 const int num_vars =
ct->linear().vars().size();
1279 for (
int i = 0; i < num_vars; ++i) {
1280 const int var =
ct->linear().vars(i);
1281 const int64_t coeff =
ct->linear().coeffs(i);
1285 const auto term_domain =
1287 if (!exact)
continue;
1291 if (new_rhs.NumIntervals() > 100)
continue;
1298 index_to_erase.insert(i);
1305 if (index_to_erase.empty()) {
1307 if (context_->
params().presolve_substitution_level() <= 0)
return false;
1308 if (!
ct->enforcement_literal().empty())
return false;
1312 if (rhs.Min() != rhs.Max())
return false;
1314 for (
int i = 0; i < num_vars; ++i) {
1315 const int var =
ct->linear().vars(i);
1316 const int64_t coeff =
ct->linear().coeffs(i);
1334 const int64_t objective_coeff =
1337 if (objective_coeff % coeff != 0)
continue;
1341 const auto term_domain =
1343 if (!exact)
continue;
1345 if (new_rhs.NumIntervals() > 100)
continue;
1353 objective_coeff))) {
1370 LOG(
WARNING) <<
"This was not supposed to happen and the presolve "
1371 "could be improved.";
1374 context_->
UpdateRuleStats(
"linear: singleton column define objective.");
1378 return RemoveConstraint(
ct);
1387 "linear: singleton column in equality and in objective.");
1389 index_to_erase.insert(i);
1393 if (index_to_erase.empty())
return false;
1402 for (
int i = 0; i < num_vars; ++i) {
1403 if (index_to_erase.count(i)) {
1407 ct->mutable_linear()->set_coeffs(new_size,
ct->linear().coeffs(i));
1408 ct->mutable_linear()->set_vars(new_size,
ct->linear().vars(i));
1411 ct->mutable_linear()->mutable_vars()->Truncate(new_size);
1412 ct->mutable_linear()->mutable_coeffs()->Truncate(new_size);
1414 DivideLinearByGcd(
ct);
1418 bool CpModelPresolver::PresolveSmallLinear(ConstraintProto*
ct) {
1419 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1425 if (
ct->linear().vars().empty()) {
1428 if (rhs.Contains(0)) {
1429 return RemoveConstraint(
ct);
1431 return MarkConstraintAsFalse(
ct);
1438 if (
ct->linear().vars_size() == 1 &&
ct->enforcement_literal_size() > 0 &&
1439 ct->linear().coeffs(0) == 1 &&
1442 context_->
UpdateRuleStats(
"linear: remove abs from abs(x) in domain");
1443 const Domain implied_abs_target_domain =
1446 .IntersectionWith(context_->
DomainOf(
ct->linear().vars(0)));
1448 if (implied_abs_target_domain.IsEmpty()) {
1449 return MarkConstraintAsFalse(
ct);
1452 const Domain new_abs_var_domain =
1453 implied_abs_target_domain
1454 .UnionWith(implied_abs_target_domain.Negation())
1455 .IntersectionWith(context_->
DomainOf(abs_arg));
1457 if (new_abs_var_domain.IsEmpty()) {
1458 return MarkConstraintAsFalse(
ct);
1461 ConstraintProto* new_ct = context_->
working_model->add_constraints();
1462 new_ct->set_name(
ct->name());
1463 for (
const int literal :
ct->enforcement_literal()) {
1464 new_ct->add_enforcement_literal(
literal);
1466 auto* arg = new_ct->mutable_linear();
1467 arg->add_vars(abs_arg);
1471 return RemoveConstraint(
ct);
1475 if (
ct->enforcement_literal_size() != 1 ||
ct->linear().vars_size() != 1 ||
1476 (
ct->linear().coeffs(0) != 1 &&
ct->linear().coeffs(0) == -1)) {
1480 const int literal =
ct->enforcement_literal(0);
1481 const LinearConstraintProto& linear =
ct->linear();
1482 const int ref = linear.vars(0);
1484 const int64_t coeff =
1487 if (linear.domain_size() == 2 && linear.domain(0) == linear.domain(1)) {
1489 : -linear.domain(0) * coeff;
1498 if (complement.Size() != 1)
return false;
1500 : -complement.Min() * coeff;
1515 if (
ct->linear().vars().size() == 1) {
1517 ?
ct->linear().coeffs(0)
1518 : -
ct->linear().coeffs(0);
1523 rhs.InverseMultiplicationBy(coeff))) {
1526 return RemoveConstraint(
ct);
1533 const LinearConstraintProto& arg =
ct->linear();
1534 if (arg.vars_size() == 2) {
1536 const int64_t rhs_min = rhs.Min();
1537 const int64_t rhs_max = rhs.Max();
1538 if (rhs_min == rhs_max) {
1539 const int v1 = arg.vars(0);
1540 const int v2 = arg.vars(1);
1541 const int64_t coeff1 = arg.coeffs(0);
1542 const int64_t coeff2 = arg.coeffs(1);
1546 }
else if (coeff2 == 1) {
1548 }
else if (coeff1 == -1) {
1550 }
else if (coeff2 == -1) {
1553 if (added)
return RemoveConstraint(
ct);
1563 bool IsLeConstraint(
const Domain& domain,
const Domain& all_values) {
1567 .IsIncludedIn(domain);
1571 bool IsGeConstraint(
const Domain& domain,
const Domain& all_values) {
1575 .IsIncludedIn(domain);
1581 bool RhsCanBeFixedToMin(int64_t coeff,
const Domain& var_domain,
1582 const Domain& terms,
const Domain& rhs) {
1583 if (var_domain.NumIntervals() != 1)
return false;
1584 if (std::abs(coeff) != 1)
return false;
1592 if (coeff == 1 && terms.Max() + var_domain.Min() <= rhs.Min()) {
1595 if (coeff == -1 && terms.Max() - var_domain.Max() <= rhs.Min()) {
1601 bool RhsCanBeFixedToMax(int64_t coeff,
const Domain& var_domain,
1602 const Domain& terms,
const Domain& rhs) {
1603 if (var_domain.NumIntervals() != 1)
return false;
1604 if (std::abs(coeff) != 1)
return false;
1606 if (coeff == 1 && terms.Min() + var_domain.Max() >= rhs.Max()) {
1609 if (coeff == -1 && terms.Min() - var_domain.Min() >= rhs.Max()) {
1616 void TakeIntersectionWith(
const absl::flat_hash_set<int>& current,
1617 absl::flat_hash_set<int>* to_clear) {
1618 std::vector<int> new_set;
1619 for (
const int c : *to_clear) {
1620 if (current.contains(c)) new_set.push_back(c);
1623 for (
const int c : new_set) to_clear->insert(c);
1628 bool CpModelPresolver::PropagateDomainsInLinear(
int c, ConstraintProto*
ct) {
1629 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1637 const int num_vars =
ct->linear().vars_size();
1638 term_domains.resize(num_vars + 1);
1639 left_domains.resize(num_vars + 1);
1640 left_domains[0] = Domain(0);
1641 for (
int i = 0; i < num_vars; ++i) {
1642 const int var =
ct->linear().vars(i);
1643 const int64_t coeff =
ct->linear().coeffs(i);
1646 left_domains[i + 1] =
1649 const Domain& implied_rhs = left_domains[num_vars];
1653 if (implied_rhs.IsIncludedIn(old_rhs)) {
1655 return RemoveConstraint(
ct);
1659 Domain rhs = old_rhs.SimplifyUsingImpliedDomain(implied_rhs);
1660 if (rhs.IsEmpty()) {
1662 return MarkConstraintAsFalse(
ct);
1664 if (rhs != old_rhs) {
1672 bool is_le_constraint = IsLeConstraint(rhs, implied_rhs);
1673 bool is_ge_constraint = IsGeConstraint(rhs, implied_rhs);
1676 if (
ct->enforcement_literal().size() > 1)
return false;
1678 bool new_bounds =
false;
1679 bool recanonicalize =
false;
1680 Domain negated_rhs = rhs.Negation();
1681 Domain right_domain(0);
1683 Domain implied_term_domain;
1684 term_domains[num_vars] = Domain(0);
1685 for (
int i = num_vars - 1; i >= 0; --i) {
1686 const int var =
ct->linear().vars(i);
1687 const int64_t var_coeff =
ct->linear().coeffs(i);
1689 right_domain.AdditionWith(term_domains[i + 1]).RelaxIfTooComplex();
1690 implied_term_domain = left_domains[i].AdditionWith(right_domain);
1691 new_domain = implied_term_domain.AdditionWith(negated_rhs)
1692 .InverseMultiplicationBy(-var_coeff);
1694 if (
ct->enforcement_literal().empty()) {
1699 }
else if (
ct->enforcement_literal().size() == 1) {
1710 recanonicalize =
true;
1714 if (is_le_constraint || is_ge_constraint) {
1715 CHECK_NE(is_le_constraint, is_ge_constraint);
1716 if ((var_coeff > 0) == is_ge_constraint) {
1731 const bool is_in_objective =
1735 const int64_t obj_coeff =
1744 if (obj_coeff <= 0 &&
1754 recanonicalize =
true;
1758 if (obj_coeff >= 0 &&
1768 recanonicalize =
true;
1776 if (!
ct->enforcement_literal().empty())
continue;
1788 if (rhs.Min() != rhs.Max() &&
1791 const bool same_sign = (var_coeff > 0) == (obj_coeff > 0);
1793 if (same_sign && RhsCanBeFixedToMin(var_coeff, context_->
DomainOf(
var),
1794 implied_term_domain, rhs)) {
1795 rhs = Domain(rhs.Min());
1798 if (!same_sign && RhsCanBeFixedToMax(var_coeff, context_->
DomainOf(
var),
1799 implied_term_domain, rhs)) {
1800 rhs = Domain(rhs.Max());
1806 negated_rhs = rhs.Negation();
1810 right_domain = Domain(0);
1814 is_le_constraint =
false;
1815 is_ge_constraint =
false;
1816 for (
const int var :
ct->linear().vars()) {
1833 if (
ct->linear().vars().size() <= 2)
continue;
1838 if (rhs.Min() != rhs.Max())
continue;
1844 if (context_->
DomainOf(
var) != new_domain)
continue;
1845 if (std::abs(var_coeff) != 1)
continue;
1846 if (context_->
params().presolve_substitution_level() <= 0)
continue;
1852 bool is_in_objective =
false;
1854 is_in_objective =
true;
1860 if (is_in_objective) col_size--;
1861 const int row_size =
ct->linear().vars_size();
1865 const int num_entries_added = (row_size - 1) * (col_size - 1);
1866 const int num_entries_removed = col_size + row_size - 1;
1868 if (num_entries_added > num_entries_removed) {
1874 std::vector<int> others;
1883 if (context_->
working_model->constraints(c).constraint_case() !=
1884 ConstraintProto::ConstraintCase::kLinear) {
1888 for (
const int ref :
1889 context_->
working_model->constraints(c).enforcement_literal()) {
1895 others.push_back(c);
1897 if (abort)
continue;
1900 for (
const int c : others) {
1913 if (is_in_objective) {
1918 absl::StrCat(
"linear: variable substitution ", others.size()));
1929 const int ct_index = context_->
mapping_model->constraints().size();
1931 LinearConstraintProto* mapping_linear_ct =
1934 std::swap(mapping_linear_ct->mutable_vars()->at(0),
1935 mapping_linear_ct->mutable_vars()->at(i));
1936 std::swap(mapping_linear_ct->mutable_coeffs()->at(0),
1937 mapping_linear_ct->mutable_coeffs()->at(i));
1938 return RemoveConstraint(
ct);
1943 if (recanonicalize)
return CanonicalizeLinear(
ct);
1954 void CpModelPresolver::ExtractEnforcementLiteralFromLinearConstraint(
1955 int ct_index, ConstraintProto*
ct) {
1956 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1961 const LinearConstraintProto& arg =
ct->linear();
1962 const int num_vars = arg.vars_size();
1966 if (num_vars <= 1)
return;
1968 int64_t min_sum = 0;
1969 int64_t max_sum = 0;
1970 int64_t max_coeff_magnitude = 0;
1971 for (
int i = 0; i < num_vars; ++i) {
1972 const int ref = arg.vars(i);
1973 const int64_t coeff = arg.coeffs(i);
1974 const int64_t term_a = coeff * context_->
MinOf(ref);
1975 const int64_t term_b = coeff * context_->
MaxOf(ref);
1976 max_coeff_magnitude =
std::max(max_coeff_magnitude, std::abs(coeff));
1977 min_sum +=
std::min(term_a, term_b);
1978 max_sum +=
std::max(term_a, term_b);
1987 const auto& domain =
ct->linear().domain();
1988 const int64_t ub_threshold = domain[domain.size() - 2] - min_sum;
1989 const int64_t lb_threshold = max_sum - domain[1];
1991 if (max_coeff_magnitude <
std::max(ub_threshold, lb_threshold))
return;
2010 const bool lower_bounded = min_sum < rhs_domain.Min();
2011 const bool upper_bounded = max_sum > rhs_domain.Max();
2012 if (!lower_bounded && !upper_bounded)
return;
2013 if (lower_bounded && upper_bounded) {
2015 ConstraintProto* new_ct1 = context_->
working_model->add_constraints();
2017 if (!
ct->name().empty()) {
2018 new_ct1->set_name(absl::StrCat(
ct->name(),
" (part 1)"));
2021 new_ct1->mutable_linear());
2023 ConstraintProto* new_ct2 = context_->
working_model->add_constraints();
2025 if (!
ct->name().empty()) {
2026 new_ct2->set_name(absl::StrCat(
ct->name(),
" (part 2)"));
2029 new_ct2->mutable_linear());
2032 return (
void)RemoveConstraint(
ct);
2038 const int64_t threshold = lower_bounded ? ub_threshold : lb_threshold;
2041 const bool only_booleans =
2042 !context_->
params().presolve_extract_integer_enforcement();
2047 int64_t rhs_offset = 0;
2048 bool some_integer_encoding_were_extracted =
false;
2049 LinearConstraintProto* mutable_arg =
ct->mutable_linear();
2050 for (
int i = 0; i < arg.vars_size(); ++i) {
2051 int ref = arg.vars(i);
2052 int64_t coeff = arg.coeffs(i);
2059 if (context_->
IsFixed(ref) || coeff < threshold ||
2060 (only_booleans && !is_boolean)) {
2062 mutable_arg->set_vars(new_size, mutable_arg->vars(i));
2063 mutable_arg->set_coeffs(new_size, mutable_arg->coeffs(i));
2071 some_integer_encoding_were_extracted =
true;
2073 "linear: extracted integer enforcement literal");
2075 if (lower_bounded) {
2076 ct->add_enforcement_literal(is_boolean
2079 ref, context_->
MinOf(ref)));
2080 rhs_offset -= coeff * context_->
MinOf(ref);
2082 ct->add_enforcement_literal(is_boolean
2085 ref, context_->
MaxOf(ref)));
2086 rhs_offset -= coeff * context_->
MaxOf(ref);
2089 mutable_arg->mutable_vars()->Truncate(new_size);
2090 mutable_arg->mutable_coeffs()->Truncate(new_size);
2092 if (some_integer_encoding_were_extracted) {
2098 void CpModelPresolver::ExtractAtMostOneFromLinear(ConstraintProto*
ct) {
2103 const LinearConstraintProto& arg =
ct->linear();
2104 const int num_vars = arg.vars_size();
2105 int64_t min_sum = 0;
2106 int64_t max_sum = 0;
2107 for (
int i = 0; i < num_vars; ++i) {
2108 const int ref = arg.vars(i);
2109 const int64_t coeff = arg.coeffs(i);
2110 const int64_t term_a = coeff * context_->
MinOf(ref);
2111 const int64_t term_b = coeff * context_->
MaxOf(ref);
2112 min_sum +=
std::min(term_a, term_b);
2113 max_sum +=
std::max(term_a, term_b);
2115 for (
const int type : {0, 1}) {
2116 std::vector<int> at_most_one;
2117 for (
int i = 0; i < num_vars; ++i) {
2118 const int ref = arg.vars(i);
2119 const int64_t coeff = arg.coeffs(i);
2120 if (context_->
MinOf(ref) != 0)
continue;
2121 if (context_->
MaxOf(ref) != 1)
continue;
2126 if (min_sum + 2 * std::abs(coeff) > rhs.Max()) {
2127 at_most_one.push_back(coeff > 0 ? ref :
NegatedRef(ref));
2130 if (max_sum - 2 * std::abs(coeff) < rhs.Min()) {
2131 at_most_one.push_back(coeff > 0 ?
NegatedRef(ref) : ref);
2135 if (at_most_one.size() > 1) {
2141 ConstraintProto* new_ct = context_->
working_model->add_constraints();
2142 new_ct->set_name(
ct->name());
2143 for (
const int ref : at_most_one) {
2144 new_ct->mutable_at_most_one()->add_literals(ref);
2153 bool CpModelPresolver::PresolveLinearOnBooleans(ConstraintProto*
ct) {
2154 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
2159 const LinearConstraintProto& arg =
ct->linear();
2160 const int num_vars = arg.vars_size();
2162 int64_t max_coeff = 0;
2163 int64_t min_sum = 0;
2164 int64_t max_sum = 0;
2165 for (
int i = 0; i < num_vars; ++i) {
2167 const int var = arg.vars(i);
2168 const int64_t coeff = arg.coeffs(i);
2171 if (context_->
MinOf(
var) != 0)
return false;
2172 if (context_->
MaxOf(
var) != 1)
return false;
2176 min_coeff =
std::min(min_coeff, coeff);
2177 max_coeff =
std::max(max_coeff, coeff);
2181 min_coeff =
std::min(min_coeff, -coeff);
2182 max_coeff =
std::max(max_coeff, -coeff);
2194 if ((!rhs_domain.Contains(min_sum) &&
2195 min_sum + min_coeff > rhs_domain.Max()) ||
2196 (!rhs_domain.Contains(max_sum) &&
2197 max_sum - min_coeff < rhs_domain.Min())) {
2198 context_->
UpdateRuleStats(
"linear: all booleans and trivially false");
2199 return MarkConstraintAsFalse(
ct);
2201 if (Domain(min_sum, max_sum).IsIncludedIn(rhs_domain)) {
2203 return RemoveConstraint(
ct);
2210 DCHECK(!rhs_domain.IsEmpty());
2211 if (min_sum + min_coeff > rhs_domain.Max()) {
2214 const auto copy = arg;
2215 ct->mutable_bool_and()->clear_literals();
2216 for (
int i = 0; i < num_vars; ++i) {
2217 ct->mutable_bool_and()->add_literals(
2218 copy.coeffs(i) > 0 ?
NegatedRef(copy.vars(i)) : copy.vars(i));
2220 return PresolveBoolAnd(
ct);
2221 }
else if (max_sum - min_coeff < rhs_domain.Min()) {
2224 const auto copy = arg;
2225 ct->mutable_bool_and()->clear_literals();
2226 for (
int i = 0; i < num_vars; ++i) {
2227 ct->mutable_bool_and()->add_literals(
2228 copy.coeffs(i) > 0 ? copy.vars(i) :
NegatedRef(copy.vars(i)));
2230 return PresolveBoolAnd(
ct);
2231 }
else if (min_sum + min_coeff >= rhs_domain.Min() &&
2232 rhs_domain.front().end >= max_sum) {
2235 const auto copy = arg;
2236 ct->mutable_bool_or()->clear_literals();
2237 for (
int i = 0; i < num_vars; ++i) {
2238 ct->mutable_bool_or()->add_literals(
2239 copy.coeffs(i) > 0 ? copy.vars(i) :
NegatedRef(copy.vars(i)));
2241 return PresolveBoolOr(
ct);
2242 }
else if (max_sum - min_coeff <= rhs_domain.Max() &&
2243 rhs_domain.back().start <= min_sum) {
2246 const auto copy = arg;
2247 ct->mutable_bool_or()->clear_literals();
2248 for (
int i = 0; i < num_vars; ++i) {
2249 ct->mutable_bool_or()->add_literals(
2250 copy.coeffs(i) > 0 ?
NegatedRef(copy.vars(i)) : copy.vars(i));
2252 return PresolveBoolOr(
ct);
2254 min_sum + max_coeff <= rhs_domain.Max() &&
2255 min_sum + 2 * min_coeff > rhs_domain.Max() &&
2256 rhs_domain.back().start <= min_sum) {
2259 const auto copy = arg;
2260 ct->mutable_at_most_one()->clear_literals();
2261 for (
int i = 0; i < num_vars; ++i) {
2262 ct->mutable_at_most_one()->add_literals(
2263 copy.coeffs(i) > 0 ? copy.vars(i) :
NegatedRef(copy.vars(i)));
2267 max_sum - max_coeff >= rhs_domain.Min() &&
2268 max_sum - 2 * min_coeff < rhs_domain.Min() &&
2269 rhs_domain.front().end >= max_sum) {
2272 const auto copy = arg;
2273 ct->mutable_at_most_one()->clear_literals();
2274 for (
int i = 0; i < num_vars; ++i) {
2275 ct->mutable_at_most_one()->add_literals(
2276 copy.coeffs(i) > 0 ?
NegatedRef(copy.vars(i)) : copy.vars(i));
2280 min_sum < rhs_domain.Min() &&
2281 min_sum + min_coeff >= rhs_domain.Min() &&
2282 min_sum + 2 * min_coeff > rhs_domain.Max() &&
2283 min_sum + max_coeff <= rhs_domain.Max()) {
2285 ConstraintProto* exactly_one = context_->
working_model->add_constraints();
2286 exactly_one->set_name(
ct->name());
2287 for (
int i = 0; i < num_vars; ++i) {
2288 exactly_one->mutable_exactly_one()->add_literals(
2289 arg.coeffs(i) > 0 ? arg.vars(i) :
NegatedRef(arg.vars(i)));
2292 return RemoveConstraint(
ct);
2294 max_sum > rhs_domain.Max() &&
2295 max_sum - min_coeff <= rhs_domain.Max() &&
2296 max_sum - 2 * min_coeff < rhs_domain.Min() &&
2297 max_sum - max_coeff >= rhs_domain.Min()) {
2299 ConstraintProto* exactly_one = context_->
working_model->add_constraints();
2300 exactly_one->set_name(
ct->name());
2301 for (
int i = 0; i < num_vars; ++i) {
2302 exactly_one->mutable_exactly_one()->add_literals(
2303 arg.coeffs(i) > 0 ?
NegatedRef(arg.vars(i)) : arg.vars(i));
2306 return RemoveConstraint(
ct);
2313 if (num_vars > 3)
return false;
2318 const int max_mask = (1 << arg.vars_size());
2319 for (
int mask = 0; mask < max_mask; ++mask) {
2321 for (
int i = 0; i < num_vars; ++i) {
2322 if ((mask >> i) & 1)
value += arg.coeffs(i);
2324 if (rhs_domain.Contains(
value))
continue;
2327 ConstraintProto* new_ct = context_->
working_model->add_constraints();
2328 auto* new_arg = new_ct->mutable_bool_or();
2330 *new_ct->mutable_enforcement_literal() =
ct->enforcement_literal();
2332 for (
int i = 0; i < num_vars; ++i) {
2333 new_arg->add_literals(((mask >> i) & 1) ?
NegatedRef(arg.vars(i))
2339 return RemoveConstraint(
ct);
2344 void AddLinearExpression(int64_t coeff,
const LinearExpressionProto& exp,
2345 LinearConstraintProto* linear_ct) {
2346 const int size = exp.vars().size();
2347 for (
int i = 0; i < size; ++i) {
2348 linear_ct->add_vars(exp.vars(i));
2349 linear_ct->add_coeffs(coeff * exp.coeffs(i));
2351 if (exp.offset() != 0) {
2353 .AdditionWith(Domain(-coeff * exp.offset())),
2360 bool CpModelPresolver::PresolveInterval(
int c, ConstraintProto*
ct) {
2363 if (
ct->enforcement_literal().empty() && !
ct->interval().has_start_view()) {
2364 bool changed =
false;
2365 const int start =
ct->interval().start();
2366 const int end =
ct->interval().end();
2367 const int size =
ct->interval().size();
2368 const Domain start_domain = context_->
DomainOf(start);
2369 const Domain end_domain = context_->
DomainOf(end);
2370 const Domain size_domain = context_->
DomainOf(size);
2377 end, start_domain.AdditionWith(size_domain), &changed)) {
2381 start, end_domain.AdditionWith(size_domain.Negation()), &changed)) {
2385 size, end_domain.AdditionWith(start_domain.Negation()), &changed)) {
2394 if (!
ct->interval().has_start_view()) {
2396 const int start =
ct->interval().start();
2397 const int end =
ct->interval().end();
2398 const int size =
ct->interval().size();
2399 ConstraintProto* new_ct = context_->
working_model->add_constraints();
2400 *(new_ct->mutable_enforcement_literal()) =
ct->enforcement_literal();
2401 new_ct->mutable_linear()->add_domain(0);
2402 new_ct->mutable_linear()->add_domain(0);
2403 new_ct->mutable_linear()->add_vars(start);
2404 new_ct->mutable_linear()->add_coeffs(1);
2405 new_ct->mutable_linear()->add_vars(size);
2406 new_ct->mutable_linear()->add_coeffs(1);
2407 new_ct->mutable_linear()->add_vars(end);
2408 new_ct->mutable_linear()->add_coeffs(-1);
2412 return RemoveConstraint(
ct);
2420 if (context_->
params().convert_intervals()) {
2421 bool changed =
false;
2422 IntervalConstraintProto*
interval =
ct->mutable_interval();
2423 if (!
ct->interval().has_start_view()) {
2428 interval->mutable_start_view()->add_coeffs(1);
2429 interval->mutable_start_view()->set_offset(0);
2431 interval->mutable_size_view()->add_coeffs(1);
2432 interval->mutable_size_view()->set_offset(0);
2434 interval->mutable_end_view()->add_coeffs(1);
2435 interval->mutable_end_view()->set_offset(0);
2442 ConstraintProto* new_ct = context_->
working_model->add_constraints();
2443 *(new_ct->mutable_enforcement_literal()) =
ct->enforcement_literal();
2444 new_ct->mutable_linear()->add_domain(0);
2445 new_ct->mutable_linear()->add_domain(0);
2446 AddLinearExpression(1,
interval->start_view(), new_ct->mutable_linear());
2447 AddLinearExpression(1,
interval->size_view(), new_ct->mutable_linear());
2448 AddLinearExpression(-1,
interval->end_view(), new_ct->mutable_linear());
2458 CanonicalizeLinearExpression(*
ct,
interval->mutable_start_view());
2459 changed |= CanonicalizeLinearExpression(*
ct,
interval->mutable_size_view());
2460 changed |= CanonicalizeLinearExpression(*
ct,
interval->mutable_end_view());
2469 if ( (
false) &&
ct->enforcement_literal().empty() &&
2470 context_->
IsFixed(
ct->interval().size())) {
2472 1, context_->
MinOf(
ct->interval().size()));
2479 bool CpModelPresolver::PresolveElement(ConstraintProto*
ct) {
2482 const int index_ref =
ct->element().index();
2483 const int target_ref =
ct->element().target();
2488 bool all_constants =
true;
2489 absl::flat_hash_set<int64_t> constant_set;
2490 bool all_included_in_target_domain =
true;
2493 bool reduced_index_domain =
false;
2495 Domain(0,
ct->element().vars_size() - 1),
2496 &reduced_index_domain)) {
2504 std::vector<int64_t> possible_indices;
2505 const Domain& index_domain = context_->
DomainOf(index_ref);
2506 for (
const ClosedInterval&
interval : index_domain) {
2509 const int ref =
ct->element().vars(index_value);
2510 const int64_t target_value =
2511 target_ref == index_ref ? index_value : -index_value;
2513 possible_indices.push_back(target_value);
2517 if (possible_indices.size() < index_domain.Size()) {
2523 "element: reduced index domain when target equals index");
2529 Domain infered_domain;
2530 const Domain& initial_index_domain = context_->
DomainOf(index_ref);
2531 const Domain& target_domain = context_->
DomainOf(target_ref);
2532 std::vector<int64_t> possible_indices;
2533 for (
const ClosedInterval
interval : initial_index_domain) {
2537 const int ref =
ct->element().vars(
value);
2538 const Domain& domain = context_->
DomainOf(ref);
2539 if (domain.IntersectionWith(target_domain).IsEmpty())
continue;
2540 possible_indices.push_back(
value);
2541 if (domain.IsFixed()) {
2542 constant_set.insert(domain.Min());
2544 all_constants =
false;
2546 if (!domain.IsIncludedIn(target_domain)) {
2547 all_included_in_target_domain =
false;
2549 infered_domain = infered_domain.
UnionWith(domain);
2552 if (possible_indices.size() < initial_index_domain.Size()) {
2559 bool domain_modified =
false;
2561 &domain_modified)) {
2564 if (domain_modified) {
2570 if (context_->
IsFixed(index_ref)) {
2571 const int var =
ct->element().vars(context_->
MinOf(index_ref));
2572 if (
var != target_ref) {
2573 LinearConstraintProto*
const lin =
2574 context_->
working_model->add_constraints()->mutable_linear();
2576 lin->add_coeffs(-1);
2577 lin->add_vars(target_ref);
2584 return RemoveConstraint(
ct);
2590 if (all_constants && constant_set.size() == 1) {
2593 return RemoveConstraint(
ct);
2598 if (context_->
MinOf(index_ref) == 0 && context_->
MaxOf(index_ref) == 1 &&
2600 const int64_t v0 = context_->
MinOf(
ct->element().vars(0));
2601 const int64_t v1 = context_->
MinOf(
ct->element().vars(1));
2603 LinearConstraintProto*
const lin =
2604 context_->
working_model->add_constraints()->mutable_linear();
2605 lin->add_vars(target_ref);
2607 lin->add_vars(index_ref);
2608 lin->add_coeffs(v0 - v1);
2609 lin->add_domain(v0);
2610 lin->add_domain(v0);
2612 context_->
UpdateRuleStats(
"element: linearize constant element of size 2");
2613 return RemoveConstraint(
ct);
2617 const AffineRelation::Relation r_index =
2619 if (r_index.representative != index_ref) {
2621 if (context_->
DomainOf(r_index.representative).
Size() >
2627 const int64_t r_min = context_->
MinOf(r_ref);
2628 const int64_t r_max = context_->
MaxOf(r_ref);
2629 const int array_size =
ct->element().vars_size();
2631 context_->
UpdateRuleStats(
"TODO element: representative has bad domain");
2632 }
else if (r_index.offset >= 0 && r_index.offset < array_size &&
2633 r_index.offset + r_max * r_index.coeff >= 0 &&
2634 r_index.offset + r_max * r_index.coeff < array_size) {
2636 ElementConstraintProto*
const element =
2637 context_->
working_model->add_constraints()->mutable_element();
2638 for (int64_t v = 0; v <= r_max; ++v) {
2639 const int64_t scaled_index = v * r_index.coeff + r_index.offset;
2641 CHECK_LT(scaled_index, array_size);
2642 element->add_vars(
ct->element().vars(scaled_index));
2644 element->set_index(r_ref);
2645 element->set_target(target_ref);
2647 if (r_index.coeff == 1) {
2653 return RemoveConstraint(
ct);
2659 if (all_constants && unique_index) {
2663 context_->
UpdateRuleStats(
"element: trivial target domain reduction");
2666 return RemoveConstraint(
ct);
2669 const bool unique_target =
2671 context_->
IsFixed(target_ref);
2672 if (all_included_in_target_domain && unique_target) {
2676 return RemoveConstraint(
ct);
2679 if (unique_target && !context_->
IsFixed(target_ref)) {
2689 bool CpModelPresolver::PresolveTable(ConstraintProto*
ct) {
2692 if (
ct->table().vars().empty()) {
2694 return RemoveConstraint(
ct);
2700 const int num_vars =
ct->table().vars_size();
2701 const int num_tuples =
ct->table().values_size() / num_vars;
2702 std::vector<int64_t> tuple(num_vars);
2703 std::vector<std::vector<int64_t>> new_tuples;
2704 new_tuples.reserve(num_tuples);
2705 std::vector<absl::flat_hash_set<int64_t>> new_domains(num_vars);
2706 std::vector<AffineRelation::Relation> affine_relations;
2708 absl::flat_hash_set<int> visited;
2709 for (
const int ref :
ct->table().vars()) {
2717 bool modified_variables =
false;
2718 for (
int v = 0; v < num_vars; ++v) {
2719 const int ref =
ct->table().vars(v);
2721 affine_relations.push_back(r);
2722 if (r.representative != ref) {
2723 modified_variables =
true;
2727 for (
int i = 0; i < num_tuples; ++i) {
2728 bool delete_row =
false;
2730 for (
int j = 0; j < num_vars; ++j) {
2731 const int ref =
ct->table().vars(j);
2732 int64_t v =
ct->table().values(i * num_vars + j);
2733 const AffineRelation::Relation& r = affine_relations[j];
2734 if (r.representative != ref) {
2735 const int64_t inverse_value = (v - r.offset) / r.coeff;
2736 if (inverse_value * r.coeff + r.offset != v) {
2749 if (delete_row)
continue;
2750 new_tuples.push_back(tuple);
2751 for (
int j = 0; j < num_vars; ++j) {
2752 const int64_t v = tuple[j];
2753 new_domains[j].insert(v);
2759 if (new_tuples.size() < num_tuples || modified_variables) {
2760 ct->mutable_table()->clear_values();
2761 for (
const std::vector<int64_t>& t : new_tuples) {
2762 for (
const int64_t v : t) {
2763 ct->mutable_table()->add_values(v);
2766 if (new_tuples.size() < num_tuples) {
2771 if (modified_variables) {
2772 for (
int j = 0; j < num_vars; ++j) {
2773 const AffineRelation::Relation& r = affine_relations[j];
2774 if (r.representative !=
ct->table().vars(j)) {
2775 ct->mutable_table()->set_vars(j, r.representative);
2779 "table: replace variable by canonical affine one");
2783 if (
ct->table().negated())
return modified_variables;
2786 bool changed =
false;
2787 for (
int j = 0; j < num_vars; ++j) {
2788 const int ref =
ct->table().vars(j);
2792 new_domains[j].end())),
2800 if (num_vars == 1) {
2803 return RemoveConstraint(
ct);
2808 for (
int j = 0; j < num_vars; ++j) prod *= new_domains[j].size();
2809 if (prod == new_tuples.size()) {
2811 return RemoveConstraint(
ct);
2817 if (new_tuples.size() > 0.7 * prod) {
2819 std::vector<std::vector<int64_t>> var_to_values(num_vars);
2820 for (
int j = 0; j < num_vars; ++j) {
2821 var_to_values[j].assign(new_domains[j].begin(), new_domains[j].end());
2823 std::vector<std::vector<int64_t>> all_tuples(prod);
2824 for (
int i = 0; i < prod; ++i) {
2825 all_tuples[i].resize(num_vars);
2827 for (
int j = 0; j < num_vars; ++j) {
2828 all_tuples[i][j] = var_to_values[j][
index % var_to_values[j].size()];
2829 index /= var_to_values[j].size();
2835 std::vector<std::vector<int64_t>> diff(prod - new_tuples.size());
2836 std::set_difference(all_tuples.begin(), all_tuples.end(),
2837 new_tuples.begin(), new_tuples.end(), diff.begin());
2840 ct->mutable_table()->set_negated(!
ct->table().negated());
2841 ct->mutable_table()->clear_values();
2842 for (
const std::vector<int64_t>& t : diff) {
2843 for (
const int64_t v : t)
ct->mutable_table()->add_values(v);
2847 return modified_variables;
2850 bool CpModelPresolver::PresolveAllDiff(ConstraintProto*
ct) {
2854 AllDifferentConstraintProto& all_diff = *
ct->mutable_all_diff();
2856 bool constraint_has_changed =
false;
2858 const int size = all_diff.vars_size();
2861 return RemoveConstraint(
ct);
2865 return RemoveConstraint(
ct);
2868 bool something_was_propagated =
false;
2869 std::vector<int> new_variables;
2870 for (
int i = 0; i < size; ++i) {
2871 if (!context_->
IsFixed(all_diff.vars(i))) {
2872 new_variables.push_back(all_diff.vars(i));
2876 const int64_t
value = context_->
MinOf(all_diff.vars(i));
2877 bool propagated =
false;
2878 for (
int j = 0; j < size; ++j) {
2879 if (i == j)
continue;
2882 Domain(
value).Complement())) {
2890 something_was_propagated =
true;
2894 std::sort(new_variables.begin(), new_variables.end());
2895 for (
int i = 1; i < new_variables.size(); ++i) {
2896 if (new_variables[i] == new_variables[i - 1]) {
2898 "Duplicate variable in all_diff");
2902 if (new_variables.size() < all_diff.vars_size()) {
2903 all_diff.mutable_vars()->Clear();
2904 for (
const int var : new_variables) {
2905 all_diff.add_vars(
var);
2908 something_was_propagated =
true;
2909 constraint_has_changed =
true;
2910 if (new_variables.size() <= 1)
continue;
2915 Domain domain = context_->
DomainOf(all_diff.vars(0));
2916 for (
int i = 1; i < all_diff.vars_size(); ++i) {
2919 if (all_diff.vars_size() == domain.Size()) {
2920 absl::flat_hash_map<int64_t, std::vector<int>> value_to_refs;
2921 for (
const int ref : all_diff.vars()) {
2924 value_to_refs[v].push_back(ref);
2928 bool propagated =
false;
2929 for (
const auto& it : value_to_refs) {
2930 if (it.second.size() == 1 &&
2932 const int ref = it.second.
front();
2941 "all_diff: propagated mandatory values in permutation");
2942 something_was_propagated =
true;
2945 if (!something_was_propagated)
break;
2948 return constraint_has_changed;
2955 std::vector<int> GetLiteralsFromSetPPCConstraint(
const ConstraintProto&
ct) {
2956 std::vector<int> sorted_literals;
2957 if (
ct.constraint_case() == ConstraintProto::kAtMostOne) {
2958 for (
const int literal :
ct.at_most_one().literals()) {
2959 sorted_literals.push_back(
literal);
2961 }
else if (
ct.constraint_case() == ConstraintProto::kBoolOr) {
2962 for (
const int literal :
ct.bool_or().literals()) {
2963 sorted_literals.push_back(
literal);
2965 }
else if (
ct.constraint_case() == ConstraintProto::kExactlyOne) {
2966 for (
const int literal :
ct.exactly_one().literals()) {
2967 sorted_literals.push_back(
literal);
2970 std::sort(sorted_literals.begin(), sorted_literals.end());
2971 return sorted_literals;
2976 void AddImplication(
int lhs,
int rhs, CpModelProto*
proto,
2977 absl::flat_hash_map<int, int>* ref_to_bool_and) {
2978 if (ref_to_bool_and->contains(lhs)) {
2979 const int ct_index = (*ref_to_bool_and)[lhs];
2980 proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(rhs);
2981 }
else if (ref_to_bool_and->contains(
NegatedRef(rhs))) {
2982 const int ct_index = (*ref_to_bool_and)[
NegatedRef(rhs)];
2983 proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(
2986 (*ref_to_bool_and)[lhs] =
proto->constraints_size();
2987 ConstraintProto*
ct =
proto->add_constraints();
2988 ct->add_enforcement_literal(lhs);
2989 ct->mutable_bool_and()->add_literals(rhs);
2993 template <
typename ClauseContainer>
2994 void ExtractClauses(
bool use_bool_and,
const ClauseContainer& container,
2995 CpModelProto*
proto) {
3002 absl::flat_hash_map<int, int> ref_to_bool_and;
3003 for (
int i = 0; i < container.NumClauses(); ++i) {
3004 const std::vector<Literal>& clause = container.Clause(i);
3005 if (clause.empty())
continue;
3008 if (use_bool_and && clause.size() == 2) {
3009 const int a = clause[0].IsPositive()
3010 ? clause[0].Variable().value()
3012 const int b = clause[1].IsPositive()
3013 ? clause[1].Variable().value()
3020 ConstraintProto*
ct =
proto->add_constraints();
3021 for (
const Literal l : clause) {
3022 if (l.IsPositive()) {
3023 ct->mutable_bool_or()->add_literals(l.Variable().value());
3025 ct->mutable_bool_or()->add_literals(
NegatedRef(l.Variable().value()));
3033 int64_t CpModelPresolver::StartMin(
3034 const IntervalConstraintProto&
interval)
const {
3039 int64_t CpModelPresolver::EndMax(
3040 const IntervalConstraintProto&
interval)
const {
3045 int64_t CpModelPresolver::SizeMin(
3046 const IntervalConstraintProto&
interval)
const {
3051 int64_t CpModelPresolver::SizeMax(
3052 const IntervalConstraintProto&
interval)
const {
3057 bool CpModelPresolver::PresolveNoOverlap(ConstraintProto*
ct) {
3059 const NoOverlapConstraintProto&
proto =
ct->no_overlap();
3060 const int initial_num_intervals =
proto.intervals_size();
3064 for (
int i = 0; i <
proto.intervals_size(); ++i) {
3065 const int interval_index =
proto.intervals(i);
3067 .constraint_case() ==
3068 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
3071 ct->mutable_no_overlap()->set_intervals(new_size++, interval_index);
3073 ct->mutable_no_overlap()->mutable_intervals()->Truncate(new_size);
3077 ct->mutable_no_overlap()->mutable_intervals()->begin(),
3078 ct->mutable_no_overlap()->mutable_intervals()->end(),
3079 [
this](
int i1,
int i2) {
3080 return StartMin(context_->working_model->constraints(i1).interval()) <
3081 StartMin(context_->working_model->constraints(i2).interval());
3090 for (
int i = 0; i <
proto.intervals_size(); ++i) {
3091 const int interval_index =
proto.intervals(i);
3092 const IntervalConstraintProto&
interval =
3093 context_->
working_model->constraints(interval_index).interval();
3094 const int64_t end_max_of_previous_intervals = end_max_so_far;
3096 if (StartMin(
interval) >= end_max_of_previous_intervals &&
3097 (i + 1 ==
proto.intervals_size() ||
3099 ->constraints(
proto.intervals(i + 1))
3104 ct->mutable_no_overlap()->set_intervals(new_size++, interval_index);
3106 ct->mutable_no_overlap()->mutable_intervals()->Truncate(new_size);
3108 if (
proto.intervals_size() == 1) {
3110 return RemoveConstraint(
ct);
3112 if (
proto.intervals().empty()) {
3114 return RemoveConstraint(
ct);
3117 return new_size < initial_num_intervals;
3120 bool CpModelPresolver::PresolveCumulative(ConstraintProto*
ct) {
3123 const CumulativeConstraintProto&
proto =
ct->cumulative();
3127 bool changed =
false;
3128 int num_zero_demand_removed = 0;
3129 int64_t sum_of_max_demands = 0;
3130 int64_t max_of_performed_demand_mins = 0;
3131 for (
int i = 0; i <
proto.intervals_size(); ++i) {
3132 const ConstraintProto& interval_ct =
3134 if (interval_ct.constraint_case() ==
3135 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
3139 const int demand_ref =
proto.demands(i);
3140 const int64_t demand_max = context_->
MaxOf(demand_ref);
3141 if (demand_max == 0) {
3142 num_zero_demand_removed++;
3145 sum_of_max_demands += demand_max;
3147 if (interval_ct.enforcement_literal().empty()) {
3148 max_of_performed_demand_mins =
3149 std::max(max_of_performed_demand_mins, context_->
MinOf(demand_ref));
3152 ct->mutable_cumulative()->set_intervals(new_size,
proto.intervals(i));
3153 ct->mutable_cumulative()->set_demands(new_size,
proto.demands(i));
3157 const int capacity_ref =
proto.capacity();
3158 if (max_of_performed_demand_mins > context_->
MinOf(capacity_ref)) {
3161 capacity_ref, Domain(max_of_performed_demand_mins,
3167 if (sum_of_max_demands <= context_->MinOf(capacity_ref)) {
3168 context_->
UpdateRuleStats(
"cumulative: capacity exceeds sum of demands");
3169 return RemoveConstraint(
ct);
3172 if (new_size <
proto.intervals_size()) {
3174 ct->mutable_cumulative()->mutable_intervals()->Truncate(new_size);
3175 ct->mutable_cumulative()->mutable_demands()->Truncate(new_size);
3178 if (num_zero_demand_removed > 0) {
3179 context_->
UpdateRuleStats(
"cumulative: removed intervals with no demands");
3182 if (new_size == 0) {
3184 return RemoveConstraint(
ct);
3189 const int num_intervals =
proto.intervals_size();
3190 const int64_t capacity_max = context_->
MaxOf(capacity_ref);
3192 bool with_start_view =
false;
3193 std::vector<int> start_refs(num_intervals, -1);
3195 int num_duration_one = 0;
3196 int num_greater_half_capacity = 0;
3198 bool has_optional_interval =
false;
3199 for (
int i = 0; i < num_intervals; ++i) {
3201 const ConstraintProto&
ct =
3203 if (!
ct.enforcement_literal().empty()) has_optional_interval =
true;
3204 const IntervalConstraintProto&
interval =
ct.interval();
3205 if (
interval.has_start_view()) with_start_view =
true;
3207 const int demand_ref =
proto.demands(i);
3216 const int64_t demand_min = context_->
MinOf(demand_ref);
3217 const int64_t demand_max = context_->
MaxOf(demand_ref);
3218 if (demand_min > capacity_max / 2) {
3219 num_greater_half_capacity++;
3221 if (demand_min > capacity_max) {
3222 context_->
UpdateRuleStats(
"cumulative: demand_min exceeds capacity max");
3223 if (
ct.enforcement_literal().empty()) {
3226 CHECK_EQ(
ct.enforcement_literal().size(), 1);
3232 }
else if (demand_max > capacity_max) {
3233 if (
ct.enforcement_literal().empty()) {
3235 "cumulative: demand_max exceeds capacity max.");
3245 "cumulative: demand_max of optional interval exceeds capacity.");
3250 if (num_greater_half_capacity == num_intervals) {
3251 if (num_duration_one == num_intervals && !has_optional_interval &&
3254 ConstraintProto* new_ct = context_->
working_model->add_constraints();
3255 auto* arg = new_ct->mutable_all_diff();
3256 for (
const int var : start_refs) arg->add_vars(
var);
3258 return RemoveConstraint(
ct);
3264 for (
int i = 0; i <
proto.demands_size(); ++i) {
3265 const int demand_ref =
proto.demands(i);
3266 const int64_t demand_max = context_->
MaxOf(demand_ref);
3267 if (demand_max > context_->
MinOf(capacity_ref)) {
3268 ConstraintProto* capacity_gt =
3272 .enforcement_literal()) {
3273 capacity_gt->add_enforcement_literal(
literal);
3275 capacity_gt->mutable_linear()->add_vars(capacity_ref);
3276 capacity_gt->mutable_linear()->add_coeffs(1);
3277 capacity_gt->mutable_linear()->add_vars(demand_ref);
3278 capacity_gt->mutable_linear()->add_coeffs(-1);
3279 capacity_gt->mutable_linear()->add_domain(0);
3280 capacity_gt->mutable_linear()->add_domain(
3286 ConstraintProto* new_ct = context_->
working_model->add_constraints();
3287 auto* arg = new_ct->mutable_no_overlap();
3292 return RemoveConstraint(
ct);
3299 bool CpModelPresolver::PresolveRoutes(ConstraintProto*
ct) {
3302 RoutesConstraintProto&
proto = *
ct->mutable_routes();
3305 const int num_arcs =
proto.literals_size();
3306 for (
int i = 0; i < num_arcs; ++i) {
3307 const int ref =
proto.literals(i);
3314 proto.set_literals(new_size, ref);
3319 if (new_size < num_arcs) {
3320 proto.mutable_literals()->Truncate(new_size);
3321 proto.mutable_tails()->Truncate(new_size);
3322 proto.mutable_heads()->Truncate(new_size);
3328 bool CpModelPresolver::PresolveCircuit(ConstraintProto*
ct) {
3331 CircuitConstraintProto&
proto = *
ct->mutable_circuit();
3335 ct->mutable_circuit()->mutable_heads());
3339 std::vector<std::vector<int>> incoming_arcs;
3340 std::vector<std::vector<int>> outgoing_arcs;
3342 const int num_arcs =
proto.literals_size();
3343 for (
int i = 0; i < num_arcs; ++i) {
3344 const int ref =
proto.literals(i);
3352 incoming_arcs[
head].push_back(ref);
3353 outgoing_arcs[
tail].push_back(ref);
3361 bool loop_again =
true;
3362 int num_fixed_at_true = 0;
3363 while (loop_again) {
3365 for (
const auto* node_to_refs : {&incoming_arcs, &outgoing_arcs}) {
3366 for (
const std::vector<int>& refs : *node_to_refs) {
3367 if (refs.size() == 1) {
3369 ++num_fixed_at_true;
3378 for (
const int ref : refs) {
3388 if (num_true == 1) {
3389 for (
const int ref : refs) {
3390 if (ref != true_ref) {
3391 if (!context_->
IsFixed(ref)) {
3402 if (num_fixed_at_true > 0) {
3409 int circuit_start = -1;
3410 std::vector<int>
next(num_nodes, -1);
3411 std::vector<int> new_in_degree(num_nodes, 0);
3412 std::vector<int> new_out_degree(num_nodes, 0);
3413 for (
int i = 0; i < num_arcs; ++i) {
3414 const int ref =
proto.literals(i);
3422 circuit_start =
proto.tails(i);
3426 ++new_out_degree[
proto.tails(i)];
3427 ++new_in_degree[
proto.heads(i)];
3430 proto.set_literals(new_size,
proto.literals(i));
3440 for (
int i = 0; i < num_nodes; ++i) {
3442 if (incoming_arcs[i].empty() && outgoing_arcs[i].empty())
continue;
3444 if (new_in_degree[i] == 0 || new_out_degree[i] == 0) {
3450 if (circuit_start != -1) {
3451 std::vector<bool> visited(num_nodes,
false);
3452 int current = circuit_start;
3453 while (current != -1 && !visited[current]) {
3454 visited[current] =
true;
3455 current =
next[current];
3457 if (current == circuit_start) {
3460 for (
int i = 0; i < num_arcs; ++i) {
3461 if (visited[
proto.tails(i)])
continue;
3469 return RemoveConstraint(
ct);
3473 if (num_true == new_size) {
3475 return RemoveConstraint(
ct);
3481 for (
int i = 0; i < num_nodes; ++i) {
3482 for (
const std::vector<int>* arc_literals :
3483 {&incoming_arcs[i], &outgoing_arcs[i]}) {
3484 std::vector<int> literals;
3485 for (
const int ref : *arc_literals) {
3491 literals.push_back(ref);
3493 if (literals.size() == 2 && literals[0] !=
NegatedRef(literals[1])) {
3502 if (new_size < num_arcs) {
3503 proto.mutable_tails()->Truncate(new_size);
3504 proto.mutable_heads()->Truncate(new_size);
3505 proto.mutable_literals()->Truncate(new_size);
3512 bool CpModelPresolver::PresolveAutomaton(ConstraintProto*
ct) {
3515 AutomatonConstraintProto&
proto = *
ct->mutable_automaton();
3516 if (
proto.vars_size() == 0 ||
proto.transition_label_size() == 0) {
3520 bool all_affine =
true;
3521 std::vector<AffineRelation::Relation> affine_relations;
3522 for (
int v = 0; v <
proto.vars_size(); ++v) {
3523 const int var =
ct->automaton().vars(v);
3525 affine_relations.push_back(r);
3526 if (r.representative ==
var) {
3530 if (v > 0 && (r.coeff != affine_relations[v - 1].coeff ||
3531 r.offset != affine_relations[v - 1].offset)) {
3538 for (
int v = 0; v <
proto.vars_size(); ++v) {
3541 const AffineRelation::Relation rep = affine_relations.front();
3543 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
3544 const int64_t label =
proto.transition_label(t);
3545 int64_t inverse_label = (label - rep.offset) / rep.coeff;
3546 if (inverse_label * rep.coeff + rep.offset == label) {
3547 if (new_size != t) {
3548 proto.set_transition_tail(new_size,
proto.transition_tail(t));
3549 proto.set_transition_head(new_size,
proto.transition_head(t));
3551 proto.set_transition_label(new_size, inverse_label);
3555 if (new_size <
proto.transition_tail_size()) {
3556 proto.mutable_transition_tail()->Truncate(new_size);
3557 proto.mutable_transition_label()->Truncate(new_size);
3558 proto.mutable_transition_head()->Truncate(new_size);
3566 for (
int v = 1; v <
proto.vars_size(); ++v) {
3571 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
3572 const int64_t label =
proto.transition_label(t);
3573 if (hull.Contains(label)) {
3574 if (new_size != t) {
3575 proto.set_transition_tail(new_size,
proto.transition_tail(t));
3576 proto.set_transition_label(new_size, label);
3577 proto.set_transition_head(new_size,
proto.transition_head(t));
3582 if (new_size <
proto.transition_tail_size()) {
3583 proto.mutable_transition_tail()->Truncate(new_size);
3584 proto.mutable_transition_label()->Truncate(new_size);
3585 proto.mutable_transition_head()->Truncate(new_size);
3590 const int n =
proto.vars_size();
3591 const std::vector<int> vars = {
proto.vars().begin(),
proto.vars().end()};
3594 std::vector<std::set<int64_t>> reachable_states(n + 1);
3595 reachable_states[0].insert(
proto.starting_state());
3596 reachable_states[n] = {
proto.final_states().begin(),
3597 proto.final_states().end()};
3604 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
3605 const int64_t
tail =
proto.transition_tail(t);
3606 const int64_t label =
proto.transition_label(t);
3607 const int64_t
head =
proto.transition_head(t);
3610 reachable_states[
time + 1].insert(
head);
3614 std::vector<std::set<int64_t>> reached_values(n);
3618 std::set<int64_t> new_set;
3619 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
3620 const int64_t
tail =
proto.transition_tail(t);
3621 const int64_t label =
proto.transition_label(t);
3622 const int64_t
head =
proto.transition_head(t);
3627 new_set.insert(
tail);
3628 reached_values[
time].insert(label);
3630 reachable_states[
time].swap(new_set);
3633 bool removed_values =
false;
3638 {reached_values[time].begin(), reached_values[time].end()}),
3643 if (removed_values) {
3649 bool CpModelPresolver::PresolveReservoir(ConstraintProto*
ct) {
3653 bool changed =
false;
3655 ReservoirConstraintProto& mutable_reservoir = *
ct->mutable_reservoir();
3656 if (mutable_reservoir.actives().empty()) {
3658 for (
int i = 0; i < mutable_reservoir.times_size(); ++i) {
3659 mutable_reservoir.add_actives(true_literal);
3664 const auto& demand_is_null = [&](
int i) {
3665 return mutable_reservoir.demands(i) == 0 ||
3671 for (
int i = 0; i < mutable_reservoir.demands_size(); ++i) {
3672 if (demand_is_null(i)) num_zeros++;
3675 if (num_zeros > 0) {
3678 for (
int i = 0; i < mutable_reservoir.demands_size(); ++i) {
3679 if (demand_is_null(i))
continue;
3680 mutable_reservoir.set_demands(new_size, mutable_reservoir.demands(i));
3681 mutable_reservoir.set_times(new_size, mutable_reservoir.times(i));
3682 mutable_reservoir.set_actives(new_size, mutable_reservoir.actives(i));
3686 mutable_reservoir.mutable_demands()->Truncate(new_size);
3687 mutable_reservoir.mutable_times()->Truncate(new_size);
3688 mutable_reservoir.mutable_actives()->Truncate(new_size);
3691 "reservoir: remove zero demands or inactive events.");
3694 const int num_events = mutable_reservoir.demands_size();
3695 int64_t gcd = mutable_reservoir.demands().empty()
3697 : std::abs(mutable_reservoir.demands(0));
3698 int num_positives = 0;
3699 int num_negatives = 0;
3700 int64_t sum_of_demands = 0;
3701 int64_t max_sum_of_positive_demands = 0;
3702 int64_t min_sum_of_negative_demands = 0;
3703 for (
int i = 0; i < num_events; ++i) {
3704 const int64_t
demand = mutable_reservoir.demands(i);
3705 sum_of_demands +=
demand;
3709 max_sum_of_positive_demands +=
demand;
3713 min_sum_of_negative_demands +=
demand;
3717 if (min_sum_of_negative_demands >= mutable_reservoir.min_level() &&
3718 max_sum_of_positive_demands <= mutable_reservoir.max_level()) {
3720 return RemoveConstraint(
ct);
3723 if (min_sum_of_negative_demands > mutable_reservoir.max_level() ||
3724 max_sum_of_positive_demands < mutable_reservoir.min_level()) {
3729 if (min_sum_of_negative_demands > mutable_reservoir.min_level()) {
3730 mutable_reservoir.set_min_level(min_sum_of_negative_demands);
3732 "reservoir: increase min_level to reachable value");
3735 if (max_sum_of_positive_demands < mutable_reservoir.max_level()) {
3736 mutable_reservoir.set_max_level(max_sum_of_positive_demands);
3737 context_->
UpdateRuleStats(
"reservoir: reduce max_level to reachable value");
3740 if (mutable_reservoir.min_level() <= 0 &&
3741 mutable_reservoir.max_level() >= 0 &&
3742 (num_positives == 0 || num_negatives == 0)) {
3746 context_->
working_model->add_constraints()->mutable_linear();
3747 int64_t fixed_contrib = 0;
3748 for (
int i = 0; i < mutable_reservoir.demands_size(); ++i) {
3749 const int64_t
demand = mutable_reservoir.demands(i);
3752 const int active = mutable_reservoir.actives(i);
3754 sum->add_vars(active);
3758 sum->add_coeffs(-
demand);
3762 sum->add_domain(mutable_reservoir.min_level() - fixed_contrib);
3763 sum->add_domain(mutable_reservoir.max_level() - fixed_contrib);
3765 return RemoveConstraint(
ct);
3769 for (
int i = 0; i < mutable_reservoir.demands_size(); ++i) {
3770 mutable_reservoir.set_demands(i, mutable_reservoir.demands(i) / gcd);
3776 const Domain reduced_domain =
3777 Domain({mutable_reservoir.min_level(), mutable_reservoir.max_level()})
3778 .InverseMultiplicationBy(gcd);
3779 mutable_reservoir.set_min_level(reduced_domain.Min());
3780 mutable_reservoir.set_max_level(reduced_domain.Max());
3781 context_->
UpdateRuleStats(
"reservoir: simplify demands and levels by gcd.");
3784 if (num_positives == 1 && num_negatives > 0) {
3786 "TODO reservoir: one producer, multiple consumers.");
3789 absl::flat_hash_set<std::pair<int, int>> time_active_set;
3790 for (
int i = 0; i < mutable_reservoir.demands_size(); ++i) {
3791 const std::pair<int, int> key = std::make_pair(
3792 mutable_reservoir.times(i), mutable_reservoir.actives(i));
3793 if (time_active_set.contains(key)) {
3794 context_->
UpdateRuleStats(
"TODO reservoir: merge synchronized events.");
3797 time_active_set.insert(key);
3807 void CpModelPresolver::ExtractBoolAnd() {
3808 absl::flat_hash_map<int, int> ref_to_bool_and;
3809 const int num_constraints = context_->
working_model->constraints_size();
3810 std::vector<int> to_remove;
3811 for (
int c = 0; c < num_constraints; ++c) {
3815 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr &&
3816 ct.bool_or().literals().size() == 2) {
3820 to_remove.push_back(c);
3824 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne &&
3825 ct.at_most_one().literals().size() == 2) {
3826 AddImplication(
ct.at_most_one().literals(0),
3829 to_remove.push_back(c);
3835 for (
const int c : to_remove) {
3836 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
3842 void CpModelPresolver::Probe() {
3846 for (
int i = 0; i < context_->
working_model->variables_size(); ++i) {
3867 auto* local_param =
model.GetOrCreate<SatParameters>();
3868 *local_param = context_->
params();
3869 local_param->set_use_implied_bounds(
false);
3871 model.GetOrCreate<TimeLimit>()->MergeWithGlobalTimeLimit(
3873 model.Register<ModelRandomGenerator>(context_->
random());
3874 auto* encoder =
model.GetOrCreate<IntegerEncoder>();
3875 encoder->DisableImplicationBetweenLiteral();
3876 auto* mapping =
model.GetOrCreate<CpModelMapping>();
3884 auto* sat_solver =
model.GetOrCreate<SatSolver>();
3885 for (
const ConstraintProto&
ct :
model_proto.constraints()) {
3886 if (mapping->ConstraintIsAlreadyLoaded(&
ct))
continue;
3888 if (sat_solver->IsModelUnsat()) {
3890 "after loading constraint during probing ",
ct.ShortDebugString()));
3893 encoder->AddAllImplicationsBetweenAssociatedLiterals();
3894 if (!sat_solver->Propagate()) {
3896 "during probing initial propagation");
3903 auto* implication_graph =
model.GetOrCreate<BinaryImplicationGraph>();
3904 auto* prober =
model.GetOrCreate<Prober>();
3905 prober->ProbeBooleanVariables(1.0);
3907 model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
3908 if (sat_solver->IsModelUnsat() || !implication_graph->DetectEquivalences()) {
3913 CHECK_EQ(sat_solver->CurrentDecisionLevel(), 0);
3914 for (
int i = 0; i < sat_solver->LiteralTrail().
Index(); ++i) {
3915 const Literal l = sat_solver->LiteralTrail()[i];
3916 const int var = mapping->GetProtoVariableFromBooleanVariable(l.Variable());
3923 const int num_variables = context_->
working_model->variables().size();
3924 auto* integer_trail =
model.GetOrCreate<IntegerTrail>();
3925 for (
int var = 0;
var < num_variables; ++
var) {
3928 if (!mapping->IsBoolean(
var)) {
3931 integer_trail->InitialVariableDomain(mapping->Integer(
var)))) {
3938 const Literal l = mapping->Literal(
var);
3939 const Literal r = implication_graph->RepresentativeOf(l);
3942 mapping->GetProtoVariableFromBooleanVariable(r.Variable());
3952 void CpModelPresolver::PresolvePureSatPart() {
3957 const int num_variables = context_->
working_model->variables_size();
3958 SatPostsolver sat_postsolver(num_variables);
3959 SatPresolver sat_presolver(&sat_postsolver, logger_);
3960 sat_presolver.SetNumVariables(num_variables);
3961 sat_presolver.SetTimeLimit(context_->
time_limit());
3963 SatParameters params = context_->
params();
3970 if (params.cp_model_postsolve_with_full_solver()) {
3971 params.set_presolve_blocked_clause(
false);
3977 params.set_presolve_use_bva(
false);
3978 sat_presolver.SetParameters(params);
3981 absl::flat_hash_set<int> used_variables;
3982 auto convert = [&used_variables](
int ref) {
3984 if (
RefIsPositive(ref))
return Literal(BooleanVariable(ref),
true);
3985 return Literal(BooleanVariable(
NegatedRef(ref)),
false);
3993 for (
int c = 0; c < context_->
working_model->constraints_size(); ++c) {
3995 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr ||
3996 ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolAnd) {
4015 std::vector<Literal> clause;
4016 int num_removed_constraints = 0;
4017 for (
int i = 0; i < context_->
working_model->constraints_size(); ++i) {
4020 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr) {
4021 ++num_removed_constraints;
4023 for (
const int ref :
ct.bool_or().literals()) {
4024 clause.push_back(convert(ref));
4026 for (
const int ref :
ct.enforcement_literal()) {
4027 clause.push_back(convert(ref).Negated());
4029 sat_presolver.AddClause(clause);
4036 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolAnd) {
4037 ++num_removed_constraints;
4038 std::vector<Literal> clause;
4039 for (
const int ref :
ct.enforcement_literal()) {
4040 clause.push_back(convert(ref).Negated());
4043 for (
const int ref :
ct.bool_and().literals()) {
4044 clause.back() = convert(ref);
4045 sat_presolver.AddClause(clause);
4055 if (num_removed_constraints == 0)
return;
4065 std::vector<bool> can_be_removed(num_variables,
false);
4066 for (
int i = 0; i < num_variables; ++i) {
4068 can_be_removed[i] =
true;
4074 if (used_variables.contains(i) && context_->
IsFixed(i)) {
4076 sat_presolver.AddClause({convert(i)});
4078 sat_presolver.AddClause({convert(
NegatedRef(i))});
4086 const int num_passes = params.presolve_use_bva() ? 4 : 1;
4087 for (
int i = 0; i < num_passes; ++i) {
4088 const int old_num_clause = sat_postsolver.NumClauses();
4089 if (!sat_presolver.Presolve(can_be_removed)) {
4090 VLOG(1) <<
"UNSAT during SAT presolve.";
4093 if (old_num_clause == sat_postsolver.NumClauses())
break;
4097 const int new_num_variables = sat_presolver.NumVariables();
4098 if (new_num_variables > context_->
working_model->variables_size()) {
4099 VLOG(1) <<
"New variables added by the SAT presolver.";
4101 i < new_num_variables; ++i) {
4102 IntegerVariableProto* var_proto =
4104 var_proto->add_domain(0);
4105 var_proto->add_domain(1);
4111 ExtractClauses(
true, sat_presolver, context_->
working_model);
4119 ExtractClauses(
false, sat_postsolver,
4127 void CpModelPresolver::ExpandObjective() {
4146 int unique_expanded_constraint = -1;
4147 const bool objective_was_a_single_variable =
4152 const int num_variables = context_->
working_model->variables_size();
4153 const int num_constraints = context_->
working_model->constraints_size();
4154 absl::flat_hash_set<int> relevant_constraints;
4155 std::vector<int> var_to_num_relevant_constraints(num_variables, 0);
4156 for (
int ct_index = 0; ct_index < num_constraints; ++ct_index) {
4157 const ConstraintProto&
ct = context_->
working_model->constraints(ct_index);
4159 if (!
ct.enforcement_literal().empty() ||
4160 ct.constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
4161 ct.linear().domain().size() != 2 ||
4162 ct.linear().domain(0) !=
ct.linear().domain(1)) {
4166 relevant_constraints.insert(ct_index);
4167 const int num_terms =
ct.linear().vars_size();
4168 for (
int i = 0; i < num_terms; ++i) {
4169 var_to_num_relevant_constraints[
PositiveRef(
ct.linear().vars(i))]++;
4173 std::set<int> var_to_process;
4175 const int var = entry.first;
4177 if (var_to_num_relevant_constraints[
var] != 0) {
4178 var_to_process.insert(
var);
4183 int num_expansions = 0;
4184 absl::flat_hash_set<int> processed_vars;
4185 std::vector<int> new_vars_in_objective;
4186 while (!relevant_constraints.empty()) {
4188 int objective_var = -1;
4189 while (!var_to_process.empty()) {
4190 const int var = *var_to_process.begin();
4191 CHECK(!processed_vars.contains(
var));
4192 if (var_to_num_relevant_constraints[
var] == 0) {
4193 processed_vars.insert(
var);
4194 var_to_process.erase(
var);
4199 var_to_process.erase(
var);
4202 objective_var =
var;
4206 if (objective_var == -1)
break;
4208 processed_vars.insert(objective_var);
4209 var_to_process.erase(objective_var);
4211 int expanded_linear_index = -1;
4212 int64_t objective_coeff_in_expanded_constraint;
4213 int64_t size_of_expanded_constraint = 0;
4214 const auto& non_deterministic_list =
4216 std::vector<int> constraints_with_objective(non_deterministic_list.begin(),
4217 non_deterministic_list.end());
4218 std::sort(constraints_with_objective.begin(),
4219 constraints_with_objective.end());
4220 for (
const int ct_index : constraints_with_objective) {
4221 if (relevant_constraints.count(ct_index) == 0)
continue;
4222 const ConstraintProto&
ct =
4227 relevant_constraints.erase(ct_index);
4228 const int num_terms =
ct.linear().vars_size();
4229 for (
int i = 0; i < num_terms; ++i) {
4230 var_to_num_relevant_constraints[
PositiveRef(
ct.linear().vars(i))]--;
4242 bool is_present =
false;
4243 int64_t objective_coeff;
4244 for (
int i = 0; i < num_terms; ++i) {
4245 const int ref =
ct.linear().vars(i);
4246 const int64_t coeff =
ct.linear().coeffs(i);
4248 CHECK(!is_present) <<
"Duplicate variables not supported.";
4250 objective_coeff = (ref == objective_var) ? coeff : -coeff;
4263 if (std::abs(objective_coeff) == 1 &&
4264 num_terms > size_of_expanded_constraint) {
4265 expanded_linear_index = ct_index;
4266 size_of_expanded_constraint = num_terms;
4267 objective_coeff_in_expanded_constraint = objective_coeff;
4271 if (expanded_linear_index != -1) {
4272 context_->
UpdateRuleStats(
"objective: expanded objective constraint.");
4276 CHECK_EQ(std::abs(objective_coeff_in_expanded_constraint), 1);
4277 const ConstraintProto&
ct =
4278 context_->
working_model->constraints(expanded_linear_index);
4280 objective_var, objective_coeff_in_expanded_constraint,
ct,
4281 &new_vars_in_objective);
4284 for (
const int var : new_vars_in_objective) {
4285 if (!processed_vars.contains(
var)) var_to_process.insert(
var);
4298 for (
int i = 0; i < size_of_expanded_constraint; ++i) {
4299 const int ref =
ct.linear().vars(i);
4304 -
ct.linear().coeffs(i)))
4305 .RelaxIfTooComplex();
4307 implied_domain = implied_domain.InverseMultiplicationBy(
4308 objective_coeff_in_expanded_constraint);
4312 if (implied_domain.IsIncludedIn(context_->
DomainOf(objective_var))) {
4313 context_->
UpdateRuleStats(
"objective: removed objective constraint.");
4315 context_->
working_model->mutable_constraints(expanded_linear_index)
4319 unique_expanded_constraint = expanded_linear_index;
4329 if (num_expansions == 1 && objective_was_a_single_variable &&
4330 unique_expanded_constraint != -1) {
4332 "objective: removed unique objective constraint.");
4333 ConstraintProto* mutable_ct = context_->
working_model->mutable_constraints(
4334 unique_expanded_constraint);
4335 *(context_->
mapping_model->add_constraints()) = *mutable_ct;
4336 mutable_ct->Clear();
4348 void CpModelPresolver::MergeNoOverlapConstraints() {
4351 const int num_constraints = context_->
working_model->constraints_size();
4352 int old_num_no_overlaps = 0;
4353 int old_num_intervals = 0;
4356 std::vector<int> disjunctive_index;
4357 std::vector<std::vector<Literal>> cliques;
4358 for (
int c = 0; c < num_constraints; ++c) {
4360 if (
ct.constraint_case() != ConstraintProto::ConstraintCase::kNoOverlap) {
4363 std::vector<Literal> clique;
4364 for (
const int i :
ct.no_overlap().intervals()) {
4365 clique.push_back(Literal(BooleanVariable(i),
true));
4367 cliques.push_back(clique);
4368 disjunctive_index.push_back(c);
4370 old_num_no_overlaps++;
4371 old_num_intervals += clique.size();
4373 if (old_num_no_overlaps == 0)
return;
4377 local_model.GetOrCreate<Trail>()->Resize(num_constraints);
4378 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
4379 graph->Resize(num_constraints);
4380 for (
const std::vector<Literal>& clique : cliques) {
4383 CHECK(graph->AddAtMostOne(clique));
4385 CHECK(graph->DetectEquivalences());
4386 graph->TransformIntoMaxCliques(
4387 &cliques, context_->
params().merge_no_overlap_work_limit());
4390 int new_num_no_overlaps = 0;
4391 int new_num_intervals = 0;
4392 for (
int i = 0; i < cliques.size(); ++i) {
4393 const int ct_index = disjunctive_index[i];
4394 ConstraintProto*
ct =
4397 if (cliques[i].empty())
continue;
4398 for (
const Literal l : cliques[i]) {
4399 CHECK(l.IsPositive());
4400 ct->mutable_no_overlap()->add_intervals(l.Variable().value());
4402 new_num_no_overlaps++;
4403 new_num_intervals += cliques[i].size();
4405 if (old_num_intervals != new_num_intervals ||
4406 old_num_no_overlaps != new_num_no_overlaps) {
4407 VLOG(1) << absl::StrCat(
"Merged ", old_num_no_overlaps,
" no-overlaps (",
4408 old_num_intervals,
" intervals) into ",
4409 new_num_no_overlaps,
" no-overlaps (",
4410 new_num_intervals,
" intervals).");
4419 void CpModelPresolver::TransformIntoMaxCliques() {
4422 auto convert = [](
int ref) {
4423 if (
RefIsPositive(ref))
return Literal(BooleanVariable(ref),
true);
4424 return Literal(BooleanVariable(
NegatedRef(ref)),
false);
4426 const int num_constraints = context_->
working_model->constraints_size();
4429 std::vector<std::vector<Literal>> cliques;
4431 for (
int c = 0; c < num_constraints; ++c) {
4432 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
4433 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne) {
4434 std::vector<Literal> clique;
4435 for (
const int ref :
ct->at_most_one().literals()) {
4436 clique.push_back(convert(ref));
4438 cliques.push_back(clique);
4439 if (RemoveConstraint(
ct)) {
4442 }
else if (
ct->constraint_case() ==
4443 ConstraintProto::ConstraintCase::kBoolAnd) {
4444 if (
ct->enforcement_literal().size() != 1)
continue;
4445 const Literal enforcement = convert(
ct->enforcement_literal(0));
4446 for (
const int ref :
ct->bool_and().literals()) {
4447 if (ref ==
ct->enforcement_literal(0))
continue;
4448 cliques.push_back({enforcement, convert(ref).Negated()});
4450 if (RemoveConstraint(
ct)) {
4456 int64_t num_literals_before = 0;
4457 const int num_old_cliques = cliques.size();
4461 const int num_variables = context_->
working_model->variables().size();
4462 local_model.GetOrCreate<Trail>()->Resize(num_variables);
4463 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
4464 graph->Resize(num_variables);
4465 for (
const std::vector<Literal>& clique : cliques) {
4466 num_literals_before += clique.size();
4467 if (!graph->AddAtMostOne(clique)) {
4471 if (!graph->DetectEquivalences()) {
4474 graph->TransformIntoMaxCliques(
4475 &cliques, context_->
params().merge_at_most_one_work_limit());
4480 for (
int var = 0;
var < num_variables; ++
var) {
4481 const Literal l = Literal(BooleanVariable(
var),
true);
4482 if (graph->RepresentativeOf(l) != l) {
4483 const Literal r = graph->RepresentativeOf(l);
4485 var, r.IsPositive() ? r.Variable().value()
4490 int num_new_cliques = 0;
4491 int64_t num_literals_after = 0;
4492 for (
const std::vector<Literal>& clique : cliques) {
4493 if (clique.empty())
continue;
4495 num_literals_after += clique.size();
4497 for (
const Literal
literal : clique) {
4499 ct->mutable_at_most_one()->add_literals(
literal.Variable().value());
4501 ct->mutable_at_most_one()->add_literals(
4507 PresolveAtMostOne(
ct);
4510 if (num_new_cliques != num_old_cliques) {
4511 context_->
UpdateRuleStats(
"at_most_one: transformed into max clique.");
4514 if (num_old_cliques != num_new_cliques ||
4515 num_literals_before != num_literals_after) {
4516 SOLVER_LOG(logger_,
"[MaxClique] Merged ", num_old_cliques,
"(",
4517 num_literals_before,
" literals) into ", num_new_cliques,
"(",
4518 num_literals_after,
" literals) at_most_ones.");
4524 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
4527 if (ExploitEquivalenceRelations(c,
ct)) {
4532 if (PresolveEnforcementLiteral(
ct)) {
4537 switch (
ct->constraint_case()) {
4538 case ConstraintProto::ConstraintCase::kBoolOr:
4539 return PresolveBoolOr(
ct);
4540 case ConstraintProto::ConstraintCase::kBoolAnd:
4541 return PresolveBoolAnd(
ct);
4542 case ConstraintProto::ConstraintCase::kAtMostOne:
4543 return PresolveAtMostOne(
ct);
4544 case ConstraintProto::ConstraintCase::kExactlyOne:
4545 return PresolveExactlyOne(
ct);
4546 case ConstraintProto::ConstraintCase::kBoolXor:
4547 return PresolveBoolXor(
ct);
4548 case ConstraintProto::ConstraintCase::kIntMax:
4549 if (
ct->int_max().vars_size() == 2 &&
4551 return PresolveIntAbs(
ct);
4553 return PresolveIntMax(
ct);
4555 case ConstraintProto::ConstraintCase::kIntMin:
4556 return PresolveIntMin(
ct);
4557 case ConstraintProto::ConstraintCase::kLinMax:
4558 return PresolveLinMax(
ct);
4559 case ConstraintProto::ConstraintCase::kLinMin:
4560 return PresolveLinMin(
ct);
4561 case ConstraintProto::ConstraintCase::kIntProd:
4562 return PresolveIntProd(
ct);
4563 case ConstraintProto::ConstraintCase::kIntDiv:
4564 return PresolveIntDiv(
ct);
4565 case ConstraintProto::ConstraintCase::kLinear: {
4566 if (CanonicalizeLinear(
ct)) {
4569 if (PresolveSmallLinear(
ct)) {
4572 if (PropagateDomainsInLinear(c,
ct)) {
4575 if (PresolveSmallLinear(
ct)) {
4579 if (RemoveSingletonInLinear(
ct)) {
4584 if (PresolveSmallLinear(
ct)) {
4588 if (PresolveLinearOnBooleans(
ct)) {
4591 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kLinear) {
4592 const int old_num_enforcement_literals =
ct->enforcement_literal_size();
4593 ExtractEnforcementLiteralFromLinearConstraint(c,
ct);
4594 if (
ct->constraint_case() ==
4595 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
4599 if (
ct->enforcement_literal_size() > old_num_enforcement_literals &&
4600 PresolveSmallLinear(
ct)) {
4603 PresolveLinearEqualityModuloTwo(
ct);
4607 case ConstraintProto::ConstraintCase::kInterval:
4608 return PresolveInterval(c,
ct);
4609 case ConstraintProto::ConstraintCase::kElement:
4610 return PresolveElement(
ct);
4611 case ConstraintProto::ConstraintCase::kTable:
4612 return PresolveTable(
ct);
4613 case ConstraintProto::ConstraintCase::kAllDiff:
4614 return PresolveAllDiff(
ct);
4615 case ConstraintProto::ConstraintCase::kNoOverlap:
4616 return PresolveNoOverlap(
ct);
4617 case ConstraintProto::ConstraintCase::kCumulative:
4618 return PresolveCumulative(
ct);
4619 case ConstraintProto::ConstraintCase::kCircuit:
4620 return PresolveCircuit(
ct);
4621 case ConstraintProto::ConstraintCase::kRoutes:
4622 return PresolveRoutes(
ct);
4623 case ConstraintProto::ConstraintCase::kAutomaton:
4624 return PresolveAutomaton(
ct);
4625 case ConstraintProto::ConstraintCase::kReservoir:
4626 return PresolveReservoir(
ct);
4635 bool CpModelPresolver::ProcessSetPPCSubset(
4636 int c1,
int c2,
const std::vector<int>& c2_minus_c1,
4637 const std::vector<int>& original_constraint_index,
4638 std::vector<bool>* marked_for_removal) {
4641 CHECK(!(*marked_for_removal)[c1]);
4642 CHECK(!(*marked_for_removal)[c2]);
4644 ConstraintProto* ct1 = context_->
working_model->mutable_constraints(
4645 original_constraint_index[c1]);
4646 ConstraintProto* ct2 = context_->
working_model->mutable_constraints(
4647 original_constraint_index[c2]);
4649 if ((ct1->constraint_case() == ConstraintProto::kBoolOr ||
4650 ct1->constraint_case() == ConstraintProto::kExactlyOne) &&
4651 (ct2->constraint_case() == ConstraintProto::kAtMostOne ||
4652 ct2->constraint_case() == ConstraintProto::kExactlyOne)) {
4657 for (
const int literal : c2_minus_c1) {
4663 if (ct2->constraint_case() != ConstraintProto::kExactlyOne) {
4664 ConstraintProto copy = *ct2;
4665 (*ct2->mutable_exactly_one()->mutable_literals()) =
4666 copy.at_most_one().literals();
4670 (*marked_for_removal)[c1] =
true;
4676 if ((ct1->constraint_case() == ConstraintProto::kBoolOr ||
4677 ct1->constraint_case() == ConstraintProto::kExactlyOne) &&
4678 ct2->constraint_case() == ConstraintProto::kBoolOr) {
4681 (*marked_for_removal)[c2] =
true;
4687 if (ct1->constraint_case() == ConstraintProto::kAtMostOne &&
4688 (ct2->constraint_case() == ConstraintProto::kAtMostOne ||
4689 ct2->constraint_case() == ConstraintProto::kExactlyOne)) {
4691 (*marked_for_removal)[c1] =
true;
4707 bool CpModelPresolver::ProcessSetPPC() {
4708 const int num_constraints = context_->
working_model->constraints_size();
4712 std::vector<uint64_t> signatures;
4716 std::vector<std::vector<int>> constraint_literals;
4720 std::vector<std::vector<int>> literals_to_constraints;
4723 std::vector<bool> removed;
4727 std::vector<int> original_constraint_index;
4731 int num_setppc_constraints = 0;
4732 for (
int c = 0; c < num_constraints; ++c) {
4733 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
4734 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kBoolOr ||
4735 ct->constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne ||
4736 ct->constraint_case() == ConstraintProto::ConstraintCase::kExactlyOne) {
4745 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kBoolOr ||
4746 ct->constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne ||
4747 ct->constraint_case() == ConstraintProto::ConstraintCase::kExactlyOne) {
4748 constraint_literals.push_back(GetLiteralsFromSetPPCConstraint(*
ct));
4750 uint64_t signature = 0;
4751 for (
const int literal : constraint_literals.back()) {
4753 signature |= (int64_t{1} << (positive_literal % 64));
4755 if (positive_literal >= literals_to_constraints.size()) {
4756 literals_to_constraints.resize(positive_literal + 1);
4758 literals_to_constraints[positive_literal].push_back(
4759 num_setppc_constraints);
4761 signatures.push_back(signature);
4762 removed.push_back(
false);
4763 original_constraint_index.push_back(c);
4764 num_setppc_constraints++;
4767 VLOG(1) <<
"#setppc constraints: " << num_setppc_constraints;
4770 absl::flat_hash_set<std::pair<int, int>> compared_constraints;
4771 for (
const std::vector<int>& literal_to_constraints :
4772 literals_to_constraints) {
4773 for (
int index1 = 0; index1 < literal_to_constraints.size(); ++index1) {
4776 const int c1 = literal_to_constraints[index1];
4777 if (removed[c1])
continue;
4778 const std::vector<int>& c1_literals = constraint_literals[c1];
4780 for (
int index2 = index1 + 1; index2 < literal_to_constraints.size();
4782 const int c2 = literal_to_constraints[index2];
4783 if (removed[c2])
continue;
4784 if (removed[c1])
break;
4787 if (c1 == c2)
continue;
4791 std::pair<int, int>(c1, c2))) {
4794 compared_constraints.insert({c1, c2});
4798 if (compared_constraints.size() >= 50000)
return true;
4800 const bool smaller = (signatures[c1] & ~signatures[c2]) == 0;
4801 const bool larger = (signatures[c2] & ~signatures[c1]) == 0;
4802 if (!(smaller || larger))
continue;
4805 const std::vector<int>& c2_literals = constraint_literals[c2];
4809 std::vector<int> c1_minus_c2;
4811 std::vector<int> c2_minus_c1;
4814 if (c1_minus_c2.empty()) {
4815 if (!ProcessSetPPCSubset(c1, c2, c2_minus_c1,
4816 original_constraint_index, &removed)) {
4819 }
else if (c2_minus_c1.empty()) {
4820 if (!ProcessSetPPCSubset(c2, c1, c1_minus_c2,
4821 original_constraint_index, &removed)) {
4832 void CpModelPresolver::TryToSimplifyDomain(
int var) {
4840 if (r.representative !=
var)
return;
4855 if (domain.Size() == 2 && (domain.Min() != 0 || domain.Max() != 1)) {
4860 if (domain.NumIntervals() != domain.Size())
return;
4862 const int64_t var_min = domain.Min();
4863 int64_t gcd = domain[1].start - var_min;
4865 const ClosedInterval& i = domain[
index];
4867 const int64_t shifted_value = i.start - var_min;
4871 if (gcd == 1)
break;
4873 if (gcd == 1)
return;
4877 std::vector<int64_t> scaled_values;
4879 const ClosedInterval& i = domain[
index];
4881 const int64_t shifted_value = i.start - var_min;
4882 scaled_values.push_back(shifted_value / gcd);
4894 void CpModelPresolver::EncodeAllAffineRelations() {
4895 int64_t num_added = 0;
4900 if (r.representative ==
var)
continue;
4907 if (!PresolveAffineRelationIfAny(
var))
break;
4914 auto* arg =
ct->mutable_linear();
4917 arg->add_vars(r.representative);
4918 arg->add_coeffs(-r.coeff);
4919 arg->add_domain(r.offset);
4920 arg->add_domain(r.offset);
4928 if (num_added > 0) {
4929 SOLVER_LOG(logger_, num_added,
" affine relations still in the model.");
4934 bool CpModelPresolver::PresolveAffineRelationIfAny(
int var) {
4938 if (r.representative ==
var)
return true;
4957 auto* arg =
ct->mutable_linear();
4960 arg->add_vars(r.representative);
4961 arg->add_coeffs(-r.coeff);
4962 arg->add_domain(r.offset);
4963 arg->add_domain(r.offset);
4969 void CpModelPresolver::PresolveToFixPoint() {
4973 const int64_t max_num_operations =
4974 context_->
params().cp_model_max_num_presolve_operations() > 0
4975 ? context_->
params().cp_model_max_num_presolve_operations()
4981 absl::flat_hash_set<std::pair<int, int>> var_constraint_pair_already_called;
4986 std::vector<bool> in_queue(context_->
working_model->constraints_size(),
4988 std::deque<int> queue;
4989 for (
int c = 0; c < in_queue.size(); ++c) {
4990 if (context_->
working_model->constraints(c).constraint_case() !=
4991 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
5001 if (context_->
params().permute_presolve_constraint_order()) {
5002 std::shuffle(queue.begin(), queue.end(), *context_->
random());
5004 std::sort(queue.begin(), queue.end(), [
this](
int a,
int b) {
5005 const int score_a = context_->ConstraintToVars(a).size();
5006 const int score_b = context_->ConstraintToVars(b).size();
5007 return score_a < score_b || (score_a == score_b && a < b);
5017 const int c = queue.front();
5018 in_queue[c] =
false;
5021 const int old_num_constraint =
5025 SOLVER_LOG(logger_,
"Unsat after presolving constraint #", c,
5026 " (warning, dump might be inconsistent): ",
5027 context_->
working_model->constraints(c).ShortDebugString());
5031 const int new_num_constraints =
5033 if (new_num_constraints > old_num_constraint) {
5035 in_queue.resize(new_num_constraints,
true);
5036 for (
int c = old_num_constraint; c < new_num_constraints; ++c) {
5053 const int current_num_variables = context_->
working_model->variables_size();
5054 for (
int v = 0; v < current_num_variables; ++v) {
5056 if (!PresolveAffineRelationIfAny(v))
return;
5061 TryToSimplifyDomain(v);
5070 in_queue.resize(context_->
working_model->constraints_size(),
false);
5075 if (c >= 0 && !in_queue[c]) {
5085 const int num_vars = context_->
working_model->variables_size();
5086 for (
int v = 0; v < num_vars; ++v) {
5088 if (constraints.size() != 1)
continue;
5089 const int c = *constraints.begin();
5090 if (c < 0)
continue;
5096 std::pair<int, int>(v, c))) {
5099 var_constraint_pair_already_called.insert({v, c});
5109 std::sort(queue.begin(), queue.end());
5123 VarDomination var_dom;
5124 DualBoundStrengthening dual_bound_strengthening;
5126 if (!dual_bound_strengthening.Strengthen(context_))
return;
5143 const int num_constraints = context_->
working_model->constraints_size();
5144 for (
int c = 0; c < num_constraints; ++c) {
5145 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
5146 switch (
ct->constraint_case()) {
5147 case ConstraintProto::ConstraintCase::kNoOverlap:
5149 if (PresolveNoOverlap(
ct)) {
5153 case ConstraintProto::ConstraintCase::kNoOverlap2D:
5157 case ConstraintProto::ConstraintCase::kCumulative:
5159 if (PresolveCumulative(
ct)) {
5163 case ConstraintProto::ConstraintCase::kBoolOr: {
5166 for (
const auto& pair :
5168 bool modified =
false;
5192 " affine relations were detected.");
5194 " variable equivalence relations were detected.");
5195 std::map<std::string, int> sorted_rules(
context->stats_by_rule_name.begin(),
5196 context->stats_by_rule_name.end());
5197 for (
const auto& entry : sorted_rules) {
5198 if (entry.second == 1) {
5199 SOLVER_LOG(logger,
" - rule '", entry.first,
"' was applied 1 time.");
5201 SOLVER_LOG(logger,
" - rule '", entry.first,
"' was applied ",
5202 entry.second,
" times.");
5211 const CpModelProto& in_model,
const std::vector<int>& ignored_constraints) {
5212 const absl::flat_hash_set<int> ignored_constraints_set(
5213 ignored_constraints.begin(), ignored_constraints.end());
5216 starting_constraint_index_ = context_->
working_model->constraints_size();
5217 for (
int c = 0; c < in_model.constraints_size(); ++c) {
5218 if (ignored_constraints_set.contains(c))
continue;
5220 const ConstraintProto&
ct = in_model.constraints(c);
5221 if (OneEnforcementLiteralIsFalse(
ct) &&
5222 ct.constraint_case() != ConstraintProto::kInterval) {
5225 switch (
ct.constraint_case()) {
5226 case ConstraintProto::CONSTRAINT_NOT_SET: {
5229 case ConstraintProto::kBoolOr: {
5230 if (!CopyBoolOr(
ct))
return CreateUnsatModel();
5233 case ConstraintProto::kBoolAnd: {
5234 if (!CopyBoolAnd(
ct))
return CreateUnsatModel();
5237 case ConstraintProto::kLinear: {
5238 if (!CopyLinear(
ct))
return CreateUnsatModel();
5241 case ConstraintProto::kAtMostOne: {
5242 if (!CopyAtMostOne(
ct))
return CreateUnsatModel();
5245 case ConstraintProto::kExactlyOne: {
5246 if (!CopyExactlyOne(
ct))
return CreateUnsatModel();
5249 case ConstraintProto::kInterval: {
5250 if (!CopyInterval(
ct, c))
return CreateUnsatModel();
5261 for (
int c = starting_constraint_index_;
5263 ConstraintProto& ct_ref = *context_->
working_model->mutable_constraints(c);
5266 const auto& it = interval_mapping_.find(*ref);
5267 if (it != interval_mapping_.end()) {
5277 void ModelCopy::CopyEnforcementLiterals(
const ConstraintProto& orig,
5278 ConstraintProto* dest) {
5279 temp_enforcement_literals_.clear();
5280 for (
const int lit : orig.enforcement_literal()) {
5282 skipped_non_zero_++;
5285 temp_enforcement_literals_.push_back(lit);
5287 dest->mutable_enforcement_literal()->Add(temp_enforcement_literals_.begin(),
5288 temp_enforcement_literals_.end());
5291 bool ModelCopy::OneEnforcementLiteralIsFalse(
const ConstraintProto&
ct)
const {
5292 for (
const int lit :
ct.enforcement_literal()) {
5300 bool ModelCopy::CopyBoolOr(
const ConstraintProto&
ct) {
5301 temp_literals_.clear();
5302 for (
const int lit :
ct.enforcement_literal()) {
5306 for (
const int lit :
ct.bool_or().literals()) {
5311 skipped_non_zero_++;
5313 temp_literals_.push_back(lit);
5319 ->mutable_literals()
5320 ->Add(temp_literals_.begin(), temp_literals_.end());
5321 return !temp_literals_.empty();
5324 bool ModelCopy::CopyBoolAnd(
const ConstraintProto&
ct) {
5325 bool at_least_one_false =
false;
5326 int num_non_fixed_literals = 0;
5327 for (
const int lit :
ct.bool_and().literals()) {
5329 at_least_one_false =
true;
5333 num_non_fixed_literals++;
5337 if (at_least_one_false) {
5338 ConstraintProto* new_ct = context_->
working_model->add_constraints();
5339 BoolArgumentProto* bool_or = new_ct->mutable_bool_or();
5342 for (
const int lit :
ct.enforcement_literal()) {
5344 skipped_non_zero_++;
5349 return !bool_or->literals().empty();
5350 }
else if (num_non_fixed_literals > 0) {
5351 ConstraintProto* new_ct = context_->
working_model->add_constraints();
5352 CopyEnforcementLiterals(
ct, new_ct);
5353 BoolArgumentProto* bool_and = new_ct->mutable_bool_and();
5354 bool_and->mutable_literals()->Reserve(num_non_fixed_literals);
5355 for (
const int lit :
ct.bool_and().literals()) {
5357 skipped_non_zero_++;
5360 bool_and->add_literals(lit);
5366 bool ModelCopy::CopyLinear(
const ConstraintProto&
ct) {
5367 non_fixed_variables_.clear();
5368 non_fixed_coefficients_.clear();
5370 for (
int i = 0; i <
ct.linear().vars_size(); ++i) {
5371 const int ref =
ct.linear().vars(i);
5372 const int64_t coeff =
ct.linear().coeffs(i);
5374 offset += coeff * context_->
MinOf(ref);
5375 skipped_non_zero_++;
5377 non_fixed_variables_.push_back(ref);
5378 non_fixed_coefficients_.push_back(coeff);
5382 const Domain new_domain =
5384 if (non_fixed_variables_.empty() && !new_domain.Contains(0)) {
5385 if (
ct.enforcement_literal().empty()) {
5388 temp_literals_.clear();
5389 for (
const int literal :
ct.enforcement_literal()) {
5391 skipped_non_zero_++;
5398 ->mutable_literals()
5399 ->Add(temp_literals_.begin(), temp_literals_.end());
5400 return !temp_literals_.empty();
5403 ConstraintProto* new_ct = context_->
working_model->add_constraints();
5404 CopyEnforcementLiterals(
ct, new_ct);
5405 LinearConstraintProto* linear = new_ct->mutable_linear();
5406 linear->mutable_vars()->Add(non_fixed_variables_.begin(),
5407 non_fixed_variables_.end());
5408 linear->mutable_coeffs()->Add(non_fixed_coefficients_.begin(),
5409 non_fixed_coefficients_.end());
5414 bool ModelCopy::CopyAtMostOne(
const ConstraintProto&
ct) {
5416 temp_literals_.clear();
5417 for (
const int lit :
ct.at_most_one().literals()) {
5419 skipped_non_zero_++;
5422 temp_literals_.push_back(lit);
5426 if (temp_literals_.size() <= 1)
return true;
5427 if (num_true > 1)
return false;
5430 ConstraintProto* new_ct = context_->
working_model->add_constraints();
5431 CopyEnforcementLiterals(
ct, new_ct);
5432 new_ct->mutable_at_most_one()->mutable_literals()->Add(temp_literals_.begin(),
5433 temp_literals_.end());
5437 bool ModelCopy::CopyExactlyOne(
const ConstraintProto&
ct) {
5439 temp_literals_.clear();
5440 for (
const int lit :
ct.exactly_one().literals()) {
5442 skipped_non_zero_++;
5445 temp_literals_.push_back(lit);
5449 if (temp_literals_.empty() || num_true > 1)
return false;
5452 ConstraintProto* new_ct = context_->
working_model->add_constraints();
5453 CopyEnforcementLiterals(
ct, new_ct);
5454 new_ct->mutable_exactly_one()->mutable_literals()->Add(temp_literals_.begin(),
5455 temp_literals_.end());
5459 bool ModelCopy::CopyInterval(
const ConstraintProto&
ct,
int c) {
5461 CHECK_EQ(starting_constraint_index_, 0)
5462 <<
"Adding new interval constraints to partially filled model is not "
5464 interval_mapping_[c] = context_->
working_model->constraints_size();
5469 bool ModelCopy::CreateUnsatModel() {
5471 context_->
working_model->add_constraints()->mutable_bool_or();
5483 return context->NotifyThatModelIsUnsat();
5488 if (!in_model.name().empty()) {
5489 context->working_model->set_name(in_model.name());
5491 if (in_model.has_objective()) {
5492 *
context->working_model->mutable_objective() = in_model.objective();
5494 if (!in_model.search_strategy().empty()) {
5495 *
context->working_model->mutable_search_strategy() =
5496 in_model.search_strategy();
5498 if (!in_model.assumptions().empty()) {
5499 *
context->working_model->mutable_assumptions() = in_model.assumptions();
5501 if (in_model.has_symmetry()) {
5502 *
context->working_model->mutable_symmetry() = in_model.symmetry();
5504 if (in_model.has_solution_hint()) {
5505 *
context->working_model->mutable_solution_hint() = in_model.solution_hint();
5514 std::vector<int>* postsolve_mapping) {
5520 std::vector<int>* postsolve_mapping)
5521 : postsolve_mapping_(postsolve_mapping),
5526 context_->
params().keep_all_feasible_solutions_in_presolve() ||
5527 context_->
params().enumerate_all_solutions() ||
5528 context_->
params().fill_tightened_domains_in_response() ||
5529 !context_->
params().cp_model_presolve();
5532 for (
const auto& decision_strategy :
5534 *(context_->
mapping_model->add_search_strategy()) = decision_strategy;
5567 if (!context_->
params().cp_model_presolve()) {
5579 for (
int c = 0; c < context_->
working_model->constraints_size(); ++c) {
5580 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
5581 PresolveEnforcementLiteral(
ct);
5582 switch (
ct->constraint_case()) {
5583 case ConstraintProto::ConstraintCase::kBoolOr:
5586 case ConstraintProto::ConstraintCase::kBoolAnd:
5587 PresolveBoolAnd(
ct);
5589 case ConstraintProto::ConstraintCase::kAtMostOne:
5590 PresolveAtMostOne(
ct);
5592 case ConstraintProto::ConstraintCase::kExactlyOne:
5593 PresolveExactlyOne(
ct);
5595 case ConstraintProto::ConstraintCase::kLinear:
5596 CanonicalizeLinear(
ct);
5608 for (
int iter = 0; iter < context_->
params().max_presolve_iterations();
5613 int old_num_non_empty_constraints = 0;
5614 for (
int c = 0; c < context_->
working_model->constraints_size(); ++c) {
5617 if (type == ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET)
continue;
5618 old_num_non_empty_constraints++;
5625 PresolveToFixPoint();
5632 if (context_->
params().cp_model_probing_level() > 0) {
5635 PresolveToFixPoint();
5642 if (context_->
params().cp_model_use_sat_presolve()) {
5644 PresolvePureSatPart();
5657 const int old_size = context_->
working_model->constraints_size();
5658 for (
int c = 0; c < old_size; ++c) {
5659 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
5660 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear) {
5663 ExtractAtMostOneFromLinear(
ct);
5668 if (iter == 0) TransformIntoMaxCliques();
5685 PresolveToFixPoint();
5691 old_num_non_empty_constraints)) {
5698 MergeNoOverlapConstraints();
5708 EncodeAllAffineRelations();
5715 const std::vector<int> duplicates =
5717 if (!duplicates.empty()) {
5718 for (
const int c : duplicates) {
5721 if (type == ConstraintProto::ConstraintCase::kInterval) {
5738 context_->
working_model->add_constraints()->mutable_bool_or();
5750 absl::flat_hash_set<int> used_variables;
5751 for (DecisionStrategyProto& strategy :
5753 DecisionStrategyProto copy = strategy;
5754 strategy.clear_variables();
5755 strategy.clear_transformations();
5756 for (
const int ref : copy.variables()) {
5765 used_variables.insert(
var);
5773 if (strategy.variable_selection_strategy() !=
5774 DecisionStrategyProto::CHOOSE_FIRST) {
5775 DecisionStrategyProto::AffineTransformation* t =
5776 strategy.add_transformations();
5777 t->set_index(strategy.variables_size());
5779 t->set_positive_coeff(std::abs(r.
coeff));
5781 strategy.add_variables(rep);
5788 strategy.add_variables(ref);
5794 for (
int i = 0; i < context_->
working_model->variables_size(); ++i) {
5805 postsolve_mapping_->clear();
5806 std::vector<int> mapping(context_->
working_model->variables_size(), -1);
5807 for (
int i = 0; i < context_->
working_model->variables_size(); ++i) {
5812 mapping[i] = postsolve_mapping_->size();
5813 postsolve_mapping_->push_back(i);
5816 if (context_->
params().permute_variable_randomly()) {
5817 std::shuffle(postsolve_mapping_->begin(), postsolve_mapping_->end(),
5819 for (
int i = 0; i < postsolve_mapping_->size(); ++i) {
5820 mapping[(*postsolve_mapping_)[i]] = i;
5845 if (!error.empty()) {
5846 SOLVER_LOG(logger_,
"Error while validating postsolved model: ", error);
5852 if (!error.empty()) {
5854 "Error while validating mapping_model model: ", error);
5867 auto mapping_function = [&mapping](
int* ref) {
5872 for (ConstraintProto& ct_ref : *
proto->mutable_constraints()) {
5878 if (
proto->has_objective()) {
5879 for (
int& mutable_ref : *
proto->mutable_objective()->mutable_vars()) {
5880 mapping_function(&mutable_ref);
5885 for (
int& mutable_ref : *
proto->mutable_assumptions()) {
5886 mapping_function(&mutable_ref);
5891 for (DecisionStrategyProto& strategy : *
proto->mutable_search_strategy()) {
5892 const DecisionStrategyProto copy = strategy;
5893 strategy.clear_variables();
5894 std::vector<int> new_indices(copy.variables().size(), -1);
5895 for (
int i = 0; i < copy.variables().size(); ++i) {
5896 const int ref = copy.variables(i);
5899 new_indices[i] = strategy.variables_size();
5903 strategy.clear_transformations();
5904 for (
const auto& transform : copy.transformations()) {
5905 CHECK_LT(transform.index(), new_indices.size());
5906 const int new_index = new_indices[transform.index()];
5907 if (new_index == -1)
continue;
5908 auto* new_transform = strategy.add_transformations();
5909 *new_transform = transform;
5910 CHECK_LT(new_index, strategy.variables().size());
5911 new_transform->set_index(new_index);
5916 if (
proto->has_solution_hint()) {
5917 auto* mutable_hint =
proto->mutable_solution_hint();
5919 for (
int i = 0; i < mutable_hint->vars_size(); ++i) {
5920 const int old_ref = mutable_hint->vars(i);
5921 const int64_t old_value = mutable_hint->values(i);
5929 const int image = mapping[
var];
5931 mutable_hint->set_vars(new_size, image);
5932 mutable_hint->set_values(new_size,
value);
5937 mutable_hint->mutable_vars()->Truncate(new_size);
5938 mutable_hint->mutable_values()->Truncate(new_size);
5940 proto->clear_solution_hint();
5945 std::vector<IntegerVariableProto> new_variables;
5946 for (
int i = 0; i < mapping.size(); ++i) {
5947 const int image = mapping[i];
5948 if (image < 0)
continue;
5949 if (image >= new_variables.size()) {
5950 new_variables.resize(image + 1, IntegerVariableProto());
5952 new_variables[image].Swap(
proto->mutable_variables(i));
5954 proto->clear_variables();
5955 for (IntegerVariableProto& proto_ref : new_variables) {
5956 proto->add_variables()->Swap(&proto_ref);
5960 for (
const IntegerVariableProto& v :
proto->variables()) {
5966 std::vector<int> result;
5969 ConstraintProto copy;
5970 absl::flat_hash_map<int64_t, int> equiv_constraints;
5973 const int num_constraints =
model_proto.constraints().size();
5974 for (
int c = 0; c < num_constraints; ++c) {
5975 if (
model_proto.constraints(c).constraint_case() ==
5976 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
5985 s = copy.SerializeAsString();
5987 const int64_t
hash = std::hash<std::string>()(s);
5988 const auto insert = equiv_constraints.insert({
hash, c});
5989 if (!insert.second) {
5991 const int other_c_with_same_hash = insert.first->second;
5992 copy =
model_proto.constraints(other_c_with_same_hash);
5994 if (s == copy.SerializeAsString()) {
5995 result.push_back(c);
#define DCHECK_NE(val1, val2)
#define CHECK_LT(val1, val2)
#define CHECK_EQ(val1, val2)
#define CHECK_GE(val1, val2)
#define CHECK_GT(val1, val2)
#define DCHECK_GE(val1, val2)
#define CHECK_NE(val1, val2)
#define DCHECK_GT(val1, val2)
#define DCHECK_LT(val1, val2)
#define DCHECK(condition)
#define CHECK_LE(val1, val2)
#define DCHECK_EQ(val1, val2)
#define VLOG(verboselevel)
Domain InverseMultiplicationBy(const int64_t coeff) const
Returns {x ∈ Int64, ∃ e ∈ D, x * coeff = e}.
bool IsIncludedIn(const Domain &domain) const
Returns true iff D is included in the given domain.
Domain AdditionWith(const Domain &domain) const
Returns {x ∈ Int64, ∃ a ∈ D, ∃ b ∈ domain, x = a + b}.
ClosedInterval front() const
int64_t Size() const
Returns the number of elements in the domain.
Domain UnionWith(const Domain &domain) const
Returns the union of D and domain.
Domain MultiplicationBy(int64_t coeff, bool *exact=nullptr) const
Returns {x ∈ Int64, ∃ e ∈ D, x = e * coeff}.
Domain IntersectionWith(const Domain &domain) const
Returns the intersection of D and domain.
Domain RelaxIfTooComplex() const
If NumIntervals() is too large, this return a superset of the domain.
static Domain FromValues(std::vector< int64_t > values)
Creates a domain from the union of an unsorted list of integer values.
Domain DivisionBy(int64_t coeff) const
Returns {x ∈ Int64, ∃ e ∈ D, x = e / coeff}.
static int64_t GCD64(int64_t x, int64_t y)
bool LoggingIsEnabled() const
const std::vector< IntegerType > & PositionsSetAtLeastOnce() const
void Set(IntegerType index)
bool LimitReached()
Returns true when the external limit is true, or the deterministic time is over the deterministic lim...
void AdvanceDeterministicTime(double deterministic_duration)
Advances the deterministic time.
void RemoveEmptyConstraints()
CpModelPresolver(PresolveContext *context, std::vector< int > *postsolve_mapping)
bool PresolveOneConstraint(int c)
std::vector< std::pair< int, Domain > > ProcessClause(absl::Span< const int > clause)
void MarkProcessingAsDoneForNow()
int NumDeductions() const
void AddDeduction(int literal_ref, int var, Domain domain)
ModelCopy(PresolveContext *context)
bool ImportAndSimplifyConstraints(const CpModelProto &in_model, const std::vector< int > &ignored_constraints)
int64_t MaxOf(int ref) const
SparseBitset< int64_t > modified_domains
bool StoreAbsRelation(int target_ref, int ref)
bool ConstraintVariableUsageIsConsistent()
bool ModelIsUnsat() const
bool VariableIsOnlyUsedInEncoding(int ref) const
ABSL_MUST_USE_RESULT bool IntersectDomainWith(int ref, const Domain &domain, bool *domain_modified=nullptr)
std::vector< absl::flat_hash_set< int > > var_to_lb_only_constraints
bool ConstraintVariableGraphIsUpToDate() const
bool StoreLiteralImpliesVarNEqValue(int literal, int var, int64_t value)
const Domain & ObjectiveDomain() const
SolverLogger * logger() const
bool DomainOfVarIsIncludedIn(int var, const Domain &domain)
bool VariableWithCostIsUniqueAndRemovable(int ref) const
void WriteObjectiveToProto() const
int GetLiteralRepresentative(int ref) const
ABSL_MUST_USE_RESULT bool SetLiteralToTrue(int lit)
bool StoreAffineRelation(int ref_x, int ref_y, int64_t coeff, int64_t offset)
std::vector< int > tmp_literals
std::vector< absl::flat_hash_set< int > > var_to_ub_only_constraints
bool ObjectiveDomainIsConstraining() const
CpModelProto * mapping_model
bool SubstituteVariableInObjective(int var_in_equality, int64_t coeff_in_equality, const ConstraintProto &equality, std::vector< int > *new_vars_in_objective=nullptr)
const std::vector< int > & ConstraintToVars(int c) const
int GetOrCreateVarValueEncoding(int ref, int64_t value)
void UpdateNewConstraintsVariableUsage()
bool VariableIsUniqueAndRemovable(int ref) const
void RemoveVariableFromAffineRelation(int var)
ABSL_MUST_USE_RESULT bool NotifyThatModelIsUnsat(const std::string &message="")
bool PropagateAffineRelation(int ref)
Domain DomainOf(int ref) const
int64_t num_presolve_operations
void InitializeNewDomains()
int GetVariableRepresentative(int ref) const
const absl::flat_hash_map< int, int64_t > & ObjectiveMap() const
int NewIntVar(const Domain &domain)
void MarkVariableAsRemoved(int ref)
DomainDeductions deductions
std::vector< Domain > tmp_left_domains
bool DomainIsEmpty(int ref) const
void CanonicalizeDomainOfSizeTwo(int var)
bool LiteralIsTrue(int lit) const
void StoreBooleanEqualityRelation(int ref_a, int ref_b)
int IntervalUsage(int c) const
CpModelProto * working_model
bool DomainContains(int ref, int64_t value) const
bool LiteralIsFalse(int lit) const
void UpdateRuleStats(const std::string &name, int num_times=1)
ABSL_MUST_USE_RESULT bool CanonicalizeObjective()
const SatParameters & params() const
void RemoveAllVariablesFromAffineRelationConstraint()
AffineRelation::Relation GetAffineRelation(int ref) const
bool VariableIsNotUsedAnymore(int ref) const
void UpdateConstraintVariableUsage(int c)
bool keep_all_feasible_solutions
bool IsFixed(int ref) const
std::vector< Domain > tmp_term_domains
const absl::flat_hash_set< int > & VarToConstraints(int var) const
ModelRandomGenerator * random()
ABSL_MUST_USE_RESULT bool SetLiteralToFalse(int lit)
int GetOrCreateConstantVar(int64_t cst)
absl::flat_hash_set< int > tmp_literal_set
void ReadObjectiveFromProto()
bool CanBeUsedAsLiteral(int ref) const
void RegisterVariablesUsedInAssumptions()
void ExploitFixedDomain(int var)
int64_t MinOf(int ref) const
bool GetAbsRelation(int target_ref, int *ref)
bool StoreLiteralImpliesVarEqValue(int literal, int var, int64_t value)
CpModelProto const * model_proto
SharedTimeLimit * time_limit
GurobiMPCallbackContext * context
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
void STLSetDifference(const In1 &a, const In2 &b, Out *out, Compare compare)
bool ContainsKey(const Collection &collection, const Key &key)
const Collection::value_type::second_type & FindOrDie(const Collection &collection, const typename Collection::value_type::first_type &key)
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
bool DetectAndExploitSymmetriesInPresolve(PresolveContext *context)
bool LoadConstraint(const ConstraintProto &ct, Model *m)
bool RefIsPositive(int ref)
void SetToNegatedLinearExpression(const LinearExpressionProto &input_expr, LinearExpressionProto *output_negated_expr)
const LiteralIndex kNoLiteralIndex(-1)
bool HasEnforcementLiteral(const ConstraintProto &ct)
void ExpandCpModel(PresolveContext *context)
constexpr int kAffineRelationConstraint
bool PresolveCpModel(PresolveContext *context, std::vector< int > *postsolve_mapping)
void SubstituteVariable(int var, int64_t var_coeff_in_definition, const ConstraintProto &definition, ConstraintProto *ct)
void ApplyToAllLiteralIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
void DetectDominanceRelations(const PresolveContext &context, VarDomination *var_domination, DualBoundStrengthening *dual_bound_strengthening)
void ApplyToAllIntervalIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
void CopyEverythingExceptVariablesAndConstraintsFieldsIntoContext(const CpModelProto &in_model, PresolveContext *context)
int ReindexArcs(IntContainer *tails, IntContainer *heads)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
std::vector< int > FindDuplicateConstraints(const CpModelProto &model_proto)
bool ImportConstraintsWithBasicPresolveIntoContext(const CpModelProto &in_model, PresolveContext *context)
void LogInfoFromContext(const PresolveContext *context)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
void ApplyToAllVariableIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
constexpr int kObjectiveConstraint
bool ExploitDominanceRelations(const VarDomination &var_domination, PresolveContext *context)
void ApplyVariableMapping(const std::vector< int > &mapping, const PresolveContext &context)
std::string ValidateCpModel(const CpModelProto &model)
Collection of objects used to extend the Constraint Solver library.
#define SOLVER_LOG(logger,...)