31 #include "absl/base/attributes.h" 32 #include "absl/container/flat_hash_map.h" 33 #include "absl/container/flat_hash_set.h" 34 #include "absl/hash/hash.h" 35 #include "absl/random/random.h" 36 #include "absl/strings/str_join.h" 63 bool CpModelPresolver::RemoveConstraint(ConstraintProto*
ct) {
76 int new_num_constraints = 0;
77 const int old_num_non_empty_constraints =
79 for (
int c = 0; c < old_num_non_empty_constraints; ++c) {
81 if (type == ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET)
continue;
82 if (type == ConstraintProto::ConstraintCase::kDummyConstraint)
continue;
83 if (type == ConstraintProto::ConstraintCase::kInterval) {
84 interval_mapping[c] = new_num_constraints;
90 new_num_constraints, old_num_non_empty_constraints - new_num_constraints);
94 [&interval_mapping](
int* ref) {
95 *ref = interval_mapping[*ref];
107 const int old_size =
ct->enforcement_literal().size();
108 for (
const int literal :
ct->enforcement_literal()) {
117 return RemoveConstraint(
ct);
124 return RemoveConstraint(
ct);
131 const int64_t obj_coeff =
135 context_->
UpdateRuleStats(
"enforcement literal with unique direction");
137 return RemoveConstraint(
ct);
141 ct->set_enforcement_literal(new_size++,
literal);
143 ct->mutable_enforcement_literal()->Truncate(new_size);
144 return new_size != old_size;
147 bool CpModelPresolver::PresolveBoolXor(ConstraintProto*
ct) {
152 bool changed =
false;
153 int num_true_literals = 0;
155 for (
const int literal :
ct->bool_xor().literals()) {
176 ct->mutable_bool_xor()->set_literals(new_size++,
literal);
180 if (num_true_literals % 2 == 0) {
184 return RemoveConstraint(
ct);
186 }
else if (new_size == 1) {
187 if (num_true_literals % 2 == 0) {
190 "bool_xor: cannot fix last literal");
195 "bool_xor: cannot fix last literal");
199 return RemoveConstraint(
ct);
200 }
else if (new_size == 2) {
201 const int a =
ct->bool_xor().literals(0);
202 const int b =
ct->bool_xor().literals(1);
204 if (num_true_literals % 2 == 0) {
208 return RemoveConstraint(
ct);
212 if (num_true_literals % 2 == 1) {
216 return RemoveConstraint(
ct);
219 if (num_true_literals % 2 == 0) {
226 return RemoveConstraint(
ct);
229 if (num_true_literals % 2 == 1) {
231 ct->mutable_bool_xor()->set_literals(new_size++, true_literal);
233 if (num_true_literals > 1) {
234 context_->
UpdateRuleStats(
"bool_xor: remove even number of true literals");
237 ct->mutable_bool_xor()->mutable_literals()->Truncate(new_size);
241 bool CpModelPresolver::PresolveBoolOr(ConstraintProto*
ct) {
248 for (
const int literal :
ct->enforcement_literal()) {
251 ct->clear_enforcement_literal();
255 bool changed =
false;
258 for (
const int literal :
ct->bool_or().literals()) {
265 return RemoveConstraint(
ct);
273 return RemoveConstraint(
ct);
277 return RemoveConstraint(
ct);
296 return RemoveConstraint(
ct);
309 ct->mutable_bool_or()->mutable_literals()->Clear();
311 ct->mutable_bool_or()->add_literals(lit);
320 ABSL_MUST_USE_RESULT
bool CpModelPresolver::MarkConstraintAsFalse(
321 ConstraintProto*
ct) {
324 ct->mutable_bool_or()->clear_literals();
325 for (
const int lit :
ct->enforcement_literal()) {
328 ct->clear_enforcement_literal();
336 bool CpModelPresolver::PresolveBoolAnd(ConstraintProto*
ct) {
341 for (
const int literal :
ct->bool_and().literals()) {
344 return RemoveConstraint(
ct);
347 bool changed =
false;
349 for (
const int literal :
ct->bool_and().literals()) {
352 return MarkConstraintAsFalse(
ct);
372 ct->mutable_bool_and()->mutable_literals()->Clear();
374 ct->mutable_bool_and()->add_literals(lit);
383 if (
ct->enforcement_literal().size() == 1 &&
384 ct->bool_and().literals().size() == 1) {
385 const int enforcement =
ct->enforcement_literal(0);
395 ct->bool_and().literals(0));
403 bool CpModelPresolver::PresolveAtMostOrExactlyOne(ConstraintProto*
ct) {
405 const std::string
name = is_at_most_one ?
"at_most_one: " :
"exactly_one: ";
406 auto* literals = is_at_most_one
407 ?
ct->mutable_at_most_one()->mutable_literals()
408 :
ct->mutable_exactly_one()->mutable_literals();
412 for (
const int literal : *literals) {
419 int num_positive = 0;
420 int num_negative = 0;
421 for (
const int other : *literals) {
442 return RemoveConstraint(
ct);
447 bool changed =
false;
448 bool transform_to_at_most_one =
false;
450 for (
const int literal : *literals) {
453 for (
const int other : *literals) {
458 return RemoveConstraint(
ct);
471 if (is_at_most_one && !is_removable &&
475 const int64_t coeff = it->second;
482 if (is_at_most_one) {
489 is_at_most_one =
true;
490 transform_to_at_most_one =
true;
501 if (!is_at_most_one && !transform_to_at_most_one &&
506 if (transform_to_at_most_one) {
509 literals =
ct->mutable_at_most_one()->mutable_literals();
521 bool CpModelPresolver::PresolveAtMostOne(ConstraintProto*
ct) {
525 const bool changed = PresolveAtMostOrExactlyOne(
ct);
529 const auto& literals =
ct->at_most_one().literals();
530 if (literals.empty()) {
532 return RemoveConstraint(
ct);
536 if (literals.size() == 1) {
538 return RemoveConstraint(
ct);
544 bool CpModelPresolver::PresolveExactlyOne(ConstraintProto*
ct) {
547 const bool changed = PresolveAtMostOrExactlyOne(
ct);
551 const auto& literals =
ct->exactly_one().literals();
552 if (literals.empty()) {
557 if (literals.size() == 1) {
560 return RemoveConstraint(
ct);
564 if (literals.size() == 2) {
568 return RemoveConstraint(
ct);
574 bool CpModelPresolver::CanonicalizeLinearArgument(
const ConstraintProto&
ct,
575 LinearArgumentProto*
proto) {
579 bool changed = CanonicalizeLinearExpression(
ct,
proto->mutable_target());
580 for (LinearExpressionProto& exp : *(
proto->mutable_exprs())) {
581 changed |= CanonicalizeLinearExpression(
ct, &exp);
586 bool CpModelPresolver::PresolveLinMax(ConstraintProto*
ct) {
592 const LinearExpressionProto& target =
ct->lin_max().target();
594 int64_t infered_min = context_->
MinOf(target);
596 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
601 if (target.vars().empty()) {
602 if (!Domain(infered_min, infered_max).Contains(target.offset())) {
604 return MarkConstraintAsFalse(
ct);
607 if (target.vars().size() <= 1) {
609 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
610 rhs_domain = rhs_domain.UnionWith(
612 {infered_min, infered_max}));
614 bool reduced =
false;
625 const int64_t target_min = context_->
MinOf(target);
626 const int64_t target_max = context_->
MaxOf(target);
627 bool changed =
false;
634 bool has_greater_or_equal_to_target_min =
false;
636 int index_to_keep = -1;
637 for (
int i = 0; i <
ct->lin_max().exprs_size(); ++i) {
638 const LinearExpressionProto& expr =
ct->lin_max().exprs(i);
639 if (context_->
MinOf(expr) >= target_min) {
640 const int64_t expr_max = context_->
MaxOf(expr);
641 if (expr_max > max_at_index_to_keep) {
642 max_at_index_to_keep = expr_max;
645 has_greater_or_equal_to_target_min =
true;
650 for (
int i = 0; i <
ct->lin_max().exprs_size(); ++i) {
651 const LinearExpressionProto& expr =
ct->lin_max().exprs(i);
652 const int64_t expr_max = context_->
MaxOf(expr);
655 if (expr_max < target_min)
continue;
656 if (expr_max == target_min && has_greater_or_equal_to_target_min &&
657 i != index_to_keep) {
660 *
ct->mutable_lin_max()->mutable_exprs(new_size) = expr;
663 if (new_size < ct->lin_max().exprs_size()) {
665 ct->mutable_lin_max()->mutable_exprs()->DeleteSubrange(
666 new_size,
ct->lin_max().exprs_size() - new_size);
671 if (
ct->lin_max().exprs().empty()) {
673 return MarkConstraintAsFalse(
ct);
678 if (
ct->lin_max().exprs().size() == 1) {
682 auto* arg = new_ct->mutable_linear();
683 const LinearExpressionProto&
a =
ct->lin_max().target();
684 const LinearExpressionProto&
b =
ct->lin_max().exprs(0);
685 for (
int i = 0; i <
a.vars().size(); ++i) {
686 arg->add_vars(
a.vars(i));
687 arg->add_coeffs(
a.coeffs(i));
689 for (
int i = 0; i <
b.vars().size(); ++i) {
690 arg->add_vars(
b.vars(i));
691 arg->add_coeffs(-
b.coeffs(i));
693 arg->add_domain(
b.offset() -
a.offset());
694 arg->add_domain(
b.offset() -
a.offset());
696 return RemoveConstraint(
ct);
704 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
705 const int64_t value_min = context_->
MinOf(expr);
706 bool modified =
false;
714 const int64_t value_max = context_->
MaxOf(expr);
715 if (value_max > target_max) {
716 context_->
UpdateRuleStats(
"TODO lin_max: linear expression above max.");
720 if (abort)
return changed;
724 if (target_min == target_max) {
725 bool all_booleans =
true;
726 std::vector<int> literals;
727 const int64_t fixed_target = target_min;
728 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
729 const int64_t value_min = context_->
MinOf(expr);
730 const int64_t value_max = context_->
MaxOf(expr);
731 CHECK_LE(value_max, fixed_target) <<
"Presolved above";
732 if (value_max < fixed_target)
continue;
734 if (value_min == value_max && value_max == fixed_target) {
736 return RemoveConstraint(
ct);
742 all_booleans =
false;
746 if (literals.empty()) {
747 return MarkConstraintAsFalse(
ct);
752 for (
const int lit : literals) {
753 ct->mutable_bool_or()->add_literals(lit);
766 bool min_is_reachable =
false;
767 std::vector<int> min_literals;
768 std::vector<int> literals_above_min;
769 std::vector<int> max_literals;
771 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
772 const int64_t value_min = context_->
MinOf(expr);
773 const int64_t value_max = context_->
MaxOf(expr);
776 if (value_min > target_min) {
784 if (value_min == value_max) {
785 if (value_min == target_min) min_is_reachable =
true;
796 if (value_min == target_min) {
801 if (value_max == target_max) {
802 max_literals.push_back(ref);
803 literals_above_min.push_back(ref);
804 }
else if (value_max > target_min) {
805 literals_above_min.push_back(ref);
806 }
else if (value_max == target_min) {
807 min_literals.push_back(ref);
816 clause->mutable_bool_or();
817 for (
const int lit : max_literals) {
818 clause->mutable_bool_or()->add_literals(lit);
822 for (
const int lit : literals_above_min) {
826 if (!min_is_reachable) {
830 clause->mutable_bool_or();
831 for (
const int lit : min_literals) {
832 clause->mutable_bool_or()->add_literals(lit);
837 return RemoveConstraint(
ct);
845 bool CpModelPresolver::PresolveIntAbs(ConstraintProto*
ct) {
848 const LinearExpressionProto& target_expr =
ct->lin_max().target();
849 const LinearExpressionProto& expr =
ct->lin_max().exprs(0);
855 const Domain new_target_domain =
856 expr_domain.
UnionWith(expr_domain.Negation())
858 bool target_domain_modified =
false;
860 &target_domain_modified)) {
863 if (expr_domain.IsFixed()) {
865 return RemoveConstraint(
ct);
867 if (target_domain_modified) {
868 context_->
UpdateRuleStats(
"int_abs: propagate domain from x to abs(x)");
874 const Domain target_domain =
877 const Domain new_expr_domain =
878 target_domain.
UnionWith(target_domain.Negation());
879 bool expr_domain_modified =
false;
881 &expr_domain_modified)) {
886 if (context_->
IsFixed(target_expr)) {
888 return RemoveConstraint(
ct);
890 if (expr_domain_modified) {
891 context_->
UpdateRuleStats(
"int_abs: propagate domain from abs(x) to x");
896 if (context_->
MinOf(expr) >= 0) {
900 auto* arg = new_ct->mutable_linear();
905 if (!CanonicalizeLinear(new_ct))
return false;
907 return RemoveConstraint(
ct);
910 if (context_->
MaxOf(expr) <= 0) {
914 auto* arg = new_ct->mutable_linear();
919 if (!CanonicalizeLinear(new_ct))
return false;
921 return RemoveConstraint(
ct);
933 return RemoveConstraint(
ct);
954 bool CpModelPresolver::PresolveIntProd(ConstraintProto*
ct) {
959 int64_t constant_factor = 1;
961 bool changed =
false;
962 LinearArgumentProto*
proto =
ct->mutable_int_prod();
963 for (
int i = 0; i <
ct->int_prod().exprs().size(); ++i) {
964 LinearExpressionProto expr =
ct->int_prod().exprs(i);
971 const int64_t coeff = expr.coeffs(0);
972 const int64_t offset = expr.offset();
975 static_cast<uint64_t>(std::abs(offset)));
977 constant_factor =
CapProd(constant_factor, gcd);
978 expr.set_coeffs(0, coeff / gcd);
979 expr.set_offset(offset / gcd);
982 *
proto->mutable_exprs(new_size++) = expr;
984 proto->mutable_exprs()->erase(
proto->mutable_exprs()->begin() + new_size,
985 proto->mutable_exprs()->end());
987 if (
ct->int_prod().exprs().empty()) {
989 Domain(constant_factor))) {
993 return RemoveConstraint(
ct);
996 if (constant_factor == 0) {
1001 return RemoveConstraint(
ct);
1012 constant_factor = 1;
1016 if (
ct->int_prod().exprs().size() == 1) {
1017 LinearConstraintProto*
const lin =
1023 -constant_factor, lin);
1025 context_->
UpdateRuleStats(
"int_prod: linearize product by constant.");
1026 return RemoveConstraint(
ct);
1029 if (constant_factor != 1) {
1034 const LinearExpressionProto old_target =
ct->int_prod().target();
1035 if (!context_->
IsFixed(old_target)) {
1036 const int ref = old_target.vars(0);
1037 const int64_t coeff = old_target.coeffs(0);
1038 const int64_t offset = old_target.offset();
1049 if (context_->
IsFixed(old_target)) {
1050 const int64_t target_value = context_->
FixedValue(old_target);
1051 if (target_value % constant_factor != 0) {
1053 "int_prod: constant factor does not divide constant target");
1055 proto->clear_target();
1056 proto->mutable_target()->set_offset(target_value / constant_factor);
1058 "int_prod: divide product and fixed target by constant factor");
1061 const AffineRelation::Relation r =
1063 const absl::int128 temp_coeff =
1064 absl::int128(old_target.coeffs(0)) * absl::int128(r.coeff);
1065 CHECK_EQ(temp_coeff % absl::int128(constant_factor), 0);
1066 const absl::int128 temp_offset =
1067 absl::int128(old_target.coeffs(0)) * absl::int128(r.offset) +
1068 absl::int128(old_target.offset());
1069 CHECK_EQ(temp_offset % absl::int128(constant_factor), 0);
1070 const absl::int128 new_coeff = temp_coeff / absl::int128(constant_factor);
1071 const absl::int128 new_offset =
1072 temp_offset / absl::int128(constant_factor);
1085 "int_prod: overflow during simplification.");
1089 proto->mutable_target()->set_coeffs(0, static_cast<int64_t>(new_coeff));
1090 proto->mutable_target()->set_vars(0, r.representative);
1091 proto->mutable_target()->set_offset(static_cast<int64_t>(new_offset));
1092 context_->
UpdateRuleStats(
"int_prod: divide product by constant factor");
1099 bool is_square =
false;
1100 if (
ct->int_prod().exprs_size() == 2 &&
1102 ct->int_prod().exprs(1))) {
1107 for (
const LinearExpressionProto& expr :
ct->int_prod().exprs()) {
1112 bool domain_modified =
false;
1114 &domain_modified)) {
1117 if (domain_modified) {
1119 is_square ?
"int_square" :
"int_prod",
": reduced target domain."));
1124 const int64_t target_max = context_->
MaxOf(
ct->int_prod().target());
1127 bool expr_reduced =
false;
1129 {-sqrt_max, sqrt_max}, &expr_reduced)) {
1137 if (
ct->int_prod().exprs_size() == 2) {
1138 LinearExpressionProto
a =
ct->int_prod().exprs(0);
1139 LinearExpressionProto
b =
ct->int_prod().exprs(1);
1140 const LinearExpressionProto product =
ct->int_prod().target();
1147 context_->
UpdateRuleStats(
"int_square: fix variable to zero or one.");
1148 return RemoveConstraint(
ct);
1153 const LinearExpressionProto target_expr =
ct->int_prod().target();
1158 std::vector<int> literals;
1159 for (
const LinearExpressionProto& expr :
ct->int_prod().exprs()) {
1164 literals.push_back(lit);
1172 auto* arg = new_ct->mutable_bool_and();
1173 for (
const int lit : literals) {
1174 arg->add_literals(lit);
1181 for (
const int lit : literals) {
1186 return RemoveConstraint(
ct);
1189 bool CpModelPresolver::PresolveIntDiv(ConstraintProto*
ct) {
1192 const LinearExpressionProto target =
ct->int_div().target();
1193 const LinearExpressionProto expr =
ct->int_div().exprs(0);
1194 const LinearExpressionProto div =
ct->int_div().exprs(1);
1201 return RemoveConstraint(
ct);
1207 return RemoveConstraint(
ct);
1211 if (!context_->
IsFixed(div))
return false;
1213 const int64_t divisor = context_->
FixedValue(div);
1215 LinearConstraintProto*
const lin =
1223 return RemoveConstraint(
ct);
1225 bool domain_modified =
false;
1228 &domain_modified)) {
1231 if (domain_modified) {
1233 "int_div: updated domain of target in target = X / cte");
1239 if (context_->
MinOf(target) >= 0 && context_->
MinOf(expr) >= 0 &&
1241 LinearConstraintProto*
const lin =
1244 lin->add_domain(divisor - 1);
1249 "int_div: linearize positive division with a constant divisor");
1250 return RemoveConstraint(
ct);
1258 bool CpModelPresolver::PresolveIntMod(ConstraintProto*
ct) {
1261 const LinearExpressionProto target =
ct->int_mod().target();
1262 const LinearExpressionProto expr =
ct->int_mod().exprs(0);
1263 const LinearExpressionProto mod =
ct->int_mod().exprs(1);
1265 bool domain_changed =
false;
1274 if (domain_changed) {
1281 bool CpModelPresolver::ExploitEquivalenceRelations(
int c, ConstraintProto*
ct) {
1282 bool changed =
false;
1287 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kLinear) {
1288 for (
int& ref : *
ct->mutable_enforcement_literal()) {
1300 bool work_to_do =
false;
1303 if (r.representative !=
var) {
1308 if (!work_to_do)
return false;
1312 [&changed,
this](
int* ref) {
1323 [&changed,
this](
int* ref) {
1334 bool CpModelPresolver::DivideLinearByGcd(ConstraintProto*
ct) {
1339 const int num_vars =
ct->linear().vars().size();
1340 for (
int i = 0; i < num_vars; ++i) {
1341 const int64_t magnitude = std::abs(
ct->linear().coeffs(i));
1343 if (gcd == 1)
break;
1347 for (
int i = 0; i < num_vars; ++i) {
1348 ct->mutable_linear()->set_coeffs(i,
ct->linear().coeffs(i) / gcd);
1352 if (
ct->linear().domain_size() == 0) {
1353 return MarkConstraintAsFalse(
ct);
1359 template <
typename ProtoWithVarsAndCoeffs>
1360 bool CpModelPresolver::CanonicalizeLinearExpressionInternal(
1361 const ConstraintProto&
ct, ProtoWithVarsAndCoeffs*
proto, int64_t* offset) {
1367 int64_t sum_of_fixed_terms = 0;
1368 bool remapped =
false;
1369 const int old_size =
proto->vars().size();
1371 for (
int i = 0; i < old_size; ++i) {
1379 const int ref =
proto->vars(i);
1381 const int64_t coeff =
1383 if (coeff == 0)
continue;
1391 if (r.representative !=
var) {
1393 sum_of_fixed_terms += coeff * r.
offset;
1396 new_var = r.representative;
1397 new_coeff = coeff * r.coeff;
1402 bool removed =
false;
1403 for (
const int enf :
ct.enforcement_literal()) {
1407 sum_of_fixed_terms += new_coeff;
1416 context_->
UpdateRuleStats(
"linear: enforcement literal in expression");
1420 tmp_terms_.push_back({new_var, new_coeff});
1422 proto->clear_vars();
1423 proto->clear_coeffs();
1424 std::sort(tmp_terms_.begin(), tmp_terms_.end());
1425 int current_var = 0;
1426 int64_t current_coeff = 0;
1427 for (
const auto entry : tmp_terms_) {
1429 if (entry.first == current_var) {
1430 current_coeff += entry.second;
1432 if (current_coeff != 0) {
1433 proto->add_vars(current_var);
1434 proto->add_coeffs(current_coeff);
1436 current_var = entry.first;
1437 current_coeff = entry.second;
1440 if (current_coeff != 0) {
1441 proto->add_vars(current_var);
1442 proto->add_coeffs(current_coeff);
1447 if (
proto->vars().size() < old_size) {
1450 *offset = sum_of_fixed_terms;
1451 return remapped ||
proto->vars().size() < old_size;
1454 bool CpModelPresolver::CanonicalizeLinearExpression(
1455 const ConstraintProto&
ct, LinearExpressionProto* exp) {
1457 const bool result = CanonicalizeLinearExpressionInternal(
ct, exp, &offset);
1458 exp->set_offset(exp->offset() + offset);
1462 bool CpModelPresolver::CanonicalizeLinear(ConstraintProto*
ct) {
1463 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1468 if (
ct->linear().domain().empty()) {
1470 return MarkConstraintAsFalse(
ct);
1475 CanonicalizeLinearExpressionInternal(*
ct,
ct->mutable_linear(), &offset);
1479 ct->mutable_linear());
1481 changed |= DivideLinearByGcd(
ct);
1485 bool CpModelPresolver::RemoveSingletonInLinear(ConstraintProto*
ct) {
1486 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1491 std::set<int> index_to_erase;
1492 const int num_vars =
ct->linear().vars().size();
1498 for (
int i = 0; i < num_vars; ++i) {
1499 const int var =
ct->linear().vars(i);
1500 const int64_t coeff =
ct->linear().coeffs(i);
1504 const auto term_domain =
1506 if (!exact)
continue;
1510 if (new_rhs.NumIntervals() > 100)
continue;
1517 index_to_erase.insert(i);
1524 if (index_to_erase.empty()) {
1527 if (!
ct->enforcement_literal().empty())
return false;
1531 if (rhs.Min() != rhs.Max())
return false;
1533 for (
int i = 0; i < num_vars; ++i) {
1534 const int var =
ct->linear().vars(i);
1535 const int64_t coeff =
ct->linear().coeffs(i);
1556 if (objective_coeff % coeff != 0)
continue;
1560 const auto term_domain =
1562 if (!exact)
continue;
1564 if (new_rhs.NumIntervals() > 100)
continue;
1572 objective_coeff))) {
1596 LOG(
WARNING) <<
"This was not supposed to happen and the presolve " 1597 "could be improved.";
1605 context_->
UpdateRuleStats(
"linear: singleton column define objective.");
1608 return RemoveConstraint(
ct);
1620 "linear: singleton column in equality and in objective.");
1622 index_to_erase.insert(i);
1626 if (index_to_erase.empty())
return false;
1637 if (!
ct->enforcement_literal().empty()) {
1638 for (
const int i : index_to_erase) {
1639 const int var =
ct->linear().vars(i);
1654 for (
int i = 0; i < num_vars; ++i) {
1655 if (index_to_erase.count(i)) {
1659 ct->mutable_linear()->set_coeffs(new_size,
ct->linear().coeffs(i));
1660 ct->mutable_linear()->set_vars(new_size,
ct->linear().vars(i));
1663 ct->mutable_linear()->mutable_vars()->Truncate(new_size);
1664 ct->mutable_linear()->mutable_coeffs()->Truncate(new_size);
1666 DivideLinearByGcd(
ct);
1672 bool CpModelPresolver::AddVarAffineRepresentativeFromLinearEquality(
1673 int target_index, ConstraintProto*
ct) {
1675 const int num_variables =
ct->linear().vars().size();
1676 for (
int i = 0; i < num_variables; ++i) {
1677 if (i == target_index)
continue;
1678 const int64_t magnitude = std::abs(
ct->linear().coeffs(i));
1680 if (gcd == 1)
return false;
1686 const int ref =
ct->linear().vars(target_index);
1687 const int64_t coeff =
ct->linear().coeffs(target_index);
1688 const int64_t rhs =
ct->linear().domain(0);
1692 if (coeff % gcd == 0)
return false;
1700 return CanonicalizeLinear(
ct);
1710 bool CpModelPresolver::PresolveLinearEqualityWithModulo(ConstraintProto*
ct) {
1713 if (
ct->linear().domain().size() != 2)
return false;
1714 if (
ct->linear().domain(0) !=
ct->linear().domain(1))
return false;
1715 if (!
ct->enforcement_literal().empty())
return false;
1717 const int num_variables =
ct->linear().vars().size();
1718 if (num_variables < 2)
return false;
1720 std::vector<int> mod2_indices;
1721 std::vector<int> mod3_indices;
1722 std::vector<int> mod5_indices;
1724 int64_t min_magnitude;
1725 int num_smallest = 0;
1727 for (
int i = 0; i < num_variables; ++i) {
1728 const int64_t magnitude = std::abs(
ct->linear().coeffs(i));
1729 if (num_smallest == 0 || magnitude < min_magnitude) {
1730 min_magnitude = magnitude;
1733 }
else if (magnitude == min_magnitude) {
1737 if (magnitude % 2 != 0) mod2_indices.push_back(i);
1738 if (magnitude % 3 != 0) mod3_indices.push_back(i);
1739 if (magnitude % 5 != 0) mod5_indices.push_back(i);
1742 if (mod2_indices.size() == 2) {
1744 std::vector<int> literals;
1745 for (
const int i : mod2_indices) {
1746 const int ref =
ct->linear().vars(i);
1751 literals.push_back(ref);
1754 const int64_t rhs = std::abs(
ct->linear().domain(0));
1755 context_->
UpdateRuleStats(
"linear: only two odd Booleans in equality");
1767 if (mod2_indices.size() == 1) {
1768 return AddVarAffineRepresentativeFromLinearEquality(mod2_indices[0],
ct);
1770 if (mod3_indices.size() == 1) {
1771 return AddVarAffineRepresentativeFromLinearEquality(mod3_indices[0],
ct);
1773 if (mod5_indices.size() == 1) {
1774 return AddVarAffineRepresentativeFromLinearEquality(mod5_indices[0],
ct);
1776 if (num_smallest == 1) {
1777 return AddVarAffineRepresentativeFromLinearEquality(smallest_index,
ct);
1783 bool CpModelPresolver::PresolveLinearOfSizeOne(ConstraintProto*
ct) {
1789 ?
ct->linear().coeffs(0)
1790 : -
ct->linear().coeffs(0);
1795 rhs.InverseMultiplicationBy(coeff))) {
1798 return RemoveConstraint(
ct);
1804 const bool zero_ok = rhs.
Contains(0);
1805 const bool one_ok = rhs.Contains(
ct->linear().coeffs(0));
1807 if (!zero_ok && !one_ok) {
1808 return MarkConstraintAsFalse(
ct);
1810 if (zero_ok && one_ok) {
1811 return RemoveConstraint(
ct);
1813 const int ref =
ct->linear().vars(0);
1817 ct->mutable_bool_and()->add_literals(ref);
1828 if (
ct->linear().coeffs(0) == 1 &&
1831 context_->
UpdateRuleStats(
"linear1: remove abs from abs(x) in domain");
1832 const Domain implied_abs_target_domain =
1835 .IntersectionWith(context_->
DomainOf(
ct->linear().vars(0)));
1837 if (implied_abs_target_domain.IsEmpty()) {
1838 return MarkConstraintAsFalse(
ct);
1841 const Domain new_abs_var_domain =
1842 implied_abs_target_domain
1843 .UnionWith(implied_abs_target_domain.Negation())
1844 .IntersectionWith(context_->
DomainOf(abs_arg));
1846 if (new_abs_var_domain.IsEmpty()) {
1847 return MarkConstraintAsFalse(
ct);
1852 *new_ct->mutable_enforcement_literal() =
ct->enforcement_literal();
1853 auto* arg = new_ct->mutable_linear();
1854 arg->add_vars(abs_arg);
1858 return RemoveConstraint(
ct);
1862 if (
ct->enforcement_literal_size() != 1 ||
1863 (
ct->linear().coeffs(0) != 1 &&
ct->linear().coeffs(0) == -1)) {
1871 const int literal =
ct->enforcement_literal(0);
1872 const LinearConstraintProto& linear =
ct->linear();
1873 const int ref = linear.vars(0);
1875 const int64_t coeff =
1878 if (linear.domain_size() == 2 && linear.domain(0) == linear.domain(1)) {
1880 : -linear.domain(0) * coeff;
1891 if (complement.Size() != 1)
return false;
1893 : -complement.Min() * coeff;
1907 bool CpModelPresolver::PresolveLinearOfSizeTwo(ConstraintProto*
ct) {
1910 const LinearConstraintProto& arg =
ct->linear();
1911 const int var1 = arg.vars(0);
1912 const int var2 = arg.vars(1);
1913 const int64_t coeff1 = arg.coeffs(0);
1914 const int64_t coeff2 = arg.coeffs(1);
1925 const bool is_equality =
1926 arg.domain_size() == 2 && arg.domain(0) == arg.domain(1);
1929 int64_t value_on_true, coeff;
1932 value_on_true = coeff1;
1937 value_on_true = coeff2;
1945 const Domain rhs_if_true =
1948 const bool implied_false =
1950 const bool implied_true =
1952 if (implied_true && implied_false) {
1954 return MarkConstraintAsFalse(
ct);
1955 }
else if (implied_true) {
1956 context_->
UpdateRuleStats(
"linear2: Boolean with one feasible value.");
1961 new_ct->mutable_bool_and()->add_literals(lit);
1965 ct->mutable_linear()->Clear();
1966 ct->mutable_linear()->add_vars(
var);
1967 ct->mutable_linear()->add_coeffs(1);
1969 return PresolveLinearOfSizeOne(
ct) ||
true;
1970 }
else if (implied_false) {
1971 context_->
UpdateRuleStats(
"linear2: Boolean with one feasible value.");
1976 new_ct->mutable_bool_and()->add_literals(
NegatedRef(lit));
1980 ct->mutable_linear()->Clear();
1981 ct->mutable_linear()->add_vars(
var);
1982 ct->mutable_linear()->add_coeffs(1);
1984 return PresolveLinearOfSizeOne(
ct) ||
true;
1996 const int64_t rhs = arg.domain(0);
1997 if (
ct->enforcement_literal().empty()) {
2005 }
else if (coeff2 == 1) {
2007 }
else if (coeff1 == -1) {
2009 }
else if (coeff2 == -1) {
2022 if (added)
return RemoveConstraint(
ct);
2032 "linear2: implied ax + by = cte has no solutions");
2033 return MarkConstraintAsFalse(
ct);
2035 const Domain reduced_domain =
2043 if (reduced_domain.IsEmpty()) {
2045 "linear2: implied ax + by = cte has no solutions");
2046 return MarkConstraintAsFalse(
ct);
2049 if (reduced_domain.Size() == 1) {
2050 const int64_t z = reduced_domain.FixedValue();
2051 const int64_t value1 = x0 +
b * z;
2052 const int64_t value2 = y0 -
a * z;
2056 DCHECK_EQ(coeff1 * value1 + coeff2 * value2, rhs);
2060 imply1->mutable_linear()->add_vars(var1);
2061 imply1->mutable_linear()->add_coeffs(1);
2062 imply1->mutable_linear()->add_domain(value1);
2063 imply1->mutable_linear()->add_domain(value1);
2067 imply2->mutable_linear()->add_vars(var2);
2068 imply2->mutable_linear()->add_coeffs(1);
2069 imply2->mutable_linear()->add_domain(value2);
2070 imply2->mutable_linear()->add_domain(value2);
2072 "linear2: implied ax + by = cte has only one solution");
2074 return RemoveConstraint(
ct);
2081 bool CpModelPresolver::PresolveSmallLinear(ConstraintProto*
ct) {
2085 if (
ct->linear().vars().empty()) {
2088 if (rhs.Contains(0)) {
2089 return RemoveConstraint(
ct);
2091 return MarkConstraintAsFalse(
ct);
2093 }
else if (
ct->linear().vars().size() == 1) {
2094 return PresolveLinearOfSizeOne(
ct);
2095 }
else if (
ct->linear().vars().size() == 2) {
2096 return PresolveLinearOfSizeTwo(
ct);
2105 bool IsLeConstraint(
const Domain& domain,
const Domain& all_values) {
2109 .IsIncludedIn(domain);
2113 bool IsGeConstraint(
const Domain& domain,
const Domain& all_values) {
2117 .IsIncludedIn(domain);
2123 bool RhsCanBeFixedToMin(int64_t coeff,
const Domain& var_domain,
2124 const Domain& terms,
const Domain& rhs) {
2125 if (var_domain.NumIntervals() != 1)
return false;
2126 if (std::abs(coeff) != 1)
return false;
2134 if (coeff == 1 && terms.Max() + var_domain.Min() <= rhs.Min()) {
2137 if (coeff == -1 && terms.Max() - var_domain.Max() <= rhs.Min()) {
2143 bool RhsCanBeFixedToMax(int64_t coeff,
const Domain& var_domain,
2144 const Domain& terms,
const Domain& rhs) {
2145 if (var_domain.NumIntervals() != 1)
return false;
2146 if (std::abs(coeff) != 1)
return false;
2148 if (coeff == 1 && terms.Min() + var_domain.Max() >= rhs.Max()) {
2151 if (coeff == -1 && terms.Min() - var_domain.Min() >= rhs.Max()) {
2158 void TakeIntersectionWith(
const absl::flat_hash_set<int>& current,
2159 absl::flat_hash_set<int>* to_clear) {
2160 std::vector<int> new_set;
2161 for (
const int c : *to_clear) {
2162 if (current.contains(c)) new_set.push_back(c);
2165 for (
const int c : new_set) to_clear->insert(c);
2170 bool CpModelPresolver::DetectAndProcessOneSidedLinearConstraint(
2171 int c, ConstraintProto*
ct) {
2172 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear) {
2180 Domain implied_rhs(0);
2181 const int num_vars =
ct->linear().vars().size();
2182 for (
int i = 0; i < num_vars; ++i) {
2183 const int ref =
ct->linear().vars(i);
2184 const int64_t coeff =
ct->linear().coeffs(i);
2188 .RelaxIfTooComplex();
2193 if (implied_rhs.IsIncludedIn(old_rhs)) {
2195 return RemoveConstraint(
ct);
2199 const Domain rhs = old_rhs.SimplifyUsingImpliedDomain(implied_rhs);
2200 if (rhs.IsEmpty()) {
2202 return MarkConstraintAsFalse(
ct);
2204 if (rhs != old_rhs) {
2212 const bool is_le_constraint = IsLeConstraint(rhs, implied_rhs);
2213 const bool is_ge_constraint = IsGeConstraint(rhs, implied_rhs);
2214 if (!is_le_constraint && !is_ge_constraint)
return false;
2215 CHECK_NE(is_le_constraint, is_ge_constraint);
2222 absl::flat_hash_set<int> enforcement_set;
2224 for (
const int ref :
ct->enforcement_literal()) {
2225 enforcement_set.insert(ref);
2229 bool recanonicalize =
false;
2230 for (
int i = 0; i < num_vars; ++i) {
2231 const int var =
ct->linear().vars(i);
2232 const int64_t var_coeff =
ct->linear().coeffs(i);
2235 if ((var_coeff > 0) == is_ge_constraint) {
2249 const int64_t obj_coeff =
2258 if (obj_coeff <= 0 &&
2267 recanonicalize =
true;
2272 if (obj_coeff >= 0 &&
2281 recanonicalize =
true;
2287 if (recanonicalize)
return CanonicalizeLinear(
ct);
2291 bool CpModelPresolver::PropagateDomainsInLinear(
int ct_index,
2292 ConstraintProto*
ct) {
2293 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
2301 const int num_vars =
ct->linear().vars_size();
2302 term_domains.resize(num_vars + 1);
2303 left_domains.resize(num_vars + 1);
2304 left_domains[0] = Domain(0);
2305 for (
int i = 0; i < num_vars; ++i) {
2306 const int var =
ct->linear().vars(i);
2307 const int64_t coeff =
ct->linear().coeffs(i);
2310 left_domains[i + 1] =
2313 const Domain& implied_rhs = left_domains[num_vars];
2317 if (implied_rhs.IsIncludedIn(old_rhs)) {
2319 return RemoveConstraint(
ct);
2323 Domain rhs = old_rhs.SimplifyUsingImpliedDomain(implied_rhs);
2324 if (rhs.IsEmpty()) {
2326 return MarkConstraintAsFalse(
ct);
2328 if (rhs != old_rhs) {
2334 if (
ct->enforcement_literal().size() > 1)
return false;
2336 bool new_bounds =
false;
2337 bool recanonicalize =
false;
2338 Domain negated_rhs = rhs.Negation();
2339 Domain right_domain(0);
2341 Domain implied_term_domain;
2342 term_domains[num_vars] = Domain(0);
2343 for (
int i = num_vars - 1; i >= 0; --i) {
2344 const int var =
ct->linear().vars(i);
2345 const int64_t var_coeff =
ct->linear().coeffs(i);
2347 right_domain.AdditionWith(term_domains[i + 1]).RelaxIfTooComplex();
2348 implied_term_domain = left_domains[i].AdditionWith(right_domain);
2349 new_domain = implied_term_domain.AdditionWith(negated_rhs)
2350 .InverseMultiplicationBy(-var_coeff);
2352 if (
ct->enforcement_literal().empty()) {
2357 }
else if (
ct->enforcement_literal().size() == 1) {
2368 recanonicalize =
true;
2373 if (!
ct->enforcement_literal().empty())
continue;
2385 if (rhs.Min() != rhs.Max() &&
2388 const bool same_sign = (var_coeff > 0) == (obj_coeff > 0);
2390 if (same_sign && RhsCanBeFixedToMin(var_coeff, context_->
DomainOf(
var),
2391 implied_term_domain, rhs)) {
2392 rhs = Domain(rhs.Min());
2395 if (!same_sign && RhsCanBeFixedToMax(var_coeff, context_->
DomainOf(
var),
2396 implied_term_domain, rhs)) {
2397 rhs = Domain(rhs.Max());
2403 negated_rhs = rhs.Negation();
2407 right_domain = Domain(0);
2421 if (
ct->linear().vars().size() <= 2)
continue;
2426 if (rhs.Min() != rhs.Max())
continue;
2432 if (context_->
DomainOf(
var) != new_domain)
continue;
2433 if (std::abs(var_coeff) != 1)
continue;
2440 bool is_in_objective =
false;
2442 is_in_objective =
true;
2448 if (is_in_objective) col_size--;
2449 const int row_size =
ct->linear().vars_size();
2453 const int num_entries_added = (row_size - 1) * (col_size - 1);
2454 const int num_entries_removed = col_size + row_size - 1;
2456 if (num_entries_added > num_entries_removed) {
2462 std::vector<int> others;
2470 if (c == ct_index)
continue;
2472 ConstraintProto::ConstraintCase::kLinear) {
2476 for (
const int ref :
2483 others.push_back(c);
2485 if (abort)
continue;
2488 for (
const int c : others) {
2507 if (is_in_objective &&
2513 absl::StrCat(
"linear: variable substitution ", others.size()));
2526 LinearConstraintProto* mapping_linear_ct =
2529 std::swap(mapping_linear_ct->mutable_vars()->at(0),
2530 mapping_linear_ct->mutable_vars()->at(i));
2531 std::swap(mapping_linear_ct->mutable_coeffs()->at(0),
2532 mapping_linear_ct->mutable_coeffs()->at(i));
2533 return RemoveConstraint(
ct);
2538 if (recanonicalize)
return CanonicalizeLinear(
ct);
2549 void CpModelPresolver::ExtractEnforcementLiteralFromLinearConstraint(
2550 int ct_index, ConstraintProto*
ct) {
2551 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
2556 const LinearConstraintProto& arg =
ct->linear();
2557 const int num_vars = arg.vars_size();
2561 if (num_vars <= 1)
return;
2563 int64_t min_sum = 0;
2564 int64_t max_sum = 0;
2565 int64_t max_coeff_magnitude = 0;
2566 for (
int i = 0; i < num_vars; ++i) {
2567 const int ref = arg.vars(i);
2568 const int64_t coeff = arg.coeffs(i);
2569 const int64_t term_a = coeff * context_->
MinOf(ref);
2570 const int64_t term_b = coeff * context_->
MaxOf(ref);
2571 max_coeff_magnitude =
std::max(max_coeff_magnitude, std::abs(coeff));
2572 min_sum +=
std::min(term_a, term_b);
2573 max_sum +=
std::max(term_a, term_b);
2582 const auto& domain =
ct->linear().domain();
2583 const int64_t ub_threshold = domain[domain.size() - 2] - min_sum;
2584 const int64_t lb_threshold = max_sum - domain[1];
2586 if (max_coeff_magnitude <
std::max(ub_threshold, lb_threshold))
return;
2605 const bool lower_bounded = min_sum < rhs_domain.
Min();
2606 const bool upper_bounded = max_sum > rhs_domain.Max();
2607 if (!lower_bounded && !upper_bounded)
return;
2608 if (lower_bounded && upper_bounded) {
2612 if (!
ct->name().empty()) {
2613 new_ct1->set_name(absl::StrCat(
ct->name(),
" (part 1)"));
2616 new_ct1->mutable_linear());
2620 if (!
ct->name().empty()) {
2621 new_ct2->set_name(absl::StrCat(
ct->name(),
" (part 2)"));
2624 new_ct2->mutable_linear());
2627 return (
void)RemoveConstraint(
ct);
2633 const int64_t threshold = lower_bounded ? ub_threshold : lb_threshold;
2640 const bool only_booleans =
2647 int64_t rhs_offset = 0;
2648 bool some_integer_encoding_were_extracted =
false;
2649 LinearConstraintProto* mutable_arg =
ct->mutable_linear();
2650 for (
int i = 0; i < arg.vars_size(); ++i) {
2651 int ref = arg.vars(i);
2652 int64_t coeff = arg.coeffs(i);
2659 if (context_->
IsFixed(ref) || coeff < threshold ||
2660 (only_booleans && !is_boolean)) {
2662 mutable_arg->set_vars(new_size, mutable_arg->vars(i));
2663 mutable_arg->set_coeffs(new_size, mutable_arg->coeffs(i));
2671 some_integer_encoding_were_extracted =
true;
2673 "linear: extracted integer enforcement literal");
2675 if (lower_bounded) {
2676 ct->add_enforcement_literal(is_boolean
2679 ref, context_->
MinOf(ref)));
2680 rhs_offset -= coeff * context_->
MinOf(ref);
2682 ct->add_enforcement_literal(is_boolean
2685 ref, context_->
MaxOf(ref)));
2686 rhs_offset -= coeff * context_->
MaxOf(ref);
2689 mutable_arg->mutable_vars()->Truncate(new_size);
2690 mutable_arg->mutable_coeffs()->Truncate(new_size);
2692 if (some_integer_encoding_were_extracted) {
2698 void CpModelPresolver::ExtractAtMostOneFromLinear(ConstraintProto*
ct) {
2703 const LinearConstraintProto& arg =
ct->linear();
2704 const int num_vars = arg.vars_size();
2705 int64_t min_sum = 0;
2706 int64_t max_sum = 0;
2707 for (
int i = 0; i < num_vars; ++i) {
2708 const int ref = arg.vars(i);
2709 const int64_t coeff = arg.coeffs(i);
2710 const int64_t term_a = coeff * context_->
MinOf(ref);
2711 const int64_t term_b = coeff * context_->
MaxOf(ref);
2712 min_sum +=
std::min(term_a, term_b);
2713 max_sum +=
std::max(term_a, term_b);
2715 for (
const int type : {0, 1}) {
2716 std::vector<int> at_most_one;
2717 for (
int i = 0; i < num_vars; ++i) {
2718 const int ref = arg.vars(i);
2719 const int64_t coeff = arg.coeffs(i);
2720 if (context_->
MinOf(ref) != 0)
continue;
2721 if (context_->
MaxOf(ref) != 1)
continue;
2726 if (min_sum + 2 * std::abs(coeff) > rhs.Max()) {
2727 at_most_one.push_back(coeff > 0 ? ref :
NegatedRef(ref));
2730 if (max_sum - 2 * std::abs(coeff) < rhs.Min()) {
2731 at_most_one.push_back(coeff > 0 ?
NegatedRef(ref) : ref);
2735 if (at_most_one.size() > 1) {
2743 for (
const int ref : at_most_one) {
2744 new_ct->mutable_at_most_one()->add_literals(ref);
2753 bool CpModelPresolver::PresolveLinearOnBooleans(ConstraintProto*
ct) {
2754 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
2759 const LinearConstraintProto& arg =
ct->linear();
2760 const int num_vars = arg.vars_size();
2762 int64_t max_coeff = 0;
2763 int64_t min_sum = 0;
2764 int64_t max_sum = 0;
2765 for (
int i = 0; i < num_vars; ++i) {
2767 const int var = arg.vars(i);
2768 const int64_t coeff = arg.coeffs(i);
2771 if (context_->
MinOf(
var) != 0)
return false;
2772 if (context_->
MaxOf(
var) != 1)
return false;
2776 min_coeff =
std::min(min_coeff, coeff);
2777 max_coeff =
std::max(max_coeff, coeff);
2781 min_coeff =
std::min(min_coeff, -coeff);
2782 max_coeff =
std::max(max_coeff, -coeff);
2794 if ((!rhs_domain.Contains(min_sum) &&
2795 min_sum + min_coeff > rhs_domain.Max()) ||
2796 (!rhs_domain.Contains(max_sum) &&
2797 max_sum - min_coeff < rhs_domain.Min())) {
2798 context_->
UpdateRuleStats(
"linear: all booleans and trivially false");
2799 return MarkConstraintAsFalse(
ct);
2801 if (Domain(min_sum, max_sum).IsIncludedIn(rhs_domain)) {
2803 return RemoveConstraint(
ct);
2810 DCHECK(!rhs_domain.IsEmpty());
2811 if (min_sum + min_coeff > rhs_domain.Max()) {
2814 const auto copy = arg;
2815 ct->mutable_bool_and()->clear_literals();
2816 for (
int i = 0; i < num_vars; ++i) {
2817 ct->mutable_bool_and()->add_literals(
2818 copy.coeffs(i) > 0 ?
NegatedRef(copy.vars(i)) : copy.vars(i));
2820 PresolveBoolAnd(
ct);
2822 }
else if (max_sum - min_coeff < rhs_domain.Min()) {
2825 const auto copy = arg;
2826 ct->mutable_bool_and()->clear_literals();
2827 for (
int i = 0; i < num_vars; ++i) {
2828 ct->mutable_bool_and()->add_literals(
2829 copy.coeffs(i) > 0 ? copy.vars(i) :
NegatedRef(copy.vars(i)));
2831 PresolveBoolAnd(
ct);
2833 }
else if (min_sum + min_coeff >= rhs_domain.Min() &&
2834 rhs_domain.front().end >= max_sum) {
2837 const auto copy = arg;
2838 ct->mutable_bool_or()->clear_literals();
2839 for (
int i = 0; i < num_vars; ++i) {
2840 ct->mutable_bool_or()->add_literals(
2841 copy.coeffs(i) > 0 ? copy.vars(i) :
NegatedRef(copy.vars(i)));
2845 }
else if (max_sum - min_coeff <= rhs_domain.Max() &&
2846 rhs_domain.back().start <= min_sum) {
2849 const auto copy = arg;
2850 ct->mutable_bool_or()->clear_literals();
2851 for (
int i = 0; i < num_vars; ++i) {
2852 ct->mutable_bool_or()->add_literals(
2853 copy.coeffs(i) > 0 ?
NegatedRef(copy.vars(i)) : copy.vars(i));
2858 min_sum + max_coeff <= rhs_domain.Max() &&
2859 min_sum + 2 * min_coeff > rhs_domain.Max() &&
2860 rhs_domain.back().start <= min_sum) {
2863 const auto copy = arg;
2864 ct->mutable_at_most_one()->clear_literals();
2865 for (
int i = 0; i < num_vars; ++i) {
2866 ct->mutable_at_most_one()->add_literals(
2867 copy.coeffs(i) > 0 ? copy.vars(i) :
NegatedRef(copy.vars(i)));
2871 max_sum - max_coeff >= rhs_domain.Min() &&
2872 max_sum - 2 * min_coeff < rhs_domain.Min() &&
2873 rhs_domain.front().end >= max_sum) {
2876 const auto copy = arg;
2877 ct->mutable_at_most_one()->clear_literals();
2878 for (
int i = 0; i < num_vars; ++i) {
2879 ct->mutable_at_most_one()->add_literals(
2880 copy.coeffs(i) > 0 ?
NegatedRef(copy.vars(i)) : copy.vars(i));
2884 min_sum < rhs_domain.Min() &&
2885 min_sum + min_coeff >= rhs_domain.Min() &&
2886 min_sum + 2 * min_coeff > rhs_domain.Max() &&
2887 min_sum + max_coeff <= rhs_domain.Max()) {
2891 for (
int i = 0; i < num_vars; ++i) {
2892 exactly_one->mutable_exactly_one()->add_literals(
2893 arg.coeffs(i) > 0 ? arg.vars(i) :
NegatedRef(arg.vars(i)));
2896 return RemoveConstraint(
ct);
2898 max_sum > rhs_domain.Max() &&
2899 max_sum - min_coeff <= rhs_domain.Max() &&
2900 max_sum - 2 * min_coeff < rhs_domain.Min() &&
2901 max_sum - max_coeff >= rhs_domain.Min()) {
2905 for (
int i = 0; i < num_vars; ++i) {
2906 exactly_one->mutable_exactly_one()->add_literals(
2907 arg.coeffs(i) > 0 ?
NegatedRef(arg.vars(i)) : arg.vars(i));
2910 return RemoveConstraint(
ct);
2917 if (num_vars > 3)
return false;
2922 const int max_mask = (1 << arg.vars_size());
2923 for (
int mask = 0; mask < max_mask; ++mask) {
2925 for (
int i = 0; i < num_vars; ++i) {
2926 if ((mask >> i) & 1)
value += arg.coeffs(i);
2928 if (rhs_domain.Contains(
value))
continue;
2934 *new_ct->mutable_enforcement_literal() =
ct->enforcement_literal();
2936 for (
int i = 0; i < num_vars; ++i) {
2937 new_arg->add_literals(((mask >> i) & 1) ?
NegatedRef(arg.vars(i))
2943 return RemoveConstraint(
ct);
2946 bool CpModelPresolver::PresolveInterval(
int c, ConstraintProto*
ct) {
2948 IntervalConstraintProto*
interval =
ct->mutable_interval();
2951 if (!
ct->enforcement_literal().empty() && context_->
SizeMax(c) < 0) {
2952 context_->
UpdateRuleStats(
"interval: negative size implies unperformed");
2953 return MarkConstraintAsFalse(
ct);
2956 bool changed =
false;
2957 if (
ct->enforcement_literal().empty()) {
2965 "interval: performed intervals must have a positive size");
2968 changed |= CanonicalizeLinearExpression(*
ct,
interval->mutable_start());
2969 changed |= CanonicalizeLinearExpression(*
ct,
interval->mutable_size());
2970 changed |= CanonicalizeLinearExpression(*
ct,
interval->mutable_end());
2975 bool CpModelPresolver::PresolveInverse(ConstraintProto*
ct) {
2976 const int size =
ct->inverse().f_direct().size();
2977 bool changed =
false;
2980 for (
const int ref :
ct->inverse().f_direct()) {
2982 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
2986 for (
const int ref :
ct->inverse().f_inverse()) {
2988 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
2998 absl::flat_hash_set<int> direct_vars;
2999 for (
const int ref :
ct->inverse().f_direct()) {
3000 const auto [it, inserted] = direct_vars.insert(
PositiveRef(ref));
3006 absl::flat_hash_set<int> inverse_vars;
3007 for (
const int ref :
ct->inverse().f_inverse()) {
3008 const auto [it, inserted] = inverse_vars.insert(
PositiveRef(ref));
3018 const auto filter_inverse_domain =
3019 [
this, size, &changed](
const auto& direct,
const auto& inverse) {
3021 std::vector<absl::flat_hash_set<int64_t>> inverse_values(size);
3022 for (
int i = 0; i < size; ++i) {
3023 const Domain domain = context_->
DomainOf(inverse[i]);
3024 for (
const int64_t j : domain.Values()) {
3025 inverse_values[i].insert(j);
3032 std::vector<int64_t> possible_values;
3033 for (
int i = 0; i < size; ++i) {
3034 possible_values.clear();
3035 const Domain domain = context_->
DomainOf(direct[i]);
3036 bool removed_value =
false;
3037 for (
const int64_t j : domain.Values()) {
3038 if (inverse_values[j].contains(i)) {
3039 possible_values.push_back(j);
3041 removed_value =
true;
3044 if (removed_value) {
3048 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
3056 if (!filter_inverse_domain(
ct->inverse().f_direct(),
3057 ct->inverse().f_inverse())) {
3061 if (!filter_inverse_domain(
ct->inverse().f_inverse(),
3062 ct->inverse().f_direct())) {
3073 bool CpModelPresolver::PresolveElement(ConstraintProto*
ct) {
3076 if (
ct->element().vars().empty()) {
3081 const int index_ref =
ct->element().index();
3082 const int target_ref =
ct->element().target();
3087 bool all_constants =
true;
3088 absl::flat_hash_set<int64_t> constant_set;
3089 bool all_included_in_target_domain =
true;
3092 bool reduced_index_domain =
false;
3094 Domain(0,
ct->element().vars_size() - 1),
3095 &reduced_index_domain)) {
3103 std::vector<int64_t> possible_indices;
3104 const Domain& index_domain = context_->
DomainOf(index_ref);
3105 for (
const int64_t index_value : index_domain.Values()) {
3106 const int ref =
ct->element().vars(index_value);
3107 const int64_t target_value =
3108 target_ref == index_ref ? index_value : -index_value;
3110 possible_indices.push_back(target_value);
3113 if (possible_indices.size() < index_domain.Size()) {
3119 "element: reduced index domain when target equals index");
3125 Domain infered_domain;
3126 const Domain& initial_index_domain = context_->
DomainOf(index_ref);
3127 const Domain& target_domain = context_->
DomainOf(target_ref);
3128 std::vector<int64_t> possible_indices;
3129 for (
const int64_t
value : initial_index_domain.Values()) {
3132 const int ref =
ct->element().vars(
value);
3133 const Domain& domain = context_->
DomainOf(ref);
3134 if (domain.IntersectionWith(target_domain).IsEmpty())
continue;
3135 possible_indices.push_back(
value);
3136 if (domain.IsFixed()) {
3137 constant_set.insert(domain.Min());
3139 all_constants =
false;
3141 if (!domain.IsIncludedIn(target_domain)) {
3142 all_included_in_target_domain =
false;
3144 infered_domain = infered_domain.UnionWith(domain);
3146 if (possible_indices.size() < initial_index_domain.Size()) {
3153 bool domain_modified =
false;
3155 &domain_modified)) {
3158 if (domain_modified) {
3164 if (context_->
IsFixed(index_ref)) {
3165 const int var =
ct->element().vars(context_->
MinOf(index_ref));
3166 if (
var != target_ref) {
3167 LinearConstraintProto*
const lin =
3170 lin->add_coeffs(-1);
3171 lin->add_vars(target_ref);
3178 return RemoveConstraint(
ct);
3184 if (all_constants && constant_set.size() == 1) {
3187 return RemoveConstraint(
ct);
3192 if (context_->
MinOf(index_ref) == 0 && context_->
MaxOf(index_ref) == 1 &&
3194 const int64_t v0 = context_->
MinOf(
ct->element().vars(0));
3195 const int64_t v1 = context_->
MinOf(
ct->element().vars(1));
3197 LinearConstraintProto*
const lin =
3201 lin->add_vars(index_ref);
3202 lin->add_coeffs(v0 - v1);
3203 lin->add_domain(v0);
3204 lin->add_domain(v0);
3206 context_->
UpdateRuleStats(
"element: linearize constant element of size 2");
3207 return RemoveConstraint(
ct);
3211 const AffineRelation::Relation r_index =
3213 if (r_index.representative != index_ref) {
3215 if (context_->
DomainOf(r_index.representative).
Size() >
3221 const int64_t r_min = context_->
MinOf(r_ref);
3222 const int64_t r_max = context_->
MaxOf(r_ref);
3223 const int array_size =
ct->element().vars_size();
3225 context_->
UpdateRuleStats(
"TODO element: representative has bad domain");
3226 }
else if (r_index.offset >= 0 && r_index.offset < array_size &&
3227 r_index.offset + r_max * r_index.coeff >= 0 &&
3228 r_index.offset + r_max * r_index.coeff < array_size) {
3230 ElementConstraintProto*
const element =
3232 for (int64_t v = 0; v <= r_max; ++v) {
3233 const int64_t scaled_index = v * r_index.coeff + r_index.offset;
3235 CHECK_LT(scaled_index, array_size);
3236 element->add_vars(
ct->element().vars(scaled_index));
3238 element->set_index(r_ref);
3239 element->set_target(target_ref);
3241 if (r_index.coeff == 1) {
3247 return RemoveConstraint(
ct);
3258 absl::flat_hash_map<int, int> local_var_occurrence_counter;
3259 local_var_occurrence_counter[
PositiveRef(index_ref)]++;
3260 local_var_occurrence_counter[
PositiveRef(target_ref)]++;
3266 const int ref =
ct->element().vars(
value);
3272 local_var_occurrence_counter.at(
PositiveRef(index_ref)) == 1) {
3273 if (all_constants) {
3277 context_->
UpdateRuleStats(
"element: trivial target domain reduction");
3280 return RemoveConstraint(
ct);
3286 if (!context_->
IsFixed(target_ref) &&
3288 local_var_occurrence_counter.at(
PositiveRef(target_ref)) == 1) {
3289 if (all_included_in_target_domain) {
3293 return RemoveConstraint(
ct);
3302 bool CpModelPresolver::PresolveTable(ConstraintProto*
ct) {
3305 if (
ct->table().vars().empty()) {
3307 return RemoveConstraint(
ct);
3310 const int initial_num_vars =
ct->table().vars_size();
3311 bool changed =
true;
3314 std::vector<AffineRelation::Relation> affine_relations;
3315 std::vector<int64_t> old_var_lb;
3316 std::vector<int64_t> old_var_ub;
3318 for (
int v = 0; v < initial_num_vars; ++v) {
3319 const int ref =
ct->table().vars(v);
3321 affine_relations.push_back(r);
3322 old_var_lb.push_back(context_->
MinOf(ref));
3323 old_var_ub.push_back(context_->
MaxOf(ref));
3324 if (r.representative != ref) {
3326 ct->mutable_table()->set_vars(v, r.representative);
3328 "table: replace variable by canonical affine one");
3337 std::vector<int> old_index_of_duplicate_to_new_index_of_first_occurrence(
3338 initial_num_vars, -1);
3340 std::vector<int> old_index_to_new_index(initial_num_vars, -1);
3343 absl::flat_hash_map<int, int> first_visit;
3344 for (
int p = 0; p < initial_num_vars; ++p) {
3345 const int ref =
ct->table().vars(p);
3347 const auto& it = first_visit.find(
var);
3348 if (it != first_visit.end()) {
3349 const int previous = it->second;
3350 old_index_of_duplicate_to_new_index_of_first_occurrence[p] = previous;
3354 ct->mutable_table()->set_vars(num_vars, ref);
3355 first_visit[
var] = num_vars;
3356 old_index_to_new_index[p] = num_vars;
3361 if (num_vars < initial_num_vars) {
3362 ct->mutable_table()->mutable_vars()->Truncate(num_vars);
3369 std::vector<std::vector<int64_t>> new_tuples;
3370 const int initial_num_tuples =
ct->table().values_size() / initial_num_vars;
3371 std::vector<absl::flat_hash_set<int64_t>> new_domains(num_vars);
3374 std::vector<int64_t> tuple(num_vars);
3375 new_tuples.reserve(initial_num_tuples);
3376 for (
int i = 0; i < initial_num_tuples; ++i) {
3377 bool delete_row =
false;
3379 for (
int j = 0; j < initial_num_vars; ++j) {
3380 const int64_t old_value =
ct->table().values(i * initial_num_vars + j);
3384 if (old_value < old_var_lb[j] || old_value > old_var_ub[j]) {
3390 const AffineRelation::Relation& r = affine_relations[j];
3391 const int64_t
value = (old_value - r.offset) / r.coeff;
3392 if (
value * r.coeff + r.offset != old_value) {
3397 const int mapped_position = old_index_to_new_index[j];
3398 if (mapped_position == -1) {
3399 const int new_index_of_first_occurrence =
3400 old_index_of_duplicate_to_new_index_of_first_occurrence[j];
3401 if (
value != tuple[new_index_of_first_occurrence]) {
3406 const int ref =
ct->table().vars(mapped_position);
3411 tuple[mapped_position] =
value;
3418 new_tuples.push_back(tuple);
3419 for (
int j = 0; j < num_vars; ++j) {
3420 new_domains[j].insert(tuple[j]);
3424 if (new_tuples.size() < initial_num_tuples) {
3431 ct->mutable_table()->clear_values();
3432 for (
const std::vector<int64_t>& t : new_tuples) {
3433 for (
const int64_t v : t) {
3434 ct->mutable_table()->add_values(v);
3440 if (
ct->table().negated())
return changed;
3443 for (
int j = 0; j < num_vars; ++j) {
3444 const int ref =
ct->table().vars(j);
3448 new_domains[j].end())),
3456 if (num_vars == 1) {
3459 return RemoveConstraint(
ct);
3464 for (
int j = 0; j < num_vars; ++j) prod *= new_domains[j].size();
3465 if (prod == new_tuples.size()) {
3467 return RemoveConstraint(
ct);
3473 if (new_tuples.size() > 0.7 * prod) {
3475 std::vector<std::vector<int64_t>> var_to_values(num_vars);
3476 for (
int j = 0; j < num_vars; ++j) {
3477 var_to_values[j].assign(new_domains[j].begin(), new_domains[j].end());
3479 std::vector<std::vector<int64_t>> all_tuples(prod);
3480 for (
int i = 0; i < prod; ++i) {
3481 all_tuples[i].resize(num_vars);
3483 for (
int j = 0; j < num_vars; ++j) {
3484 all_tuples[i][j] = var_to_values[j][
index % var_to_values[j].size()];
3485 index /= var_to_values[j].size();
3491 std::vector<std::vector<int64_t>> diff(prod - new_tuples.size());
3492 std::set_difference(all_tuples.begin(), all_tuples.end(),
3493 new_tuples.begin(), new_tuples.end(), diff.begin());
3496 ct->mutable_table()->set_negated(!
ct->table().negated());
3497 ct->mutable_table()->clear_values();
3498 for (
const std::vector<int64_t>& t : diff) {
3499 for (
const int64_t v : t)
ct->mutable_table()->add_values(v);
3506 bool CpModelPresolver::PresolveAllDiff(ConstraintProto*
ct) {
3510 AllDifferentConstraintProto& all_diff = *
ct->mutable_all_diff();
3512 bool constraint_has_changed =
false;
3513 for (LinearExpressionProto& exp :
3514 *(
ct->mutable_all_diff()->mutable_exprs())) {
3515 constraint_has_changed |= CanonicalizeLinearExpression(*
ct, &exp);
3519 const int size = all_diff.exprs_size();
3522 return RemoveConstraint(
ct);
3526 return RemoveConstraint(
ct);
3529 bool something_was_propagated =
false;
3530 std::vector<LinearExpressionProto> kept_expressions;
3531 for (
int i = 0; i < size; ++i) {
3532 if (!context_->
IsFixed(all_diff.exprs(i))) {
3533 kept_expressions.push_back(all_diff.exprs(i));
3537 const int64_t
value = context_->
MinOf(all_diff.exprs(i));
3538 bool propagated =
false;
3539 for (
int j = 0; j < size; ++j) {
3540 if (i == j)
continue;
3543 Domain(
value).Complement())) {
3551 something_was_propagated =
true;
3558 kept_expressions.begin(), kept_expressions.end(),
3559 [](
const LinearExpressionProto& expr_a,
3560 const LinearExpressionProto& expr_b) {
3563 const int ref_a = expr_a.vars(0);
3564 const int ref_b = expr_b.vars(0);
3565 const int64_t coeff_a = expr_a.coeffs(0);
3566 const int64_t coeff_b = expr_b.coeffs(0);
3567 const int64_t abs_coeff_a = std::abs(coeff_a);
3568 const int64_t abs_coeff_b = std::abs(coeff_b);
3569 const int64_t offset_a = expr_a.offset();
3570 const int64_t offset_b = expr_b.offset();
3571 const int64_t abs_offset_a = std::abs(offset_a);
3572 const int64_t abs_offset_b = std::abs(offset_b);
3573 return std::tie(ref_a, abs_coeff_a, coeff_a, abs_offset_a, offset_a) <
3574 std::tie(ref_b, abs_coeff_b, coeff_b, abs_offset_b, offset_b);
3580 for (
int i = 1; i < kept_expressions.size(); ++i) {
3582 kept_expressions[i - 1], 1)) {
3584 "Duplicate variable in all_diff");
3587 kept_expressions[i - 1], -1)) {
3588 bool domain_modified =
false;
3590 Domain(0).Complement(),
3591 &domain_modified)) {
3594 if (domain_modified) {
3596 "all_diff: remove 0 from expression appearing with its " 3602 if (kept_expressions.size() < all_diff.exprs_size()) {
3603 all_diff.clear_exprs();
3604 for (
const LinearExpressionProto& expr : kept_expressions) {
3605 *all_diff.add_exprs() = expr;
3608 something_was_propagated =
true;
3609 constraint_has_changed =
true;
3610 if (kept_expressions.size() <= 1)
continue;
3614 CHECK_GE(all_diff.exprs_size(), 2);
3616 for (
int i = 1; i < all_diff.exprs_size(); ++i) {
3619 if (all_diff.exprs_size() == domain.Size()) {
3620 absl::flat_hash_map<int64_t, std::vector<LinearExpressionProto>>
3622 for (
const LinearExpressionProto& expr : all_diff.exprs()) {
3623 for (
const int64_t v : context_->
DomainOf(expr.vars(0)).Values()) {
3624 value_to_exprs[expr.coeffs(0) * v + expr.offset()].push_back(expr);
3627 bool propagated =
false;
3628 for (
const auto& it : value_to_exprs) {
3629 if (it.second.size() == 1 && !context_->
IsFixed(it.second.front())) {
3630 const LinearExpressionProto& expr = it.second.
front();
3639 "all_diff: propagated mandatory values in permutation");
3640 something_was_propagated =
true;
3643 if (!something_was_propagated)
break;
3646 return constraint_has_changed;
3653 std::vector<int> GetLiteralsFromSetPPCConstraint(
const ConstraintProto&
ct) {
3654 std::vector<int> sorted_literals;
3656 for (
const int literal :
ct.at_most_one().literals()) {
3657 sorted_literals.push_back(
literal);
3660 for (
const int literal :
ct.bool_or().literals()) {
3661 sorted_literals.push_back(
literal);
3664 for (
const int literal :
ct.exactly_one().literals()) {
3665 sorted_literals.push_back(
literal);
3668 std::sort(sorted_literals.begin(), sorted_literals.end());
3669 return sorted_literals;
3674 void AddImplication(
int lhs,
int rhs, CpModelProto*
proto,
3675 absl::flat_hash_map<int, int>* ref_to_bool_and) {
3676 if (ref_to_bool_and->contains(lhs)) {
3677 const int ct_index = (*ref_to_bool_and)[lhs];
3679 }
else if (ref_to_bool_and->contains(
NegatedRef(rhs))) {
3680 const int ct_index = (*ref_to_bool_and)[
NegatedRef(rhs)];
3686 ct->add_enforcement_literal(lhs);
3687 ct->mutable_bool_and()->add_literals(rhs);
3691 template <
typename ClauseContainer>
3692 void ExtractClauses(
bool use_bool_and,
const ClauseContainer& container,
3693 CpModelProto*
proto) {
3700 absl::flat_hash_map<int, int> ref_to_bool_and;
3701 for (
int i = 0; i < container.NumClauses(); ++i) {
3702 const std::vector<Literal>& clause = container.Clause(i);
3703 if (clause.empty())
continue;
3706 if (use_bool_and && clause.size() == 2) {
3707 const int a = clause[0].IsPositive()
3708 ? clause[0].Variable().value()
3710 const int b = clause[1].IsPositive()
3711 ? clause[1].Variable().value()
3719 for (
const Literal l : clause) {
3720 if (l.IsPositive()) {
3721 ct->mutable_bool_or()->add_literals(l.Variable().value());
3723 ct->mutable_bool_or()->add_literals(
NegatedRef(l.Variable().value()));
3731 bool CpModelPresolver::PresolveNoOverlap(ConstraintProto*
ct) {
3733 NoOverlapConstraintProto*
proto =
ct->mutable_no_overlap();
3734 bool changed =
false;
3738 const int initial_num_intervals =
proto->intervals_size();
3741 for (
int i = 0; i < initial_num_intervals; ++i) {
3742 const int interval_index =
proto->intervals(i);
3745 proto->set_intervals(new_size++, interval_index);
3748 if (new_size < initial_num_intervals) {
3749 proto->mutable_intervals()->Truncate(new_size);
3756 if (
proto->intervals_size() > 1) {
3757 std::vector<IndexedInterval> indexed_intervals;
3758 for (
int i = 0; i <
proto->intervals().size(); ++i) {
3760 indexed_intervals.push_back({
index,
3764 std::vector<std::vector<int>> components;
3767 if (components.size() > 1) {
3768 for (
const std::vector<int>& intervals : components) {
3769 if (intervals.size() <= 1)
continue;
3771 NoOverlapConstraintProto* new_no_overlap =
3775 for (
const int i : intervals) {
3780 context_->
UpdateRuleStats(
"no_overlap: split into disjoint components");
3781 return RemoveConstraint(
ct);
3785 std::vector<int> constant_intervals;
3786 int64_t size_min_of_non_constant_intervals =
3788 for (
int i = 0; i <
proto->intervals_size(); ++i) {
3789 const int interval_index =
proto->intervals(i);
3791 constant_intervals.push_back(interval_index);
3793 size_min_of_non_constant_intervals =
3794 std::min(size_min_of_non_constant_intervals,
3795 context_->
SizeMin(interval_index));
3799 bool move_constraint_last =
false;
3800 if (!constant_intervals.empty()) {
3802 std::sort(constant_intervals.begin(), constant_intervals.end(),
3803 [
this](
int i1,
int i2) {
3804 const int64_t s1 = context_->
StartMin(i1);
3805 const int64_t e1 = context_->
EndMax(i1);
3806 const int64_t s2 = context_->
StartMin(i2);
3807 const int64_t e2 = context_->
EndMax(i2);
3808 return std::tie(s1, e1) < std::tie(s2, e2);
3814 for (
int i = 0; i + 1 < constant_intervals.size(); ++i) {
3815 if (context_->
EndMax(constant_intervals[i]) >
3816 context_->
StartMin(constant_intervals[i + 1])) {
3822 if (constant_intervals.size() ==
proto->intervals_size()) {
3824 return RemoveConstraint(
ct);
3827 absl::flat_hash_set<int> intervals_to_remove;
3831 for (
int i = 0; i + 1 < constant_intervals.size(); ++i) {
3832 const int start = i;
3833 while (i + 1 < constant_intervals.size() &&
3834 context_->
StartMin(constant_intervals[i + 1]) -
3835 context_->
EndMax(constant_intervals[i]) <
3836 size_min_of_non_constant_intervals) {
3839 if (i == start)
continue;
3840 for (
int j = start; j <= i; ++j) {
3841 intervals_to_remove.insert(constant_intervals[j]);
3843 const int64_t new_start = context_->
StartMin(constant_intervals[start]);
3844 const int64_t new_end = context_->
EndMax(constant_intervals[i]);
3846 IntervalConstraintProto* new_interval =
3849 new_interval->mutable_size()->set_offset(new_end - new_start);
3850 new_interval->mutable_end()->set_offset(new_end);
3851 move_constraint_last =
true;
3855 if (!intervals_to_remove.empty()) {
3857 const int old_size =
proto->intervals_size();
3858 for (
int i = 0; i < old_size; ++i) {
3859 const int interval_index =
proto->intervals(i);
3860 if (intervals_to_remove.contains(interval_index)) {
3863 proto->set_intervals(new_size++, interval_index);
3866 proto->mutable_intervals()->Truncate(new_size);
3868 "no_overlap: merge constant contiguous intervals");
3869 intervals_to_remove.clear();
3870 constant_intervals.clear();
3876 if (
proto->intervals_size() == 1) {
3878 return RemoveConstraint(
ct);
3880 if (
proto->intervals().empty()) {
3882 return RemoveConstraint(
ct);
3888 if (move_constraint_last) {
3892 return RemoveConstraint(
ct);
3898 bool CpModelPresolver::PresolveNoOverlap2D(
int c, ConstraintProto*
ct) {
3903 const NoOverlap2DConstraintProto&
proto =
ct->no_overlap_2d();
3904 const int initial_num_boxes =
proto.x_intervals_size();
3906 bool has_zero_sizes =
false;
3907 bool x_constant =
true;
3908 bool y_constant =
true;
3912 std::vector<Rectangle> bounding_boxes;
3913 std::vector<int> active_boxes;
3914 for (
int i = 0; i <
proto.x_intervals_size(); ++i) {
3915 const int x_interval_index =
proto.x_intervals(i);
3916 const int y_interval_index =
proto.y_intervals(i);
3923 if (
proto.boxes_with_null_area_can_overlap() &&
3924 (context_->
SizeMax(x_interval_index) == 0 ||
3925 context_->
SizeMax(y_interval_index) == 0)) {
3926 if (
proto.boxes_with_null_area_can_overlap())
continue;
3927 has_zero_sizes =
true;
3929 ct->mutable_no_overlap_2d()->set_x_intervals(new_size, x_interval_index);
3930 ct->mutable_no_overlap_2d()->set_y_intervals(new_size, y_interval_index);
3931 bounding_boxes.push_back(
3932 {IntegerValue(context_->
StartMin(x_interval_index)),
3933 IntegerValue(context_->
EndMax(x_interval_index)),
3934 IntegerValue(context_->
StartMin(y_interval_index)),
3935 IntegerValue(context_->
EndMax(y_interval_index))});
3936 active_boxes.push_back(new_size);
3948 bounding_boxes, absl::MakeSpan(active_boxes));
3949 if (components.size() > 1) {
3950 for (
const absl::Span<int> boxes : components) {
3951 if (boxes.size() <= 1)
continue;
3953 NoOverlap2DConstraintProto* new_no_overlap_2d =
3955 for (
const int b : boxes) {
3957 new_no_overlap_2d->add_y_intervals(
proto.y_intervals(
b));
3961 context_->
UpdateRuleStats(
"no_overlap_2d: split into disjoint components");
3962 return RemoveConstraint(
ct);
3965 if (!has_zero_sizes && (x_constant || y_constant)) {
3967 "no_overlap_2d: a dimension is constant, splitting into many no " 3969 std::vector<IndexedInterval> indexed_intervals;
3970 for (
int i = 0; i < new_size; ++i) {
3971 int x =
proto.x_intervals(i);
3972 int y =
proto.y_intervals(i);
3974 indexed_intervals.push_back({x, IntegerValue(context_->
StartMin(y)),
3975 IntegerValue(context_->
EndMax(y))});
3977 std::vector<std::vector<int>> no_overlaps;
3980 for (
const std::vector<int>& no_overlap : no_overlaps) {
3984 for (
const int i : no_overlap) {
3989 return RemoveConstraint(
ct);
3992 if (new_size < initial_num_boxes) {
3994 ct->mutable_no_overlap_2d()->mutable_x_intervals()->Truncate(new_size);
3995 ct->mutable_no_overlap_2d()->mutable_y_intervals()->Truncate(new_size);
3998 if (new_size == 0) {
4000 return RemoveConstraint(
ct);
4003 if (new_size == 1) {
4005 return RemoveConstraint(
ct);
4008 return new_size < initial_num_boxes;
4012 LinearExpressionProto ConstantExpressionProto(int64_t
value) {
4013 LinearExpressionProto expr;
4014 expr.set_offset(
value);
4019 bool CpModelPresolver::PresolveCumulative(ConstraintProto*
ct) {
4022 CumulativeConstraintProto*
proto =
ct->mutable_cumulative();
4024 bool changed = CanonicalizeLinearExpression(*
ct,
proto->mutable_capacity());
4025 for (LinearExpressionProto& exp :
4026 *(
ct->mutable_cumulative()->mutable_demands())) {
4027 changed |= CanonicalizeLinearExpression(*
ct, &exp);
4030 const int64_t capacity_max = context_->
MaxOf(
proto->capacity());
4034 bool domain_changed =
false;
4036 proto->capacity(), Domain(0, capacity_max), &domain_changed)) {
4039 if (domain_changed) {
4048 int num_zero_demand_removed = 0;
4049 int num_zero_size_removed = 0;
4050 int num_incompatible_demands = 0;
4051 for (
int i = 0; i <
proto->intervals_size(); ++i) {
4054 const LinearExpressionProto& demand_expr =
proto->demands(i);
4055 const int64_t demand_max = context_->
MaxOf(demand_expr);
4056 if (demand_max == 0) {
4057 num_zero_demand_removed++;
4063 num_zero_size_removed++;
4067 if (context_->
MinOf(demand_expr) > capacity_max) {
4069 ConstraintProto* interval_ct =
4071 DCHECK_EQ(interval_ct->enforcement_literal_size(), 1);
4072 const int literal = interval_ct->enforcement_literal(0);
4076 num_incompatible_demands++;
4080 "cumulative: performed demand exceeds capacity.");
4084 proto->set_intervals(new_size,
proto->intervals(i));
4085 *
proto->mutable_demands(new_size) =
proto->demands(i);
4089 if (new_size < proto->intervals_size()) {
4091 proto->mutable_intervals()->Truncate(new_size);
4092 proto->mutable_demands()->erase(
4093 proto->mutable_demands()->begin() + new_size,
4094 proto->mutable_demands()->end());
4097 if (num_zero_demand_removed > 0) {
4099 "cumulative: removed intervals with no demands");
4101 if (num_zero_size_removed > 0) {
4103 "cumulative: removed intervals with a size of zero");
4105 if (num_incompatible_demands > 0) {
4107 "cumulative: removed intervals demands greater than the capacity");
4113 for (
int i = 0; i <
proto->demands_size(); ++i) {
4115 const LinearExpressionProto& demand_expr =
proto->demands(i);
4117 bool domain_changed =
false;
4122 if (domain_changed) {
4124 "cumulative: fit demand in [0..capacity_max]");
4136 if (
proto->intervals_size() > 1) {
4137 std::vector<IndexedInterval> indexed_intervals;
4138 for (
int i = 0; i <
proto->intervals().size(); ++i) {
4140 indexed_intervals.push_back({i, IntegerValue(context_->
StartMin(
index)),
4143 std::vector<std::vector<int>> components;
4146 if (components.size() > 1) {
4147 for (
const std::vector<int>& component : components) {
4148 CumulativeConstraintProto* new_cumulative =
4150 for (
const int i : component) {
4152 *new_cumulative->add_demands() =
proto->demands(i);
4154 *new_cumulative->mutable_capacity() =
proto->capacity();
4157 context_->
UpdateRuleStats(
"cumulative: split into disjoint components");
4158 return RemoveConstraint(
ct);
4166 std::map<int64_t, int64_t> time_to_demand_deltas;
4167 const int64_t capacity_min = context_->
MinOf(
proto->capacity());
4168 for (
int i = 0; i <
proto->intervals_size(); ++i) {
4169 const int interval_index =
proto->intervals(i);
4170 const int64_t demand_max = context_->
MaxOf(
proto->demands(i));
4171 time_to_demand_deltas[context_->
StartMin(interval_index)] += demand_max;
4172 time_to_demand_deltas[context_->
EndMax(interval_index)] -= demand_max;
4181 int num_possible_overloads = 0;
4182 int64_t current_load = 0;
4183 absl::flat_hash_map<int64_t, int64_t> num_possible_overloads_before;
4184 for (
const auto& it : time_to_demand_deltas) {
4185 num_possible_overloads_before[it.first] = num_possible_overloads;
4186 current_load += it.second;
4187 if (current_load > capacity_min) {
4188 ++num_possible_overloads;
4194 if (num_possible_overloads == 0) {
4196 "cumulative: max profile is always under the min capacity");
4197 return RemoveConstraint(
ct);
4207 for (
int i = 0; i <
proto->intervals_size(); ++i) {
4225 const int num_diff = num_possible_overloads_before.at(
end_max) -
4226 num_possible_overloads_before.at(
start_min);
4227 if (num_diff == 0)
continue;
4229 proto->set_intervals(new_size,
proto->intervals(i));
4230 *
proto->mutable_demands(new_size) =
proto->demands(i);
4234 if (new_size < proto->intervals_size()) {
4236 proto->mutable_intervals()->Truncate(new_size);
4237 proto->mutable_demands()->erase(
4238 proto->mutable_demands()->begin() + new_size,
4239 proto->mutable_demands()->end());
4241 "cumulative: remove never conflicting intervals.");
4245 if (
proto->intervals().empty()) {
4247 return RemoveConstraint(
ct);
4251 int64_t max_of_performed_demand_mins = 0;
4252 int64_t sum_of_max_demands = 0;
4253 for (
int i = 0; i <
proto->intervals_size(); ++i) {
4254 const ConstraintProto& interval_ct =
4257 const LinearExpressionProto& demand_expr =
proto->demands(i);
4258 sum_of_max_demands += context_->
MaxOf(demand_expr);
4260 if (interval_ct.enforcement_literal().empty()) {
4261 max_of_performed_demand_mins =
std::max(max_of_performed_demand_mins,
4262 context_->
MinOf(demand_expr));
4266 const LinearExpressionProto& capacity_expr =
proto->capacity();
4267 if (max_of_performed_demand_mins > context_->
MinOf(capacity_expr)) {
4270 capacity_expr, Domain(max_of_performed_demand_mins,
4276 if (max_of_performed_demand_mins > context_->
MaxOf(capacity_expr)) {
4277 context_->
UpdateRuleStats(
"cumulative: cannot fit performed demands");
4281 if (sum_of_max_demands <= context_->MinOf(capacity_expr)) {
4282 context_->
UpdateRuleStats(
"cumulative: capacity exceeds sum of demands");
4283 return RemoveConstraint(
ct);
4289 for (
int i = 0; i <
ct->cumulative().demands_size(); ++i) {
4290 const LinearExpressionProto& demand_expr =
ct->cumulative().demands(i);
4291 if (!context_->
IsFixed(demand_expr)) {
4297 if (gcd == 1)
break;
4301 for (
int i = 0; i <
ct->cumulative().demands_size(); ++i) {
4302 const int64_t
demand = context_->
MinOf(
ct->cumulative().demands(i));
4303 *
proto->mutable_demands(i) = ConstantExpressionProto(
demand / gcd);
4306 const int64_t old_capacity = context_->
MinOf(
proto->capacity());
4307 *
proto->mutable_capacity() = ConstantExpressionProto(old_capacity / gcd);
4309 "cumulative: divide demands and capacity by gcd");
4313 const int num_intervals =
proto->intervals_size();
4314 const LinearExpressionProto& capacity_expr =
proto->capacity();
4316 std::vector<LinearExpressionProto> start_exprs(num_intervals);
4318 int num_duration_one = 0;
4319 int num_greater_half_capacity = 0;
4321 bool has_optional_interval =
false;
4322 bool all_starts_are_variables =
true;
4323 for (
int i = 0; i < num_intervals; ++i) {
4327 const ConstraintProto&
ct =
4329 const IntervalConstraintProto&
interval =
ct.interval();
4332 const LinearExpressionProto& demand_expr =
proto->demands(i);
4341 const int64_t demand_min = context_->
MinOf(demand_expr);
4342 const int64_t demand_max = context_->
MaxOf(demand_expr);
4343 if (demand_min > capacity_max / 2) {
4344 num_greater_half_capacity++;
4346 if (demand_min > capacity_max) {
4347 context_->
UpdateRuleStats(
"cumulative: demand_min exceeds capacity max");
4351 CHECK_EQ(
ct.enforcement_literal().size(), 1);
4357 }
else if (demand_max > capacity_max) {
4358 if (
ct.enforcement_literal().empty()) {
4360 "cumulative: demand_max exceeds capacity max.");
4370 "cumulative: demand_max of optional interval exceeds capacity.");
4375 if (num_greater_half_capacity == num_intervals) {
4376 if (num_duration_one == num_intervals && !has_optional_interval &&
4377 all_starts_are_variables) {
4381 for (
const LinearExpressionProto& expr : start_exprs) {
4385 return RemoveConstraint(
ct);
4390 for (
int i = 0; i <
proto->demands_size(); ++i) {
4391 const LinearExpressionProto& demand_expr =
proto->demands(i);
4392 const int64_t demand_max = context_->
MaxOf(demand_expr);
4393 if (demand_max > context_->
MinOf(capacity_expr)) {
4394 ConstraintProto* capacity_gt =
4398 .enforcement_literal();
4400 capacity_gt->mutable_linear()->add_domain(
4403 capacity_gt->mutable_linear());
4405 capacity_gt->mutable_linear());
4415 return RemoveConstraint(
ct);
4422 bool CpModelPresolver::PresolveRoutes(ConstraintProto*
ct) {
4425 RoutesConstraintProto&
proto = *
ct->mutable_routes();
4427 const int old_size =
proto.literals_size();
4429 std::vector<bool> has_incoming_or_outgoing_arcs;
4430 const int num_arcs =
proto.literals_size();
4431 for (
int i = 0; i < num_arcs; ++i) {
4432 const int ref =
proto.literals(i);
4439 proto.set_literals(new_size, ref);
4443 if (
tail >= has_incoming_or_outgoing_arcs.size()) {
4444 has_incoming_or_outgoing_arcs.resize(
tail + 1,
false);
4446 if (
head >= has_incoming_or_outgoing_arcs.size()) {
4447 has_incoming_or_outgoing_arcs.resize(
head + 1,
false);
4449 has_incoming_or_outgoing_arcs[
tail] =
true;
4450 has_incoming_or_outgoing_arcs[
head] =
true;
4453 if (old_size > 0 && new_size == 0) {
4458 "routes: graph with nodes and no arcs");
4461 if (new_size < num_arcs) {
4462 proto.mutable_literals()->Truncate(new_size);
4463 proto.mutable_tails()->Truncate(new_size);
4464 proto.mutable_heads()->Truncate(new_size);
4470 for (
int n = 0; n < has_incoming_or_outgoing_arcs.size(); ++n) {
4471 if (!has_incoming_or_outgoing_arcs[n]) {
4473 "routes: node ", n,
" misses incoming or outgoing arcs"));
4480 bool CpModelPresolver::PresolveCircuit(ConstraintProto*
ct) {
4483 CircuitConstraintProto&
proto = *
ct->mutable_circuit();
4487 ct->mutable_circuit()->mutable_heads());
4491 std::vector<std::vector<int>> incoming_arcs;
4492 std::vector<std::vector<int>> outgoing_arcs;
4494 const int num_arcs =
proto.literals_size();
4495 for (
int i = 0; i < num_arcs; ++i) {
4496 const int ref =
proto.literals(i);
4504 incoming_arcs[
head].push_back(ref);
4505 outgoing_arcs[
tail].push_back(ref);
4509 for (
int i = 0; i < num_nodes; ++i) {
4510 if (incoming_arcs[i].empty() || outgoing_arcs[i].empty()) {
4511 return MarkConstraintAsFalse(
ct);
4520 bool loop_again =
true;
4521 int num_fixed_at_true = 0;
4522 while (loop_again) {
4524 for (
const auto* node_to_refs : {&incoming_arcs, &outgoing_arcs}) {
4525 for (
const std::vector<int>& refs : *node_to_refs) {
4526 if (refs.size() == 1) {
4528 ++num_fixed_at_true;
4537 for (
const int ref : refs) {
4547 if (num_true == 1) {
4548 for (
const int ref : refs) {
4549 if (ref != true_ref) {
4550 if (!context_->
IsFixed(ref)) {
4561 if (num_fixed_at_true > 0) {
4568 int circuit_start = -1;
4569 std::vector<int>
next(num_nodes, -1);
4570 std::vector<int> new_in_degree(num_nodes, 0);
4571 std::vector<int> new_out_degree(num_nodes, 0);
4572 for (
int i = 0; i < num_arcs; ++i) {
4573 const int ref =
proto.literals(i);
4581 circuit_start =
proto.tails(i);
4585 ++new_out_degree[
proto.tails(i)];
4586 ++new_in_degree[
proto.heads(i)];
4589 proto.set_literals(new_size,
proto.literals(i));
4599 for (
int i = 0; i < num_nodes; ++i) {
4600 if (new_in_degree[i] == 0 || new_out_degree[i] == 0) {
4606 if (circuit_start != -1) {
4607 std::vector<bool> visited(num_nodes,
false);
4608 int current = circuit_start;
4609 while (current != -1 && !visited[current]) {
4610 visited[current] =
true;
4611 current =
next[current];
4613 if (current == circuit_start) {
4616 std::vector<bool> has_self_arc(num_nodes,
false);
4617 for (
int i = 0; i < num_arcs; ++i) {
4618 if (visited[
proto.tails(i)])
continue;
4620 has_self_arc[
proto.tails(i)] =
true;
4626 for (
int n = 0; n < num_nodes; ++n) {
4627 if (!visited[n] && !has_self_arc[n]) {
4629 return MarkConstraintAsFalse(
ct);
4633 return RemoveConstraint(
ct);
4637 if (num_true == new_size) {
4639 return RemoveConstraint(
ct);
4645 for (
int i = 0; i < num_nodes; ++i) {
4646 for (
const std::vector<int>* arc_literals :
4647 {&incoming_arcs[i], &outgoing_arcs[i]}) {
4648 std::vector<int> literals;
4649 for (
const int ref : *arc_literals) {
4655 literals.push_back(ref);
4657 if (literals.size() == 2 && literals[0] !=
NegatedRef(literals[1])) {
4666 if (new_size < num_arcs) {
4667 proto.mutable_tails()->Truncate(new_size);
4668 proto.mutable_heads()->Truncate(new_size);
4669 proto.mutable_literals()->Truncate(new_size);
4676 bool CpModelPresolver::PresolveAutomaton(ConstraintProto*
ct) {
4679 AutomatonConstraintProto&
proto = *
ct->mutable_automaton();
4680 if (
proto.vars_size() == 0 ||
proto.transition_label_size() == 0) {
4684 bool all_have_same_affine_relation =
true;
4685 std::vector<AffineRelation::Relation> affine_relations;
4686 for (
int v = 0; v <
proto.vars_size(); ++v) {
4687 const int var =
ct->automaton().vars(v);
4689 affine_relations.push_back(r);
4690 if (r.representative ==
var) {
4691 all_have_same_affine_relation =
false;
4694 if (v > 0 && (r.coeff != affine_relations[v - 1].coeff ||
4695 r.offset != affine_relations[v - 1].offset)) {
4696 all_have_same_affine_relation =
false;
4701 if (all_have_same_affine_relation) {
4702 for (
int v = 0; v <
proto.vars_size(); ++v) {
4705 const AffineRelation::Relation rep = affine_relations.front();
4707 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
4708 const int64_t label =
proto.transition_label(t);
4709 int64_t inverse_label = (label - rep.offset) / rep.coeff;
4710 if (inverse_label * rep.coeff + rep.offset == label) {
4711 if (new_size != t) {
4712 proto.set_transition_tail(new_size,
proto.transition_tail(t));
4713 proto.set_transition_head(new_size,
proto.transition_head(t));
4715 proto.set_transition_label(new_size, inverse_label);
4719 if (new_size <
proto.transition_tail_size()) {
4720 proto.mutable_transition_tail()->Truncate(new_size);
4721 proto.mutable_transition_label()->Truncate(new_size);
4722 proto.mutable_transition_head()->Truncate(new_size);
4730 for (
int v = 1; v <
proto.vars_size(); ++v) {
4735 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
4736 const int64_t label =
proto.transition_label(t);
4737 if (hull.Contains(label)) {
4738 if (new_size != t) {
4739 proto.set_transition_tail(new_size,
proto.transition_tail(t));
4740 proto.set_transition_label(new_size, label);
4741 proto.set_transition_head(new_size,
proto.transition_head(t));
4746 if (new_size <
proto.transition_tail_size()) {
4747 proto.mutable_transition_tail()->Truncate(new_size);
4748 proto.mutable_transition_label()->Truncate(new_size);
4749 proto.mutable_transition_head()->Truncate(new_size);
4754 const int n =
proto.vars_size();
4755 const std::vector<int> vars = {
proto.vars().begin(),
proto.vars().end()};
4758 std::vector<std::set<int64_t>> reachable_states(n + 1);
4759 reachable_states[0].insert(
proto.starting_state());
4760 reachable_states[n] = {
proto.final_states().begin(),
4761 proto.final_states().end()};
4765 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
4766 const int64_t
tail =
proto.transition_tail(t);
4767 const int64_t label =
proto.transition_label(t);
4768 const int64_t
head =
proto.transition_head(t);
4771 reachable_states[
time + 1].insert(
head);
4775 std::vector<std::set<int64_t>> reached_values(n);
4779 std::set<int64_t> new_set;
4780 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
4781 const int64_t
tail =
proto.transition_tail(t);
4782 const int64_t label =
proto.transition_label(t);
4783 const int64_t
head =
proto.transition_head(t);
4788 new_set.insert(
tail);
4789 reached_values[
time].insert(label);
4791 reachable_states[
time].swap(new_set);
4794 bool removed_values =
false;
4799 {reached_values[time].begin(), reached_values[time].end()}),
4804 if (removed_values) {
4810 bool CpModelPresolver::PresolveReservoir(ConstraintProto*
ct) {
4814 ReservoirConstraintProto&
proto = *
ct->mutable_reservoir();
4815 bool changed =
false;
4816 for (LinearExpressionProto& exp : *(
proto.mutable_time_exprs())) {
4817 changed |= CanonicalizeLinearExpression(*
ct, &exp);
4820 if (
proto.active_literals().empty()) {
4822 for (
int i = 0; i <
proto.time_exprs_size(); ++i) {
4823 proto.add_active_literals(true_literal);
4828 const auto& demand_is_null = [&](
int i) {
4829 return proto.level_changes(i) == 0 ||
4835 for (
int i = 0; i <
proto.level_changes_size(); ++i) {
4836 if (demand_is_null(i)) num_zeros++;
4839 if (num_zeros > 0) {
4842 for (
int i = 0; i <
proto.level_changes_size(); ++i) {
4843 if (demand_is_null(i))
continue;
4844 proto.set_level_changes(new_size,
proto.level_changes(i));
4845 *
proto.mutable_time_exprs(new_size) =
proto.time_exprs(i);
4846 proto.set_active_literals(new_size,
proto.active_literals(i));
4850 proto.mutable_level_changes()->Truncate(new_size);
4851 proto.mutable_time_exprs()->erase(
4852 proto.mutable_time_exprs()->begin() + new_size,
4853 proto.mutable_time_exprs()->end());
4854 proto.mutable_active_literals()->Truncate(new_size);
4857 "reservoir: remove zero level_changes or inactive events.");
4860 const int num_events =
proto.level_changes_size();
4862 proto.level_changes().empty() ? 0 : std::abs(
proto.level_changes(0));
4863 int num_positives = 0;
4864 int num_negatives = 0;
4865 int64_t max_sum_of_positive_level_changes = 0;
4866 int64_t min_sum_of_negative_level_changes = 0;
4867 for (
int i = 0; i < num_events; ++i) {
4872 max_sum_of_positive_level_changes +=
demand;
4876 min_sum_of_negative_level_changes +=
demand;
4880 if (min_sum_of_negative_level_changes >=
proto.min_level() &&
4881 max_sum_of_positive_level_changes <=
proto.max_level()) {
4883 return RemoveConstraint(
ct);
4886 if (min_sum_of_negative_level_changes >
proto.max_level() ||
4887 max_sum_of_positive_level_changes <
proto.min_level()) {
4892 if (min_sum_of_negative_level_changes >
proto.min_level()) {
4893 proto.set_min_level(min_sum_of_negative_level_changes);
4895 "reservoir: increase min_level to reachable value");
4898 if (max_sum_of_positive_level_changes <
proto.max_level()) {
4899 proto.set_max_level(max_sum_of_positive_level_changes);
4900 context_->
UpdateRuleStats(
"reservoir: reduce max_level to reachable value");
4903 if (
proto.min_level() <= 0 &&
proto.max_level() >= 0 &&
4904 (num_positives == 0 || num_negatives == 0)) {
4909 int64_t fixed_contrib = 0;
4910 for (
int i = 0; i <
proto.level_changes_size(); ++i) {
4914 const int active =
proto.active_literals(i);
4916 sum->add_vars(active);
4920 sum->add_coeffs(-
demand);
4924 sum->add_domain(
proto.min_level() - fixed_contrib);
4925 sum->add_domain(
proto.max_level() - fixed_contrib);
4927 return RemoveConstraint(
ct);
4931 for (
int i = 0; i <
proto.level_changes_size(); ++i) {
4932 proto.set_level_changes(i,
proto.level_changes(i) / gcd);
4938 const Domain reduced_domain = Domain({
proto.min_level(),
proto.max_level()})
4939 .InverseMultiplicationBy(gcd);
4940 proto.set_min_level(reduced_domain.Min());
4941 proto.set_max_level(reduced_domain.Max());
4943 "reservoir: simplify level_changes and levels by gcd.");
4946 if (num_positives == 1 && num_negatives > 0) {
4948 "TODO reservoir: one producer, multiple consumers.");
4951 absl::flat_hash_set<std::tuple<int, int64_t, int64_t, int>> time_active_set;
4952 for (
int i = 0; i <
proto.level_changes_size(); ++i) {
4953 const LinearExpressionProto&
time =
proto.time_exprs(i);
4957 const std::tuple<int, int64_t, int64_t, int> key = std::make_tuple(
4960 proto.active_literals(i));
4961 if (time_active_set.contains(key)) {
4962 context_->
UpdateRuleStats(
"TODO reservoir: merge synchronized events.");
4965 time_active_set.insert(key);
4975 void CpModelPresolver::ExtractBoolAnd() {
4976 absl::flat_hash_map<int, int> ref_to_bool_and;
4978 std::vector<int> to_remove;
4979 for (
int c = 0; c < num_constraints; ++c) {
4983 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr &&
4984 ct.bool_or().literals().size() == 2) {
4988 to_remove.push_back(c);
4992 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne &&
4993 ct.at_most_one().literals().size() == 2) {
4994 AddImplication(
ct.at_most_one().literals(0),
4997 to_remove.push_back(c);
5003 for (
const int c : to_remove) {
5012 void CpModelPresolver::Probe() {
5020 auto* implication_graph =
model.GetOrCreate<BinaryImplicationGraph>();
5021 auto* sat_solver =
model.GetOrCreate<SatSolver>();
5022 auto* mapping =
model.GetOrCreate<CpModelMapping>();
5023 auto* prober =
model.GetOrCreate<Prober>();
5024 prober->ProbeBooleanVariables(1.0);
5026 model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
5027 if (sat_solver->IsModelUnsat() || !implication_graph->DetectEquivalences()) {
5032 CHECK_EQ(sat_solver->CurrentDecisionLevel(), 0);
5033 for (
int i = 0; i < sat_solver->LiteralTrail().Index(); ++i) {
5034 const Literal l = sat_solver->LiteralTrail()[i];
5035 const int var = mapping->GetProtoVariableFromBooleanVariable(l.Variable());
5043 auto* integer_trail =
model.GetOrCreate<IntegerTrail>();
5044 for (
int var = 0;
var < num_variables; ++
var) {
5047 if (!mapping->IsBoolean(
var)) {
5050 integer_trail->InitialVariableDomain(mapping->Integer(
var)))) {
5057 const Literal l = mapping->Literal(
var);
5058 const Literal r = implication_graph->RepresentativeOf(l);
5061 mapping->GetProtoVariableFromBooleanVariable(r.Variable());
5071 void CpModelPresolver::PresolvePureSatPart() {
5077 SatPostsolver sat_postsolver(num_variables);
5078 SatPresolver sat_presolver(&sat_postsolver, logger_);
5079 sat_presolver.SetNumVariables(num_variables);
5080 sat_presolver.SetTimeLimit(context_->
time_limit());
5082 SatParameters params = context_->
params();
5089 if (params.debug_postsolve_with_full_solver()) {
5096 params.set_presolve_use_bva(
false);
5097 sat_presolver.SetParameters(params);
5100 absl::flat_hash_set<int> used_variables;
5101 auto convert = [&used_variables](
int ref) {
5103 if (
RefIsPositive(ref))
return Literal(BooleanVariable(ref),
true);
5104 return Literal(BooleanVariable(
NegatedRef(ref)),
false);
5114 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr ||
5115 ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolAnd) {
5134 std::vector<Literal> clause;
5135 int num_removed_constraints = 0;
5139 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr) {
5140 ++num_removed_constraints;
5142 for (
const int ref :
ct.bool_or().literals()) {
5143 clause.push_back(convert(ref));
5145 for (
const int ref :
ct.enforcement_literal()) {
5146 clause.push_back(convert(ref).Negated());
5148 sat_presolver.AddClause(clause);
5155 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolAnd) {
5156 ++num_removed_constraints;
5157 std::vector<Literal> clause;
5158 for (
const int ref :
ct.enforcement_literal()) {
5159 clause.push_back(convert(ref).Negated());
5162 for (
const int ref :
ct.bool_and().literals()) {
5163 clause.back() = convert(ref);
5164 sat_presolver.AddClause(clause);
5174 if (num_removed_constraints == 0)
return;
5184 std::vector<bool> can_be_removed(num_variables,
false);
5185 for (
int i = 0; i < num_variables; ++i) {
5187 can_be_removed[i] =
true;
5193 if (used_variables.contains(i) && context_->
IsFixed(i)) {
5195 sat_presolver.AddClause({convert(i)});
5197 sat_presolver.AddClause({convert(
NegatedRef(i))});
5205 const int num_passes = params.presolve_use_bva() ? 4 : 1;
5206 for (
int i = 0; i < num_passes; ++i) {
5207 const int old_num_clause = sat_postsolver.NumClauses();
5208 if (!sat_presolver.Presolve(can_be_removed)) {
5209 VLOG(1) <<
"UNSAT during SAT presolve.";
5212 if (old_num_clause == sat_postsolver.NumClauses())
break;
5216 const int new_num_variables = sat_presolver.NumVariables();
5218 VLOG(1) <<
"New variables added by the SAT presolver.";
5220 i < new_num_variables; ++i) {
5221 IntegerVariableProto* var_proto =
5224 var_proto->add_domain(1);
5230 ExtractClauses(
true, sat_presolver, context_->
working_model);
5238 ExtractClauses(
false, sat_postsolver,
5246 void CpModelPresolver::ExpandObjective() {
5265 int unique_expanded_constraint = -1;
5266 const bool objective_was_a_single_variable =
5273 absl::flat_hash_set<int> relevant_constraints;
5274 std::vector<int> var_to_num_relevant_constraints(num_variables, 0);
5275 for (
int ct_index = 0; ct_index < num_constraints; ++ct_index) {
5278 if (!
ct.enforcement_literal().empty() ||
5279 ct.constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
5280 ct.linear().domain().size() != 2 ||
5281 ct.linear().domain(0) !=
ct.linear().domain(1)) {
5285 relevant_constraints.insert(ct_index);
5286 const int num_terms =
ct.linear().vars_size();
5287 for (
int i = 0; i < num_terms; ++i) {
5288 var_to_num_relevant_constraints[
PositiveRef(
ct.linear().vars(i))]++;
5292 std::set<int> var_to_process;
5294 const int var = entry.first;
5296 if (var_to_num_relevant_constraints[
var] != 0) {
5297 var_to_process.insert(
var);
5302 int num_expansions = 0;
5303 int last_expanded_objective_var;
5304 absl::flat_hash_set<int> processed_vars;
5305 std::vector<int> new_vars_in_objective;
5306 while (!relevant_constraints.empty()) {
5308 int objective_var = -1;
5309 while (!var_to_process.empty()) {
5310 const int var = *var_to_process.begin();
5311 CHECK(!processed_vars.contains(
var));
5312 if (var_to_num_relevant_constraints[
var] == 0) {
5313 processed_vars.insert(
var);
5314 var_to_process.erase(
var);
5319 var_to_process.erase(
var);
5322 objective_var =
var;
5326 if (objective_var == -1)
break;
5328 processed_vars.insert(objective_var);
5329 var_to_process.erase(objective_var);
5331 int expanded_linear_index = -1;
5332 int64_t objective_coeff_in_expanded_constraint;
5333 int64_t size_of_expanded_constraint = 0;
5334 const auto& non_deterministic_list =
5336 std::vector<int> constraints_with_objective(non_deterministic_list.begin(),
5337 non_deterministic_list.end());
5338 std::sort(constraints_with_objective.begin(),
5339 constraints_with_objective.end());
5340 for (
const int ct_index : constraints_with_objective) {
5341 if (relevant_constraints.count(ct_index) == 0)
continue;
5342 const ConstraintProto&
ct =
5347 relevant_constraints.erase(ct_index);
5348 const int num_terms =
ct.linear().vars_size();
5349 for (
int i = 0; i < num_terms; ++i) {
5350 var_to_num_relevant_constraints[
PositiveRef(
ct.linear().vars(i))]--;
5362 bool is_present =
false;
5363 int64_t objective_coeff;
5364 for (
int i = 0; i < num_terms; ++i) {
5365 const int ref =
ct.linear().vars(i);
5366 const int64_t coeff =
ct.linear().coeffs(i);
5368 CHECK(!is_present) <<
"Duplicate variables not supported.";
5370 objective_coeff = (ref == objective_var) ? coeff : -coeff;
5383 if (std::abs(objective_coeff) == 1 &&
5384 num_terms > size_of_expanded_constraint) {
5385 expanded_linear_index = ct_index;
5386 size_of_expanded_constraint = num_terms;
5387 objective_coeff_in_expanded_constraint = objective_coeff;
5391 if (expanded_linear_index != -1) {
5394 CHECK_EQ(std::abs(objective_coeff_in_expanded_constraint), 1);
5395 const ConstraintProto&
ct =
5398 objective_var, objective_coeff_in_expanded_constraint,
ct,
5399 &new_vars_in_objective)) {
5404 context_->
UpdateRuleStats(
"objective: expanded objective constraint.");
5407 for (
const int var : new_vars_in_objective) {
5408 if (!processed_vars.contains(
var)) var_to_process.insert(
var);
5421 for (
int i = 0; i < size_of_expanded_constraint; ++i) {
5422 const int ref =
ct.linear().vars(i);
5427 -
ct.linear().coeffs(i)))
5428 .RelaxIfTooComplex();
5430 implied_domain = implied_domain.InverseMultiplicationBy(
5431 objective_coeff_in_expanded_constraint);
5435 if (implied_domain.IsIncludedIn(context_->
DomainOf(objective_var))) {
5437 context_->
UpdateRuleStats(
"objective: removed objective constraint.");
5443 unique_expanded_constraint = expanded_linear_index;
5448 last_expanded_objective_var = objective_var;
5454 if (num_expansions == 1 && objective_was_a_single_variable &&
5455 unique_expanded_constraint != -1) {
5457 "objective: removed unique objective constraint.");
5459 unique_expanded_constraint);
5461 mutable_ct->
Clear();
5474 void CpModelPresolver::MergeNoOverlapConstraints() {
5478 int old_num_no_overlaps = 0;
5479 int old_num_intervals = 0;
5482 std::vector<int> disjunctive_index;
5483 std::vector<std::vector<Literal>> cliques;
5484 for (
int c = 0; c < num_constraints; ++c) {
5486 if (
ct.constraint_case() != ConstraintProto::ConstraintCase::kNoOverlap) {
5489 std::vector<Literal> clique;
5490 for (
const int i :
ct.no_overlap().intervals()) {
5491 clique.push_back(Literal(BooleanVariable(i),
true));
5493 cliques.push_back(clique);
5494 disjunctive_index.push_back(c);
5496 old_num_no_overlaps++;
5497 old_num_intervals += clique.size();
5499 if (old_num_no_overlaps == 0)
return;
5503 local_model.GetOrCreate<Trail>()->Resize(num_constraints);
5504 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
5505 graph->Resize(num_constraints);
5506 for (
const std::vector<Literal>& clique : cliques) {
5509 CHECK(graph->AddAtMostOne(clique));
5511 CHECK(graph->DetectEquivalences());
5512 graph->TransformIntoMaxCliques(
5516 int new_num_no_overlaps = 0;
5517 int new_num_intervals = 0;
5518 for (
int i = 0; i < cliques.size(); ++i) {
5519 const int ct_index = disjunctive_index[i];
5520 ConstraintProto*
ct =
5523 if (cliques[i].empty())
continue;
5524 for (
const Literal l : cliques[i]) {
5525 CHECK(l.IsPositive());
5526 ct->mutable_no_overlap()->add_intervals(l.Variable().value());
5528 new_num_no_overlaps++;
5529 new_num_intervals += cliques[i].size();
5531 if (old_num_intervals != new_num_intervals ||
5532 old_num_no_overlaps != new_num_no_overlaps) {
5533 VLOG(1) << absl::StrCat(
"Merged ", old_num_no_overlaps,
" no-overlaps (",
5534 old_num_intervals,
" intervals) into ",
5535 new_num_no_overlaps,
" no-overlaps (",
5536 new_num_intervals,
" intervals).");
5545 void CpModelPresolver::TransformIntoMaxCliques() {
5548 auto convert = [](
int ref) {
5549 if (
RefIsPositive(ref))
return Literal(BooleanVariable(ref),
true);
5550 return Literal(BooleanVariable(
NegatedRef(ref)),
false);
5555 std::vector<std::vector<Literal>> cliques;
5557 for (
int c = 0; c < num_constraints; ++c) {
5559 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne) {
5560 std::vector<Literal> clique;
5561 for (
const int ref :
ct->at_most_one().literals()) {
5562 clique.push_back(convert(ref));
5564 cliques.push_back(clique);
5565 if (RemoveConstraint(
ct)) {
5568 }
else if (
ct->constraint_case() ==
5569 ConstraintProto::ConstraintCase::kBoolAnd) {
5570 if (
ct->enforcement_literal().size() != 1)
continue;
5571 const Literal enforcement = convert(
ct->enforcement_literal(0));
5572 for (
const int ref :
ct->bool_and().literals()) {
5573 if (ref ==
ct->enforcement_literal(0))
continue;
5574 cliques.push_back({enforcement, convert(ref).Negated()});
5576 if (RemoveConstraint(
ct)) {
5582 int64_t num_literals_before = 0;
5583 const int num_old_cliques = cliques.size();
5588 local_model.GetOrCreate<Trail>()->Resize(num_variables);
5589 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
5590 graph->Resize(num_variables);
5591 for (
const std::vector<Literal>& clique : cliques) {
5592 num_literals_before += clique.size();
5593 if (!graph->AddAtMostOne(clique)) {
5597 if (!graph->DetectEquivalences()) {
5600 graph->TransformIntoMaxCliques(
5606 for (
int var = 0;
var < num_variables; ++
var) {
5607 const Literal l = Literal(BooleanVariable(
var),
true);
5608 if (graph->RepresentativeOf(l) != l) {
5609 const Literal r = graph->RepresentativeOf(l);
5611 var, r.IsPositive() ? r.Variable().value()
5616 int num_new_cliques = 0;
5617 int64_t num_literals_after = 0;
5618 for (
const std::vector<Literal>& clique : cliques) {
5619 if (clique.empty())
continue;
5621 num_literals_after += clique.size();
5623 for (
const Literal
literal : clique) {
5625 ct->mutable_at_most_one()->add_literals(
literal.Variable().value());
5627 ct->mutable_at_most_one()->add_literals(
5633 PresolveAtMostOne(
ct);
5636 if (num_new_cliques != num_old_cliques) {
5637 context_->
UpdateRuleStats(
"at_most_one: transformed into max clique.");
5640 if (num_old_cliques != num_new_cliques ||
5641 num_literals_before != num_literals_after) {
5642 SOLVER_LOG(logger_,
"[MaxClique] Merged ", num_old_cliques,
"(",
5643 num_literals_before,
" literals) into ", num_new_cliques,
"(",
5644 num_literals_after,
" literals) at_most_ones.");
5650 bool IsAffineIntAbs(ConstraintProto*
ct) {
5652 ct->lin_max().exprs_size() != 2 ||
5653 ct->lin_max().target().vars_size() > 1 ||
5654 ct->lin_max().exprs(0).vars_size() != 1 ||
5655 ct->lin_max().exprs(1).vars_size() != 1) {
5659 const LinearArgumentProto& lin_max =
ct->lin_max();
5660 if (lin_max.exprs(0).offset() != -lin_max.exprs(1).offset())
return false;
5666 const int64_t left_coeff =
RefIsPositive(lin_max.exprs(0).vars(0))
5667 ? lin_max.exprs(0).coeffs(0)
5668 : -lin_max.exprs(0).coeffs(0);
5669 const int64_t right_coeff =
RefIsPositive(lin_max.exprs(1).vars(0))
5670 ? lin_max.exprs(1).coeffs(0)
5671 : -lin_max.exprs(1).coeffs(0);
5672 return left_coeff == -right_coeff;
5682 if (ExploitEquivalenceRelations(c,
ct)) {
5687 if (PresolveEnforcementLiteral(
ct)) {
5692 switch (
ct->constraint_case()) {
5693 case ConstraintProto::ConstraintCase::kBoolOr:
5694 return PresolveBoolOr(
ct);
5695 case ConstraintProto::ConstraintCase::kBoolAnd:
5696 return PresolveBoolAnd(
ct);
5697 case ConstraintProto::ConstraintCase::kAtMostOne:
5698 return PresolveAtMostOne(
ct);
5699 case ConstraintProto::ConstraintCase::kExactlyOne:
5700 return PresolveExactlyOne(
ct);
5701 case ConstraintProto::ConstraintCase::kBoolXor:
5702 return PresolveBoolXor(
ct);
5703 case ConstraintProto::ConstraintCase::kLinMax:
5704 if (CanonicalizeLinearArgument(*
ct,
ct->mutable_lin_max())) {
5707 if (IsAffineIntAbs(
ct)) {
5708 return PresolveIntAbs(
ct);
5710 return PresolveLinMax(
ct);
5712 case ConstraintProto::ConstraintCase::kIntProd:
5713 if (CanonicalizeLinearArgument(*
ct,
ct->mutable_int_prod())) {
5716 return PresolveIntProd(
ct);
5717 case ConstraintProto::ConstraintCase::kIntDiv:
5718 if (CanonicalizeLinearArgument(*
ct,
ct->mutable_int_div())) {
5721 return PresolveIntDiv(
ct);
5722 case ConstraintProto::ConstraintCase::kIntMod:
5723 if (CanonicalizeLinearArgument(*
ct,
ct->mutable_int_mod())) {
5726 return PresolveIntMod(
ct);
5727 case ConstraintProto::ConstraintCase::kLinear: {
5736 for (
const int ref :
ct->linear().vars()) {
5742 if (CanonicalizeLinear(
ct)) {
5745 if (PropagateDomainsInLinear(c,
ct)) {
5748 if (PresolveSmallLinear(
ct)) {
5751 if (PresolveLinearEqualityWithModulo(
ct)) {
5755 if (RemoveSingletonInLinear(
ct)) {
5760 if (PresolveSmallLinear(
ct)) {
5764 if (PresolveSmallLinear(
ct)) {
5767 if (PresolveLinearOnBooleans(
ct)) {
5771 const int old_num_enforcement_literals =
ct->enforcement_literal_size();
5772 ExtractEnforcementLiteralFromLinearConstraint(c,
ct);
5773 if (
ct->constraint_case() ==
5774 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
5778 if (
ct->enforcement_literal_size() > old_num_enforcement_literals &&
5779 PresolveSmallLinear(
ct)) {
5788 DetectAndProcessOneSidedLinearConstraint(c,
ct)) {
5793 case ConstraintProto::ConstraintCase::kInterval:
5794 return PresolveInterval(c,
ct);
5795 case ConstraintProto::ConstraintCase::kInverse:
5796 return PresolveInverse(
ct);
5797 case ConstraintProto::ConstraintCase::kElement:
5798 return PresolveElement(
ct);
5799 case ConstraintProto::ConstraintCase::kTable:
5800 return PresolveTable(
ct);
5801 case ConstraintProto::ConstraintCase::kAllDiff:
5802 return PresolveAllDiff(
ct);
5803 case ConstraintProto::ConstraintCase::kNoOverlap:
5804 return PresolveNoOverlap(
ct);
5805 case ConstraintProto::ConstraintCase::kNoOverlap2D:
5806 return PresolveNoOverlap2D(c,
ct);
5807 case ConstraintProto::ConstraintCase::kCumulative:
5808 return PresolveCumulative(
ct);
5809 case ConstraintProto::ConstraintCase::kCircuit:
5810 return PresolveCircuit(
ct);
5811 case ConstraintProto::ConstraintCase::kRoutes:
5812 return PresolveRoutes(
ct);
5813 case ConstraintProto::ConstraintCase::kAutomaton:
5814 return PresolveAutomaton(
ct);
5815 case ConstraintProto::ConstraintCase::kReservoir:
5816 return PresolveReservoir(
ct);
5826 bool CpModelPresolver::ProcessSetPPCSubset(
5827 int c1,
int c2,
const std::vector<int>& c2_minus_c1,
5828 const std::vector<int>& original_constraint_index,
5829 std::vector<bool>* marked_for_removal) {
5832 CHECK(!(*marked_for_removal)[c1]);
5833 CHECK(!(*marked_for_removal)[c2]);
5836 original_constraint_index[c1]);
5838 original_constraint_index[c2]);
5848 for (
const int literal : c2_minus_c1) {
5855 ConstraintProto copy = *ct2;
5861 (*marked_for_removal)[c1] =
true;
5872 (*marked_for_removal)[c2] =
true;
5882 (*marked_for_removal)[c1] =
true;
5899 int num_matches = 0;
5900 for (
int i = 0; i < ct2->
linear().
vars().size(); ++i) {
5902 if (literals.contains(
var)) {
5910 if (num_matches != literals.size())
return true;
5916 for (
int i = 0; i < ct2->
linear().
vars().size(); ++i) {
5919 if (literals.contains(
var)) {
5921 if (coeff == min_coeff)
continue;
5948 bool CpModelPresolver::ProcessSetPPC() {
5953 std::vector<uint64_t> signatures;
5957 std::vector<std::vector<int>> constraint_literals;
5961 std::vector<std::vector<int>> literals_to_constraints;
5964 std::vector<bool> removed;
5968 std::vector<int> original_constraint_index;
5972 int num_setppc_constraints = 0;
5973 std::vector<int> temp_literals;
5975 for (
int c = 0; c < num_constraints; ++c) {
5987 constraint_literals.push_back(GetLiteralsFromSetPPCConstraint(*
ct));
5998 const int size =
ct->linear().vars().size();
5999 if (size <= 2)
continue;
6004 temp_literals.clear();
6005 for (
int i = 0; i < size; ++i) {
6006 const int var =
ct->linear().vars(i);
6007 const int64_t coeff =
ct->linear().coeffs(i);
6010 if (coeff < 0)
continue;
6011 temp_literals.push_back(
var);
6013 if (temp_literals.size() <= 2)
continue;
6014 constraint_literals.push_back(temp_literals);
6019 uint64_t signature = 0;
6020 for (
const int literal : constraint_literals.back()) {
6022 signature |= (int64_t{1} << (positive_literal % 64));
6024 if (positive_literal >= literals_to_constraints.size()) {
6025 literals_to_constraints.resize(positive_literal + 1);
6027 literals_to_constraints[positive_literal].push_back(
6028 num_setppc_constraints);
6030 signatures.push_back(signature);
6031 removed.push_back(
false);
6032 original_constraint_index.push_back(c);
6033 num_setppc_constraints++;
6035 VLOG(1) <<
"#setppc constraints: " << num_setppc_constraints;
6038 absl::flat_hash_set<std::pair<int, int>> compared_constraints;
6039 for (
const std::vector<int>& literal_to_constraints :
6040 literals_to_constraints) {
6041 for (
int index1 = 0; index1 < literal_to_constraints.size(); ++index1) {
6044 const int c1 = literal_to_constraints[index1];
6045 if (removed[c1])
continue;
6046 const std::vector<int>& c1_literals = constraint_literals[c1];
6048 for (
int index2 = index1 + 1; index2 < literal_to_constraints.size();
6050 const int c2 = literal_to_constraints[index2];
6051 if (removed[c2])
continue;
6052 if (removed[c1])
break;
6055 if (c1 == c2)
continue;
6058 const auto [_, inserted] = compared_constraints.insert({c1, c2});
6059 if (!inserted)
continue;
6063 if (compared_constraints.size() >= 50000)
return true;
6065 const bool smaller = (signatures[c1] & ~signatures[c2]) == 0;
6066 const bool larger = (signatures[c2] & ~signatures[c1]) == 0;
6067 if (!(smaller || larger))
continue;
6070 const std::vector<int>& c2_literals = constraint_literals[c2];
6074 std::vector<int> c1_minus_c2;
6076 std::vector<int> c2_minus_c1;
6079 if (c1_minus_c2.empty()) {
6080 if (!ProcessSetPPCSubset(c1, c2, c2_minus_c1,
6081 original_constraint_index, &removed)) {
6084 }
else if (c2_minus_c1.empty()) {
6085 if (!ProcessSetPPCSubset(c2, c1, c1_minus_c2,
6086 original_constraint_index, &removed)) {
6105 bool CpModelPresolver::ProcessEncodingFromLinear(
6106 const int linear_encoding_ct_index,
6107 const ConstraintProto& at_most_or_exactly_one, int64_t* num_unique_terms,
6108 int64_t* num_multiple_terms) {
6110 bool in_exactly_one =
false;
6111 absl::flat_hash_map<int, int> var_to_ref;
6113 for (
const int ref : at_most_or_exactly_one.at_most_one().literals()) {
6118 in_exactly_one =
true;
6119 for (
const int ref : at_most_or_exactly_one.exactly_one().literals()) {
6126 const ConstraintProto& linear_encoding =
6130 std::vector<std::pair<int, int64_t>> ref_to_coeffs;
6131 const int num_terms = linear_encoding.linear().vars().size();
6132 for (
int i = 0; i < num_terms; ++i) {
6133 const int ref = linear_encoding.linear().vars(i);
6134 const int64_t coeff = linear_encoding.linear().coeffs(i);
6135 const auto it = var_to_ref.find(
PositiveRef(ref));
6137 if (it == var_to_ref.end()) {
6140 target_ref = coeff == 1 ? ref :
NegatedRef(ref);
6146 if (it->second == ref) {
6148 ref_to_coeffs.push_back({ref, coeff});
6152 ref_to_coeffs.push_back({
NegatedRef(ref), -coeff});
6160 context_->
UpdateRuleStats(
"encoding: candidate linear is all Boolean now.");
6165 std::vector<int64_t> all_values;
6166 std::map<int64_t, std::vector<int>> value_to_refs;
6167 for (
const auto& [ref, coeff] : ref_to_coeffs) {
6168 const int64_t
value = rhs - coeff;
6169 all_values.push_back(
value);
6170 value_to_refs[
value].push_back(ref);
6174 for (
const auto& [
var, ref] : var_to_ref) {
6175 all_values.push_back(rhs);
6176 value_to_refs[rhs].push_back(ref);
6178 if (!in_exactly_one) {
6181 all_values.push_back(rhs);
6186 bool domain_reduced =
false;
6190 if (domain_reduced) {
6196 context_->
UpdateRuleStats(
"encoding: candidate linear is all Boolean now.");
6201 absl::flat_hash_set<int64_t> value_set;
6202 for (
const int64_t v : context_->
DomainOf(target_ref).
Values()) {
6203 value_set.insert(v);
6205 for (
const auto& [
value, literals] : value_to_refs) {
6207 if (!value_set.contains(
value)) {
6208 for (
const int lit : literals) {
6214 if (literals.size() == 1 && (in_exactly_one ||
value != rhs)) {
6217 ++*num_unique_terms;
6222 ++*num_multiple_terms;
6223 const int associated_lit =
6225 for (
const int lit : literals) {
6231 if (in_exactly_one ||
value != rhs) {
6238 for (
const int lit : literals) bool_or->
add_literals(lit);
6239 bool_or->add_literals(
NegatedRef(associated_lit));
6255 uint64_t ComputeSignature(
const std::vector<int>& integers) {
6256 uint64_t signature = 0;
6257 for (
const int i : integers) signature |= (int64_t{1} << (i & 63));
6263 void CpModelPresolver::ExtractEncodingFromLinear() {
6272 std::vector<int> vars;
6273 bool is_exactly_one;
6275 std::vector<Superset> potential_supersets;
6279 std::vector<int> vars;
6281 std::vector<Subset> potential_subsets;
6285 for (
int c = 0; c < num_constraints; ++c) {
6287 switch (
ct.constraint_case()) {
6289 std::vector<int> vars;
6290 for (
const int ref :
ct.at_most_one().literals()) {
6293 potential_supersets.push_back({c, std::move(vars),
false});
6297 std::vector<int> vars;
6298 for (
const int ref :
ct.exactly_one().literals()) {
6301 potential_supersets.push_back({c, std::move(vars),
true});
6306 if (!
ct.enforcement_literal().empty())
continue;
6307 if (
ct.linear().domain().size() != 2)
continue;
6308 if (
ct.linear().domain(0) !=
ct.linear().domain(1))
continue;
6312 std::vector<int> vars;
6313 bool is_candidate =
true;
6314 int num_integers = 0;
6315 const int num_terms =
ct.linear().vars().size();
6316 for (
int i = 0; i < num_terms; ++i) {
6317 const int ref =
ct.linear().vars(i);
6322 if (std::abs(
ct.linear().coeffs(i)) != 1) {
6323 is_candidate =
false;
6326 if (num_integers == 2) {
6327 is_candidate =
false;
6335 if (is_candidate && num_integers == 1 && vars.size() > 1) {
6336 potential_subsets.push_back({c, std::move(vars)});
6345 if (potential_supersets.empty())
return;
6346 if (potential_subsets.empty())
return;
6350 std::sort(potential_supersets.begin(), potential_supersets.end(),
6351 [](
const Superset&
a,
const Superset&
b) {
6352 const int size_a =
a.vars.size();
6353 const int size_b =
b.vars.size();
6354 return std::tie(size_a,
a.is_exactly_one) <
6355 std::tie(size_b,
b.is_exactly_one);
6357 std::sort(potential_subsets.begin(), potential_subsets.end(),
6358 [](
const Subset&
a,
const Subset&
b) {
6359 return a.vars.size() <
b.vars.size();
6363 int64_t num_exactly_one_encodings = 0;
6364 int64_t num_at_most_one_encodings = 0;
6365 int64_t num_literals = 0;
6366 int64_t num_unique_terms = 0;
6367 int64_t num_multiple_terms = 0;
6373 int subset_index = 0;
6374 std::vector<uint64_t> signatures;
6375 std::vector<std::vector<int>> one_watcher;
6376 std::vector<bool> is_in_superset;
6377 for (
const Superset& superset : potential_supersets) {
6379 const int superset_size = superset.vars.size();
6380 for (; subset_index < potential_subsets.size(); ++subset_index) {
6381 const std::vector<int>& vars = potential_subsets[subset_index].vars;
6382 if (vars.size() > superset_size)
break;
6385 int best_choice = -1;
6386 for (
const int var : vars) {
6387 if (one_watcher.size() <=
var) one_watcher.resize(
var + 1);
6388 if (best_choice == -1 ||
6389 one_watcher[
var].size() < one_watcher[best_choice].size()) {
6393 one_watcher[best_choice].push_back(subset_index);
6394 CHECK_EQ(signatures.size(), subset_index);
6395 signatures.push_back(ComputeSignature(vars));
6399 DCHECK(absl::c_all_of(is_in_superset, [](
bool b) {
return !
b; }));
6400 for (
const int var : superset.vars) {
6401 if (
var >= is_in_superset.size()) {
6402 is_in_superset.resize(
var + 1,
false);
6404 is_in_superset[
var] =
true;
6407 const int max_size =
std::max(one_watcher.size(), is_in_superset.size());
6408 one_watcher.resize(max_size);
6409 is_in_superset.resize(max_size,
false);
6412 const uint64_t superset_signature = ComputeSignature(superset.vars);
6413 for (
const int superset_var : superset.vars) {
6414 for (
int i = 0; i < one_watcher[superset_var].size(); ++i) {
6415 const int subset_index = one_watcher[superset_var][i];
6416 const Subset& subset = potential_subsets[subset_index];
6417 CHECK_LE(subset.vars.size(), superset_size);
6420 if ((signatures[subset_index] & ~superset_signature) != 0)
continue;
6423 bool is_included =
true;
6424 for (
const int subset_var : subset.vars) {
6425 if (!is_in_superset[subset_var]) {
6426 is_included =
false;
6430 if (!is_included)
continue;
6432 if (!superset.is_exactly_one) {
6433 ++num_at_most_one_encodings;
6435 ++num_exactly_one_encodings;
6437 num_literals += subset.vars.size();
6440 if (!ProcessEncodingFromLinear(
6442 &num_unique_terms, &num_multiple_terms)) {
6449 one_watcher[superset_var].back());
6450 one_watcher[superset_var].pop_back();
6456 for (
const int var : superset.vars) {
6457 is_in_superset[
var] =
false;
6461 SOLVER_LOG(logger_,
"[ExtractEncodingFromLinear]",
6462 " #potential_supersets=", potential_supersets.size(),
6463 " #potential_subsets=", potential_subsets.size(),
6464 " #at_most_one_encodings=", num_at_most_one_encodings,
6465 " #exactly_one_encodings=", num_exactly_one_encodings,
6466 " #unique_terms=", num_unique_terms,
6467 " #multiple_terms=", num_multiple_terms,
6468 " #literals=", num_literals,
" time=",
wall_timer.
Get(),
"s");
6471 void CpModelPresolver::TryToSimplifyDomain(
int var) {
6480 if (r.representative !=
var)
return;
6500 absl::flat_hash_set<int64_t> values_set;
6501 absl::flat_hash_map<int64_t, std::vector<int>> value_to_equal_literals;
6502 absl::flat_hash_map<int64_t, std::vector<int>> value_to_not_equal_literals;
6505 if (c < 0)
continue;
6509 int64_t coeff =
ct.linear().coeffs(0);
6510 if (std::abs(coeff) != 1 ||
ct.enforcement_literal().size() != 1) {
6518 if (rhs.IsFixed()) {
6524 values_set.insert(rhs.FixedValue());
6525 value_to_equal_literals[rhs.FixedValue()].push_back(
6526 ct.enforcement_literal(0));
6529 const Domain complement =
6531 if (complement.IsEmpty()) {
6536 if (complement.IsFixed()) {
6538 values_set.insert(complement.FixedValue());
6539 value_to_not_equal_literals[complement.FixedValue()].push_back(
6540 ct.enforcement_literal(0));
6550 }
else if (value_to_not_equal_literals.empty() &&
6551 value_to_equal_literals.empty()) {
6556 std::vector<int64_t> encoded_values(values_set.begin(), values_set.end());
6557 std::sort(encoded_values.begin(), encoded_values.end());
6558 CHECK(!encoded_values.empty());
6559 const bool is_fully_encoded =
6565 for (
const int64_t v : encoded_values) {
6567 const auto eq_it = value_to_equal_literals.find(v);
6568 if (eq_it != value_to_equal_literals.end()) {
6569 for (
const int lit : eq_it->second) {
6573 const auto neq_it = value_to_not_equal_literals.find(v);
6574 if (neq_it != value_to_not_equal_literals.end()) {
6575 for (
const int lit : neq_it->second) {
6583 Domain other_values;
6584 if (!is_fully_encoded) {
6594 if (is_fully_encoded) {
6598 obj_coeff > 0 ? encoded_values.front() : encoded_values.back();
6607 Domain(obj_coeff > 0 ? other_values.Min() : other_values.Max());
6608 min_value = other_values.FixedValue();
6613 int64_t accumulated = std::abs(min_value);
6614 for (
const int64_t
value : encoded_values) {
6618 "TODO variables: only used in objective and in encoding");
6623 ConstraintProto encoding_ct;
6624 LinearConstraintProto* linear = encoding_ct.mutable_linear();
6625 const int64_t coeff_in_equality = -1;
6626 linear->add_vars(
var);
6627 linear->add_coeffs(coeff_in_equality);
6629 linear->add_domain(-min_value);
6630 linear->add_domain(-min_value);
6631 for (
const int64_t
value : encoded_values) {
6632 if (
value == min_value)
continue;
6634 const int64_t coeff =
value - min_value;
6636 linear->add_vars(enf);
6637 linear->add_coeffs(coeff);
6640 linear->set_domain(0, encoding_ct.linear().domain(0) - coeff);
6641 linear->set_domain(1, encoding_ct.linear().domain(1) - coeff);
6643 linear->add_coeffs(-coeff);
6649 "TODO variables: only used in objective and in encoding");
6653 "variables: only used in objective and in encoding");
6660 for (
const int c : copy) {
6661 if (c < 0)
continue;
6668 for (
const int64_t
value : encoded_values) {
6671 ct->add_enforcement_literal(enf);
6672 ct->mutable_linear()->add_vars(
var);
6673 ct->mutable_linear()->add_coeffs(1);
6674 ct->mutable_linear()->add_domain(
value);
6675 ct->mutable_linear()->add_domain(
value);
6680 if (is_fully_encoded) {
6682 for (
const int64_t
value : encoded_values) {
6686 PresolveExactlyOne(new_ct);
6689 ConstraintProto* mapping_ct =
6692 mapping_ct->mutable_linear()->add_coeffs(1);
6695 for (
const int64_t
value : encoded_values) {
6698 new_ct->mutable_at_most_one()->add_literals(
literal);
6700 PresolveAtMostOne(new_ct);
6725 Domain union_of_domain;
6726 int num_positive = 0;
6727 std::vector<int> constraint_indices_to_remove;
6733 constraint_indices_to_remove.push_back(c);
6735 if (
ct.enforcement_literal().size() != 1 ||
6738 ct.linear().vars().size() != 1) {
6742 if (
ct.enforcement_literal(0) ==
var) ++num_positive;
6743 if (ct_var != -1 &&
PositiveRef(
ct.linear().vars(0)) != ct_var) {
6748 union_of_domain = union_of_domain.UnionWith(
6751 ?
ct.linear().coeffs(0)
6752 : -
ct.linear().coeffs(0)));
6754 if (!abort && num_positive == 1) {
6758 context_->
UpdateRuleStats(
"variables: removable enforcement literal");
6759 for (
const int c : constraint_indices_to_remove) {
6778 if (domain.Size() == 2 && (domain.Min() != 0 || domain.Max() != 1)) {
6783 if (domain.NumIntervals() != domain.Size())
return;
6785 const int64_t var_min = domain.Min();
6786 int64_t gcd = domain[1].start - var_min;
6788 const ClosedInterval& i = domain[
index];
6790 const int64_t shifted_value = i.start - var_min;
6794 if (gcd == 1)
break;
6796 if (gcd == 1)
return;
6803 void CpModelPresolver::EncodeAllAffineRelations() {
6804 int64_t num_added = 0;
6809 if (r.representative ==
var)
continue;
6816 if (!PresolveAffineRelationIfAny(
var))
break;
6823 auto* arg =
ct->mutable_linear();
6826 arg->add_vars(r.representative);
6827 arg->add_coeffs(-r.coeff);
6828 arg->add_domain(r.offset);
6829 arg->add_domain(r.offset);
6837 if (num_added > 0) {
6838 SOLVER_LOG(logger_, num_added,
" affine relations still in the model.");
6843 bool CpModelPresolver::PresolveAffineRelationIfAny(
int var) {
6847 if (r.representative ==
var)
return true;
6866 auto* arg =
ct->mutable_linear();
6869 arg->add_vars(r.representative);
6870 arg->add_coeffs(-r.coeff);
6871 arg->add_domain(r.offset);
6872 arg->add_domain(r.offset);
6878 void CpModelPresolver::PresolveToFixPoint() {
6882 const int64_t max_num_operations =
6890 absl::flat_hash_set<std::pair<int, int>> var_constraint_pair_already_called;
6897 std::deque<int> queue;
6898 for (
int c = 0; c < in_queue.size(); ++c) {
6900 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
6911 std::shuffle(queue.begin(), queue.end(), *context_->
random());
6913 std::sort(queue.begin(), queue.end(), [
this](
int a,
int b) {
6916 return score_a < score_b || (score_a == score_b &&
a <
b);
6926 const int c = queue.front();
6927 in_queue[c] =
false;
6930 const int old_num_constraint =
6934 SOLVER_LOG(logger_,
"Unsat after presolving constraint #", c,
6935 " (warning, dump might be inconsistent): ",
6940 const int new_num_constraints =
6942 if (new_num_constraints > old_num_constraint) {
6944 in_queue.resize(new_num_constraints,
true);
6945 for (
int c = old_num_constraint; c < new_num_constraints; ++c) {
6968 for (
int v = 0; v < current_num_variables; ++v) {
6970 if (!PresolveAffineRelationIfAny(v))
return;
6975 TryToSimplifyDomain(v);
6984 for (
int v = 0; v < num_vars; ++v) {
6986 if (constraints.size() != 1)
continue;
6987 const int c = *constraints.begin();
6988 if (c < 0)
continue;
6993 if (var_constraint_pair_already_called.contains(
6994 std::pair<int, int>(v, c))) {
6997 var_constraint_pair_already_called.insert({v, c});
7005 for (
int i = 0; i < 2; ++i) {
7016 if (c >= 0 && !in_queue[c]) {
7024 if (!queue.empty() || i == 1)
break;
7034 VarDomination var_dom;
7035 DualBoundStrengthening dual_bound_strengthening;
7037 &dual_bound_strengthening);
7038 if (!dual_bound_strengthening.Strengthen(context_))
return;
7050 std::sort(queue.begin(), queue.end());
7065 for (
int c = 0; c < num_constraints; ++c) {
7067 switch (
ct->constraint_case()) {
7068 case ConstraintProto::ConstraintCase::kNoOverlap:
7070 if (PresolveNoOverlap(
ct)) {
7074 case ConstraintProto::ConstraintCase::kNoOverlap2D:
7076 if (PresolveNoOverlap2D(c,
ct)) {
7080 case ConstraintProto::ConstraintCase::kCumulative:
7082 if (PresolveCumulative(
ct)) {
7086 case ConstraintProto::ConstraintCase::kBoolOr: {
7089 for (
const auto& pair :
7091 bool modified =
false;
7118 const CpModelProto& in_model,
const std::vector<int>& ignored_constraints,
7120 const absl::flat_hash_set<int> ignored_constraints_set(
7121 ignored_constraints.begin(), ignored_constraints.end());
7126 std::vector<int> constraints_using_intervals;
7130 if (ignored_constraints_set.contains(c))
continue;
7133 if (OneEnforcementLiteralIsFalse(
ct))
continue;
7135 switch (
ct.constraint_case()) {
7139 if (!CopyBoolOr(
ct))
return CreateUnsatModel();
7142 if (!CopyBoolAnd(
ct))
return CreateUnsatModel();
7145 if (!CopyLinear(
ct))
return CreateUnsatModel();
7148 if (!CopyAtMostOne(
ct))
return CreateUnsatModel();
7151 if (!CopyExactlyOne(
ct))
return CreateUnsatModel();
7154 if (!CopyInterval(
ct, c))
return CreateUnsatModel();
7158 constraints_using_intervals.push_back(c);
7160 CopyAndMapNoOverlap(
ct);
7165 constraints_using_intervals.push_back(c);
7167 CopyAndMapNoOverlap2D(
ct);
7172 constraints_using_intervals.push_back(c);
7174 CopyAndMapCumulative(
ct);
7183 DCHECK(first_copy || constraints_using_intervals.empty());
7184 for (
const int c : constraints_using_intervals) {
7186 switch (
ct.constraint_case()) {
7188 CopyAndMapNoOverlap(
ct);
7191 CopyAndMapNoOverlap2D(
ct);
7194 CopyAndMapCumulative(
ct);
7197 LOG(DFATAL) <<
"Shouldn't be here.";
7206 temp_enforcement_literals_.clear();
7209 skipped_non_zero_++;
7212 temp_enforcement_literals_.push_back(lit);
7215 temp_enforcement_literals_.end());
7218 bool ModelCopy::OneEnforcementLiteralIsFalse(
const ConstraintProto&
ct)
const {
7219 for (
const int lit :
ct.enforcement_literal()) {
7227 bool ModelCopy::CopyBoolOr(
const ConstraintProto&
ct) {
7228 temp_literals_.clear();
7229 for (
const int lit :
ct.enforcement_literal()) {
7233 for (
const int lit :
ct.bool_or().literals()) {
7238 skipped_non_zero_++;
7240 temp_literals_.push_back(lit);
7247 ->Add(temp_literals_.begin(), temp_literals_.end());
7248 return !temp_literals_.empty();
7251 bool ModelCopy::CopyBoolAnd(
const ConstraintProto&
ct) {
7252 bool at_least_one_false =
false;
7253 int num_non_fixed_literals = 0;
7254 for (
const int lit :
ct.bool_and().literals()) {
7256 at_least_one_false =
true;
7260 num_non_fixed_literals++;
7264 if (at_least_one_false) {
7269 for (
const int lit :
ct.enforcement_literal()) {
7271 skipped_non_zero_++;
7276 return !bool_or->literals().empty();
7277 }
else if (num_non_fixed_literals > 0) {
7279 CopyEnforcementLiterals(
ct, new_ct);
7280 BoolArgumentProto* bool_and = new_ct->mutable_bool_and();
7281 bool_and->mutable_literals()->Reserve(num_non_fixed_literals);
7282 for (
const int lit :
ct.bool_and().literals()) {
7284 skipped_non_zero_++;
7287 bool_and->add_literals(lit);
7293 bool ModelCopy::CopyLinear(
const ConstraintProto&
ct) {
7294 non_fixed_variables_.clear();
7295 non_fixed_coefficients_.clear();
7297 for (
int i = 0; i <
ct.linear().vars_size(); ++i) {
7298 const int ref =
ct.linear().vars(i);
7299 const int64_t coeff =
ct.linear().coeffs(i);
7301 offset += coeff * context_->
MinOf(ref);
7302 skipped_non_zero_++;
7304 non_fixed_variables_.push_back(ref);
7305 non_fixed_coefficients_.push_back(coeff);
7309 const Domain new_domain =
7311 if (non_fixed_variables_.empty() && !new_domain.Contains(0)) {
7312 if (
ct.enforcement_literal().empty()) {
7315 temp_literals_.clear();
7316 for (
const int literal :
ct.enforcement_literal()) {
7318 skipped_non_zero_++;
7326 ->Add(temp_literals_.begin(), temp_literals_.end());
7327 return !temp_literals_.empty();
7333 CopyEnforcementLiterals(
ct, new_ct);
7334 LinearConstraintProto* linear = new_ct->mutable_linear();
7335 linear->mutable_vars()->Add(non_fixed_variables_.begin(),
7336 non_fixed_variables_.end());
7337 linear->mutable_coeffs()->Add(non_fixed_coefficients_.begin(),
7338 non_fixed_coefficients_.end());
7343 bool ModelCopy::CopyAtMostOne(
const ConstraintProto&
ct) {
7345 temp_literals_.clear();
7346 for (
const int lit :
ct.at_most_one().literals()) {
7348 skipped_non_zero_++;
7351 temp_literals_.push_back(lit);
7355 if (temp_literals_.size() <= 1)
return true;
7356 if (num_true > 1)
return false;
7360 CopyEnforcementLiterals(
ct, new_ct);
7361 new_ct->mutable_at_most_one()->mutable_literals()->Add(temp_literals_.begin(),
7362 temp_literals_.end());
7366 bool ModelCopy::CopyExactlyOne(
const ConstraintProto&
ct) {
7368 temp_literals_.clear();
7369 for (
const int lit :
ct.exactly_one().literals()) {
7371 skipped_non_zero_++;
7374 temp_literals_.push_back(lit);
7378 if (temp_literals_.empty() || num_true > 1)
return false;
7379 if (temp_literals_.size() == 1 && num_true == 1)
return true;
7383 CopyEnforcementLiterals(
ct, new_ct);
7384 new_ct->mutable_exactly_one()->mutable_literals()->Add(temp_literals_.begin(),
7385 temp_literals_.end());
7389 bool ModelCopy::CopyInterval(
const ConstraintProto&
ct,
int c) {
7390 CHECK_EQ(starting_constraint_index_, 0)
7391 <<
"Adding new interval constraints to partially filled model is not " 7398 void ModelCopy::CopyAndMapNoOverlap(
const ConstraintProto&
ct) {
7403 for (
const int index :
ct.no_overlap().intervals()) {
7404 const auto it = interval_mapping_.find(
index);
7405 if (it == interval_mapping_.end())
continue;
7406 new_ct->add_intervals(it->second);
7410 void ModelCopy::CopyAndMapNoOverlap2D(
const ConstraintProto&
ct) {
7415 ct.no_overlap_2d().boxes_with_null_area_can_overlap());
7417 const int num_intervals =
ct.no_overlap_2d().x_intervals().size();
7418 new_ct->mutable_x_intervals()->Reserve(num_intervals);
7419 new_ct->mutable_y_intervals()->Reserve(num_intervals);
7420 for (
int i = 0; i < num_intervals; ++i) {
7421 const auto x_it = interval_mapping_.find(
ct.no_overlap_2d().x_intervals(i));
7422 if (x_it == interval_mapping_.end())
continue;
7423 const auto y_it = interval_mapping_.find(
ct.no_overlap_2d().y_intervals(i));
7424 if (y_it == interval_mapping_.end())
continue;
7425 new_ct->add_x_intervals(x_it->second);
7426 new_ct->add_y_intervals(y_it->second);
7430 void ModelCopy::CopyAndMapCumulative(
const ConstraintProto&
ct) {
7436 const int num_intervals =
ct.cumulative().intervals().size();
7437 new_ct->mutable_intervals()->Reserve(num_intervals);
7438 new_ct->mutable_demands()->Reserve(num_intervals);
7439 for (
int i = 0; i < num_intervals; ++i) {
7440 const auto it = interval_mapping_.find(
ct.cumulative().intervals(i));
7441 if (it == interval_mapping_.end())
continue;
7442 new_ct->add_intervals(it->second);
7443 *new_ct->add_demands() =
ct.cumulative().demands(i);
7447 bool ModelCopy::CreateUnsatModel() {
7461 return context->NotifyThatModelIsUnsat();
7466 if (!in_model.
name().empty()) {
7467 context->working_model->set_name(in_model.
name());
7473 *
context->working_model->mutable_floating_point_objective() =
7477 *
context->working_model->mutable_search_strategy() =
7496 std::vector<int>* postsolve_mapping) {
7502 std::vector<int>* postsolve_mapping)
7503 : postsolve_mapping_(postsolve_mapping),
7537 for (
const auto& decision_strategy :
7553 PresolveEnforcementLiteral(
ct);
7554 switch (
ct->constraint_case()) {
7555 case ConstraintProto::ConstraintCase::kBoolOr:
7558 case ConstraintProto::ConstraintCase::kBoolAnd:
7559 PresolveBoolAnd(
ct);
7561 case ConstraintProto::ConstraintCase::kAtMostOne:
7562 PresolveAtMostOne(
ct);
7564 case ConstraintProto::ConstraintCase::kExactlyOne:
7565 PresolveExactlyOne(
ct);
7567 case ConstraintProto::ConstraintCase::kLinear:
7568 CanonicalizeLinear(
ct);
7576 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
7586 "The floating point objective cannot be scaled with enough " 7609 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
7612 EncodeAllAffineRelations();
7623 int old_num_non_empty_constraints = 0;
7627 if (type == ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET)
continue;
7628 old_num_non_empty_constraints++;
7635 PresolveToFixPoint();
7639 ExtractEncodingFromLinear();
7648 PresolveToFixPoint();
7657 PresolvePureSatPart();
7671 for (
int c = 0; c < old_size; ++c) {
7673 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear) {
7676 ExtractAtMostOneFromLinear(
ct);
7681 if (iter == 0) TransformIntoMaxCliques();
7698 PresolveToFixPoint();
7707 old_num_non_empty_constraints)) {
7711 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
7714 MergeNoOverlapConstraints();
7715 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
7720 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
7724 EncodeAllAffineRelations();
7725 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
7731 const std::vector<std::pair<int, int>> duplicates =
7733 for (
const auto [dup, rep] : duplicates) {
7741 if (type == ConstraintProto::ConstraintCase::kInterval) {
7753 context_->
UpdateRuleStats(
"duplicate: merged rhs of linear constraint");
7756 if (!MarkConstraintAsFalse(
7758 SOLVER_LOG(logger_,
"Unsat after merging two linear constraints");
7778 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
7788 absl::flat_hash_set<int> used_variables;
7793 strategy.clear_transformations();
7794 for (
const int ref : copy.
variables()) {
7802 if (used_variables.contains(
var))
continue;
7803 used_variables.insert(
var);
7811 if (strategy.variable_selection_strategy() !=
7814 strategy.add_transformations();
7815 t->
set_index(strategy.variables_size());
7819 strategy.add_variables(rep);
7826 strategy.add_variables(ref);
7843 postsolve_mapping_->clear();
7845 int num_free_variables = 0;
7847 if (mapping[i] != -1)
continue;
7856 mapping[r] = postsolve_mapping_->size();
7857 postsolve_mapping_->push_back(r);
7869 ++num_free_variables;
7875 mapping[i] = postsolve_mapping_->size();
7876 postsolve_mapping_->push_back(i);
7878 context_->
UpdateRuleStats(absl::StrCat(
"presolve: ", num_free_variables,
7879 " unused variables removed."));
7882 std::shuffle(postsolve_mapping_->begin(), postsolve_mapping_->end(),
7884 for (
int i = 0; i < postsolve_mapping_->size(); ++i) {
7885 mapping[(*postsolve_mapping_)[i]] = i;
7908 const std::string error =
7910 if (!error.empty()) {
7911 SOLVER_LOG(logger_,
"Error while validating postsolved model: ", error);
7917 if (!error.empty()) {
7919 "Error while validating mapping_model model: ", error);
7933 auto mapping_function = [&mapping](
int* ref) {
7946 mapping_function(&mutable_ref);
7952 mapping_function(&mutable_ref);
7960 std::vector<int> new_indices(copy.
variables().size(), -1);
7961 for (
int i = 0; i < copy.
variables().size(); ++i) {
7965 new_indices[i] = strategy.variables_size();
7969 strategy.clear_transformations();
7971 CHECK_LT(transform.index(), new_indices.size());
7972 const int new_index = new_indices[transform.index()];
7973 if (new_index == -1)
continue;
7974 auto* new_transform = strategy.add_transformations();
7975 *new_transform = transform;
7976 CHECK_LT(new_index, strategy.variables().size());
7977 new_transform->set_index(new_index);
7984 absl::flat_hash_set<int> used_vars;
7987 for (
int i = 0; i < mutable_hint->vars_size(); ++i) {
7988 const int old_ref = mutable_hint->
vars(i);
7989 int64_t old_value = mutable_hint->values(i);
7993 if (old_value <
context.MinOf(old_ref)) {
7994 old_value =
context.MinOf(old_ref);
7996 if (old_value >
context.MaxOf(old_ref)) {
7997 old_value =
context.MaxOf(old_ref);
8006 const int image = mapping[
var];
8008 if (!used_vars.insert(image).second)
continue;
8009 mutable_hint->set_vars(new_size, image);
8010 mutable_hint->set_values(new_size,
value);
8015 mutable_hint->mutable_vars()->Truncate(new_size);
8016 mutable_hint->mutable_values()->Truncate(new_size);
8023 std::vector<IntegerVariableProto> new_variables;
8024 for (
int i = 0; i < mapping.size(); ++i) {
8025 const int image = mapping[i];
8026 if (image < 0)
continue;
8027 if (image >= new_variables.size()) {
8044 ConstraintProto CopyConstraintForDuplicateDetection(
const ConstraintProto&
ct) {
8045 ConstraintProto copy =
ct;
8048 copy.mutable_linear()->clear_domain();
8056 std::vector<std::pair<int, int>> result;
8060 absl::flat_hash_map<uint64_t, int> equiv_constraints;
8064 for (
int c = 0; c < num_constraints; ++c) {
8074 s = copy.SerializeAsString();
8076 const uint64_t
hash = absl::Hash<std::string>()(s);
8077 const auto [it, inserted] = equiv_constraints.insert({
hash, c});
8080 const int other_c_with_same_hash = it->second;
8081 copy = CopyConstraintForDuplicateDetection(
8083 if (s == copy.SerializeAsString()) {
8084 result.push_back({c, other_c_with_same_hash});
int64_t SmallestValue() const
Returns the value closest to zero.
bool CanonicalizeAffineVariable(int ref, int64_t coeff, int64_t mod, int64_t rhs)
int32_t assumptions(int index) const
::operations_research::sat::DecisionStrategyProto * mutable_search_strategy(int index)
::operations_research::sat::BoolArgumentProto * mutable_exactly_one()
void set_presolve_blocked_clause(bool value)
int64_t MinOf(int ref) const
int64_t CapSub(int64_t x, int64_t y)
std::vector< int > tmp_literals
void InitializeNewDomains()
::PROTOBUF_NAMESPACE_ID::RepeatedField< int32_t > * mutable_intervals()
bool VariableIsUniqueAndRemovable(int ref) const
bool StoreBooleanEqualityRelation(int ref_a, int ref_b)
ConstraintCase constraint_case() const
::operations_research::sat::AllDifferentConstraintProto * mutable_all_diff()
int32_t symmetry_level() const
void Set(IntegerType index)
bool ModelIsUnsat() const
void ReadObjectiveFromProto()
void add_enforcement_literal(int32_t value)
#define SOLVER_LOG(logger,...)
ModelRandomGenerator * random()
int32_t vars(int index) const
#define CHECK_GE(val1, val2)
::operations_research::sat::ElementConstraintProto * mutable_element()
const ::operations_research::sat::BoolArgumentProto & at_most_one() const
Domain InverseMultiplicationBy(const int64_t coeff) const
Returns {x ∈ Int64, ∃ e ∈ D, x * coeff = e}.
void RemoveVariableFromAffineRelation(int var)
bool ConstraintVariableGraphIsUpToDate() const
void add_x_intervals(int32_t value)
bool ConstraintVariableUsageIsConsistent()
SparseBitset< int64_t > modified_domains
void UpdateRuleStats(const std::string &name, int num_times=1)
void GetOverlappingIntervalComponents(std::vector< IndexedInterval > *intervals, std::vector< std::vector< int >> *components)
void RemoveEmptyConstraints()
std::vector< std::pair< int, int > > FindDuplicateConstraints(const CpModelProto &model_proto)
bool StoreLiteralImpliesVarNEqValue(int literal, int var, int64_t value)
ModelSharedTimeLimit * time_limit
::PROTOBUF_NAMESPACE_ID::RepeatedField< int32_t > * mutable_vars()
const absl::flat_hash_set< int > & VarToConstraints(int var) const
bool ConstraintIsOptional(int ct_ref) const
int GetOrCreateConstantVar(int64_t cst)
void CanonicalizeDomainOfSizeTwo(int var)
void ApplyToAllIntervalIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
int64_t StartMin(int ct_ref) const
#define CHECK_GT(val1, val2)
bool permute_variable_randomly() const
const ::operations_research::sat::FloatObjectiveProto & floating_point_objective() const
#define VLOG(verboselevel)
bool ConstraintIsInactive(int ct_index) const
bool GetAbsRelation(int target_ref, int *ref)
const Domain & ObjectiveDomain() const
void set_vars(int index, int32_t value)
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
void ApplyToAllVariableIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
::operations_research::sat::NoOverlapConstraintProto * mutable_no_overlap()
constexpr int kAffineRelationConstraint
void add_literals(int32_t value)
DomainIteratorBeginEnd Values() const &
int32_t enforcement_literal(int index) const
bool VariableIsNotUsedAnymore(int ref) const
bool keep_all_feasible_solutions
bool StoreAbsRelation(int target_ref, int ref)
int64_t FixedValue(int ref) const
int32_t debug_max_num_presolve_operations() const
int LiteralForExpressionMax(const LinearExpressionProto &expr) const
::PROTOBUF_NAMESPACE_ID::RepeatedField< int64_t > * mutable_coeffs()
int64_t CapProd(int64_t x, int64_t y)
bool LinearExpressionProtosAreEqual(const LinearExpressionProto &a, const LinearExpressionProto &b, int64_t b_scaling)
::operations_research::sat::CumulativeConstraintProto * mutable_cumulative()
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
::operations_research::sat::SatParameters_SearchBranching search_branching() const
::operations_research::sat::NoOverlap2DConstraintProto * mutable_no_overlap_2d()
#define DCHECK_GT(val1, val2)
const std::vector< int > & ConstraintToVars(int c) const
bool ExpressionIsAffineBoolean(const LinearExpressionProto &expr) const
CpModelProto * mapping_model
bool StoreAffineRelation(int ref_x, int ref_y, int64_t coeff, int64_t offset, bool debug_no_recursion=false)
int variables_size() const
int32_t vars(int index) const
::PROTOBUF_NAMESPACE_ID::RepeatedField< int32_t > * mutable_vars()
bool cp_model_use_sat_presolve() const
bool permute_presolve_constraint_order() const
::operations_research::sat::IntegerVariableProto * add_variables()
bool VariableIsOnlyUsedInEncodingAndMaybeInObjective(int ref) const
ModelCopy(PresolveContext *context)
void set_offset(int64_t value)
int64_t num_presolve_operations
void UpdateConstraintVariableUsage(int c)
static constexpr VariableSelectionStrategy CHOOSE_FIRST
CpModelProto * working_model
bool ObjectiveDomainIsConstraining() const
void ExploitFixedDomain(int var)
bool CanBeUsedAsLiteral(int ref) const
Domain UnionWith(const Domain &domain) const
Returns the union of D and domain.
#define CHECK_LT(val1, val2)
const ::operations_research::sat::PartialVariableAssignment & solution_hint() const
ABSL_MUST_USE_RESULT bool SetLiteralToFalse(int lit)
bool has_symmetry() const
const ::operations_research::sat::BoolArgumentProto & exactly_one() const
Domain DomainOf(int ref) const
void CopyFrom(const IntegerVariableProto &from)
const ::operations_research::sat::SymmetryProto & symmetry() const
int ReindexArcs(IntContainer *tails, IntContainer *heads)
::operations_research::sat::BoolArgumentProto * mutable_bool_or()
const ::operations_research::sat::LinearConstraintProto & linear() const
const SatParameters & params() const
const ::operations_research::sat::DecisionStrategyProto_AffineTransformation & transformations(int index) const
bool LoadModelForProbing(PresolveContext *context, Model *local_model)
bool VariableIsRemovable(int ref) const
bool fill_tightened_domains_in_response() const
const ::operations_research::sat::IntegerVariableProto & variables(int index) const
int64_t EndMax(int ct_ref) const
int32_t presolve_substitution_level() const
int64_t Min() const
Returns the min value of the domain.
int64_t CapAdd(int64_t x, int64_t y)
bool ImportConstraintsWithBasicPresolveIntoContext(const CpModelProto &in_model, PresolveContext *context)
::operations_research::sat::BoolArgumentProto * mutable_bool_and()
ClosedInterval front() const
void CopyEverythingExceptVariablesAndConstraintsFieldsIntoContext(const CpModelProto &in_model, PresolveContext *context)
#define DCHECK_NE(val1, val2)
int64_t SizeMax(int ct_ref) const
bool VariableWasRemoved(int ref) const
int64_t MaxOf(int ref) const
void RegisterVariablesUsedInAssumptions()
void WriteObjectiveToProto() const
static Domain FromValues(std::vector< int64_t > values)
Creates a domain from the union of an unsorted list of integer values.
CpModelPresolver(PresolveContext *context, std::vector< int > *postsolve_mapping)
std::string ValidateCpModel(const CpModelProto &model, bool after_presolve)
bool PropagateAffineRelation(int ref)
::operations_research::sat::LinearExpressionProto * add_exprs()
#define CHECK_LE(val1, val2)
::operations_research::sat::LinearConstraintProto * mutable_linear()
::PROTOBUF_NAMESPACE_ID::RepeatedField< int32_t > * mutable_enforcement_literal()
void ExpandCpModel(PresolveContext *context)
const ::operations_research::sat::ConstraintProto & constraints(int index) const
int64_t coeffs(int index) const
::operations_research::sat::LinearExpressionProto * mutable_start()
static int64_t GCD64(int64_t x, int64_t y)
void add_intervals(int32_t value)
Domain PositiveModuloBySuperset(const Domain &modulo) const
Returns a superset of {x ∈ Int64, ∃ e ∈ D, ∃ m ∈ modulo, x = e % m }.
::operations_research::sat::ConstraintProto * add_constraints()
bool ContainsKey(const Collection &collection, const Key &key)
ABSL_MUST_USE_RESULT bool ScaleFloatingPointObjective()
const ::operations_research::sat::CpObjectiveProto & objective() const
bool has_floating_point_objective() const
bool ImportAndSimplifyConstraints(const CpModelProto &in_model, const std::vector< int > &ignored_constraints, bool first_copy=false)
void MarkVariableAsRemoved(int ref)
void set_boxes_with_null_area_can_overlap(bool value)
std::vector< absl::flat_hash_set< int > > var_to_ub_only_constraints
void ConstructOverlappingSets(bool already_sorted, std::vector< IndexedInterval > *intervals, std::vector< std::vector< int >> *result)
int64_t Size() const
Returns the number of elements in the domain.
Domain RelaxIfTooComplex() const
If NumIntervals() is too large, this return a superset of the domain.
::operations_research::sat::IntervalConstraintProto * mutable_interval()
ABSL_MUST_USE_RESULT bool IntersectDomainWith(int ref, const Domain &domain, bool *domain_modified=nullptr)
Domain IntersectionWith(const Domain &domain) const
Returns the intersection of D and domain.
const std::string & name() const
::PROTOBUF_NAMESPACE_ID::RepeatedField< int32_t > * mutable_assumptions()
#define DCHECK_GE(val1, val2)
Domain MultiplicationBy(int64_t coeff, bool *exact=nullptr) const
Returns {x ∈ Int64, ∃ e ∈ D, x = e * coeff}.
bool DetectAndExploitSymmetriesInPresolve(PresolveContext *context)
CpSolverStatus Presolve()
int constraints_size() const
PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final
absl::flat_hash_set< int > tmp_literal_set
bool VariableWithCostIsUniqueAndRemovable(int ref) const
bool SolveDiophantineEquationOfSizeTwo(int64_t &a, int64_t &b, int64_t &cte, int64_t &x0, int64_t &y0)
void add_domain(int64_t value)
ABSL_MUST_USE_RESULT bool NotifyThatModelIsUnsat(const std::string &message="")
void AddLinearExpressionToLinearConstraint(const LinearExpressionProto &expr, int64_t coefficient, LinearConstraintProto *linear)
#define CHECK_EQ(val1, val2)
std::vector< absl::Span< int > > GetOverlappingRectangleComponents(const std::vector< Rectangle > &rectangles, absl::Span< int > active_rectangles)
ABSL_MUST_USE_RESULT bool SubstituteVariableInObjective(int var_in_equality, int64_t coeff_in_equality, const ConstraintProto &equality, std::vector< int > *new_vars_in_objective=nullptr)
Domain AdditionWith(const Domain &domain) const
Returns {x ∈ Int64, ∃ a ∈ D, ∃ b ∈ domain, x = a + b}.
bool has_solution_hint() const
::operations_research::sat::LinearExpressionProto * mutable_capacity()
bool has_objective() const
::operations_research::sat::DecisionStrategyProto * add_search_strategy()
::operations_research::sat::ConstraintProto * mutable_constraints(int index)
bool ModelIsExpanded() const
CpModelProto const * model_proto
bool DomainContains(int ref, int64_t value) const
Domain DivisionBy(int64_t coeff) const
Returns {x ∈ Int64, ∃ e ∈ D, x = e / coeff}.
bool enumerate_all_solutions() const
#define DCHECK(condition)
int32_t cp_model_probing_level() const
void set_name(ArgT0 &&arg0, ArgT... args)
We call domain any subset of Int64 = [kint64min, kint64max].
bool LiteralIsTrue(int lit) const
int64_t FloorSquareRoot(int64_t a)
void AddImplication(int a, int b)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
void Swap(IntegerVariableProto *other)
void UpdateNewConstraintsVariableUsage()
CpSolverStatus PresolveCpModel(PresolveContext *context, std::vector< int > *postsolve_mapping)
bool Contains(int64_t value) const
Returns true iff value is in Domain.
bool LimitReached()
Returns true when the external limit is true, or the deterministic time is over the deterministic lim...
double merge_at_most_one_work_limit() const
bool LoggingIsEnabled() const
#define DCHECK_EQ(val1, val2)
bool VariableWithCostIsUnique(int ref) const
Domain DomainSuperSetOf(const LinearExpressionProto &expr) const
bool ExpressionContainsSingleRef(const LinearExpressionProto &expr)
void add_intervals(int32_t value)
DomainDeductions deductions
bool HasEnforcementLiteral(const ConstraintProto &ct)
std::vector< absl::flat_hash_set< int > > var_to_lb_only_constraints
bool InsertVarValueEncoding(int literal, int ref, int64_t value)
void AdvanceDeterministicTime(double deterministic_duration)
Advances the deterministic time.
void AddDeduction(int literal_ref, int var, Domain domain)
void add_domain(int64_t value)
bool DomainOfVarIsIncludedIn(int var, const Domain &domain)
const ::operations_research::sat::DecisionStrategyProto & search_strategy(int index) const
std::vector< Domain > tmp_term_domains
Domain ContinuousMultiplicationBy(int64_t coeff) const
Returns a superset of MultiplicationBy() to avoid the explosion in the representation size.
bool ExpressionIsALiteral(const LinearExpressionProto &expr, int *literal=nullptr) const
const std::vector< IntegerType > & PositionsSetAtLeastOnce() const
void clear_solution_hint()
int GetVariableRepresentative(int ref) const
bool StoreLiteralImpliesVarEqValue(int literal, int var, int64_t value)
static constexpr SearchBranching FIXED_SEARCH
int32_t max_presolve_iterations() const
Collection of objects used to extend the Constraint Solver library.
bool PresolveOneConstraint(int c)
bool IsFixed(int ref) const
::operations_research::sat::CpObjectiveProto * mutable_objective()
int64_t SizeMin(int ct_ref) const
bool ExploitExactlyOneInObjective(absl::Span< const int > exactly_one)
bool IntervalIsConstant(int ct_ref) const
ABSL_MUST_USE_RESULT bool CanonicalizeObjective(bool simplify_domain=true)
void DetectDominanceRelations(const PresolveContext &context, VarDomination *var_domination, DualBoundStrengthening *dual_bound_strengthening)
void add_vars(int32_t value)
::operations_research::sat::PartialVariableAssignment * mutable_solution_hint()
const absl::flat_hash_map< int, int64_t > & ObjectiveMap() const
void RemoveAllVariablesFromAffineRelationConstraint()
std::vector< Domain > tmp_left_domains
bool ExploitDominanceRelations(const VarDomination &var_domination, PresolveContext *context)
bool RefIsPositive(int ref)
constexpr int kObjectiveConstraint
const LiteralIndex kNoLiteralIndex(-1)
int32_t literals(int index) const
void ApplyVariableMapping(const std::vector< int > &mapping, const PresolveContext &context)
bool SubstituteVariable(int var, int64_t var_coeff_in_definition, const ConstraintProto &definition, ConstraintProto *ct)
void Swap(ConstraintProto *other)
double merge_no_overlap_work_limit() const
bool LiteralIsFalse(int lit) const
void set_coeffs(int index, int64_t value)
int64_t domain(int index) const
ABSL_MUST_USE_RESULT bool SetLiteralToTrue(int lit)
int GetSingleRefFromExpression(const LinearExpressionProto &expr)
int GetOrCreateVarValueEncoding(int ref, int64_t value)
GurobiMPCallbackContext * context
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
::operations_research::sat::IntegerVariableProto * mutable_variables(int index)
bool IsEmpty() const
Returns true if this is the empty set.
void STLSetDifference(const In1 &a, const In2 &b, Out *out, Compare compare)
int NumDeductions() const
void ApplyToAllLiteralIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
AffineRelation::Relation GetAffineRelation(int ref) const
bool cp_model_presolve() const
#define CHECK_NE(val1, val2)
std::vector< std::pair< int, Domain > > ProcessClause(absl::Span< const int > clause)
int32_t variables(int index) const
int GetLiteralRepresentative(int ref) const
#define DCHECK_LT(val1, val2)
bool presolve_extract_integer_enforcement() const
::PROTOBUF_NAMESPACE_ID::RepeatedField< int32_t > * mutable_literals()
void MarkProcessingAsDoneForNow()
bool keep_all_feasible_solutions_in_presolve() const