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"
63bool 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;
147bool 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);
241bool 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);
320ABSL_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();
336bool 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));
403bool 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) {
418 int num_positive = 0;
419 int num_negative = 0;
420 for (
const int other : *literals) {
441 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();
521bool 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);
544bool 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);
574bool CpModelPresolver::CanonicalizeLinMax(ConstraintProto*
ct) {
581 bool changed = CanonicalizeLinearExpression(
582 *
ct,
ct->mutable_lin_max()->mutable_target());
583 for (LinearExpressionProto& exp : *(
ct->mutable_lin_max()->mutable_exprs())) {
584 changed |= CanonicalizeLinearExpression(*
ct, &exp);
589bool CpModelPresolver::PresolveLinMax(ConstraintProto*
ct) {
594 const LinearExpressionProto& target =
ct->lin_max().target();
596 int64_t infered_min = context_->
MinOf(target);
598 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
603 if (target.vars().empty()) {
604 if (!Domain(infered_min, infered_max).Contains(target.offset())) {
606 return MarkConstraintAsFalse(
ct);
611 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
612 rhs_domain = rhs_domain.UnionWith(
614 {infered_min, infered_max}));
616 bool reduced =
false;
627 const int64_t target_min = context_->
MinOf(target);
628 const int64_t target_max = context_->
MaxOf(target);
629 bool changed =
false;
632 for (
int i = 0; i <
ct->lin_max().exprs_size(); ++i) {
633 const LinearExpressionProto& expr =
ct->lin_max().exprs(i);
634 if (context_->
MaxOf(expr) < target_min)
continue;
635 *
ct->mutable_lin_max()->mutable_exprs(new_size) = expr;
638 if (new_size < ct->lin_max().exprs_size()) {
640 ct->mutable_lin_max()->mutable_exprs()->DeleteSubrange(
641 new_size,
ct->lin_max().exprs_size() - new_size);
646 if (
ct->lin_max().exprs().empty()) {
648 return MarkConstraintAsFalse(
ct);
651 if (
ct->lin_max().exprs().size() == 1) {
657 auto* arg = new_ct->mutable_linear();
658 const LinearExpressionProto&
a =
ct->lin_max().target();
659 const LinearExpressionProto&
b =
ct->lin_max().exprs(0);
660 for (
int i = 0; i <
a.vars().size(); ++i) {
661 arg->add_vars(
a.vars(i));
662 arg->add_coeffs(
a.coeffs(i));
664 for (
int i = 0; i <
b.vars().size(); ++i) {
665 arg->add_vars(
b.vars(i));
666 arg->add_coeffs(-
b.coeffs(i));
668 arg->add_domain(
b.offset() -
a.offset());
669 arg->add_domain(
b.offset() -
a.offset());
671 return RemoveConstraint(
ct);
679 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
680 const int64_t value_min = context_->
MinOf(expr);
681 bool modified =
false;
689 const int64_t value_max = context_->
MaxOf(expr);
690 if (value_max > target_max) {
691 context_->
UpdateRuleStats(
"TODO lin_max: linear expression above max.");
695 if (abort)
return changed;
699 if (target_min == target_max) {
700 bool all_booleans =
true;
701 std::vector<int> literals;
702 const int64_t fixed_target = target_min;
703 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
704 const int64_t value_min = context_->
MinOf(expr);
705 const int64_t value_max = context_->
MaxOf(expr);
706 CHECK_LE(value_max, fixed_target) <<
"Presolved above";
707 if (value_max < fixed_target)
continue;
709 if (value_min == value_max && value_max == fixed_target) {
711 return RemoveConstraint(
ct);
717 all_booleans =
false;
721 if (literals.empty()) {
722 return MarkConstraintAsFalse(
ct);
727 for (
const int lit : literals) {
728 ct->mutable_bool_or()->add_literals(lit);
741 bool min_is_reachable =
false;
742 std::vector<int> min_literals;
743 std::vector<int> literals_above_min;
744 std::vector<int> max_literals;
746 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
747 const int64_t value_min = context_->
MinOf(expr);
748 const int64_t value_max = context_->
MaxOf(expr);
751 if (value_min > target_min) {
759 if (value_min == value_max) {
760 if (value_min == target_min) min_is_reachable =
true;
771 if (value_min == target_min) {
776 if (value_max == target_max) {
777 max_literals.push_back(ref);
778 literals_above_min.push_back(ref);
779 }
else if (value_max > target_min) {
780 literals_above_min.push_back(ref);
781 }
else if (value_max == target_min) {
782 min_literals.push_back(ref);
791 clause->mutable_bool_or();
792 for (
const int lit : max_literals) {
793 clause->mutable_bool_or()->add_literals(lit);
797 for (
const int lit : literals_above_min) {
801 if (!min_is_reachable) {
805 clause->mutable_bool_or();
806 for (
const int lit : min_literals) {
807 clause->mutable_bool_or()->add_literals(lit);
812 return RemoveConstraint(
ct);
820bool CpModelPresolver::PresolveIntAbs(ConstraintProto*
ct) {
823 const LinearExpressionProto& target_expr =
ct->lin_max().target();
824 const LinearExpressionProto& expr =
ct->lin_max().exprs(0);
830 const Domain new_target_domain =
831 expr_domain.
UnionWith(expr_domain.Negation())
833 bool target_domain_modified =
false;
835 &target_domain_modified)) {
838 if (expr_domain.IsFixed()) {
840 return RemoveConstraint(
ct);
842 if (target_domain_modified) {
843 context_->
UpdateRuleStats(
"int_abs: propagate domain from x to abs(x)");
849 const Domain target_domain =
852 const Domain new_expr_domain =
853 target_domain.
UnionWith(target_domain.Negation());
854 bool expr_domain_modified =
false;
856 &expr_domain_modified)) {
861 if (context_->
IsFixed(target_expr)) {
863 return RemoveConstraint(
ct);
865 if (expr_domain_modified) {
866 context_->
UpdateRuleStats(
"int_abs: propagate domain from abs(x) to x");
871 if (context_->
MinOf(expr) >= 0) {
875 auto* arg = new_ct->mutable_linear();
880 if (!CanonicalizeLinear(new_ct))
return false;
882 return RemoveConstraint(
ct);
885 if (context_->
MaxOf(expr) <= 0) {
889 auto* arg = new_ct->mutable_linear();
894 if (!CanonicalizeLinear(new_ct))
return false;
896 return RemoveConstraint(
ct);
908 return RemoveConstraint(
ct);
929bool CpModelPresolver::PresolveIntProd(ConstraintProto*
ct) {
933 LinearArgumentProto*
proto =
ct->mutable_int_prod();
935 bool changed = CanonicalizeLinearExpression(*
ct,
proto->mutable_target());
936 for (LinearExpressionProto& exp : *(
proto->mutable_exprs())) {
937 changed |= CanonicalizeLinearExpression(*
ct, &exp);
941 int64_t constant_factor = 1;
943 for (
int i = 0; i <
ct->int_prod().exprs().size(); ++i) {
944 LinearExpressionProto expr =
ct->int_prod().exprs(i);
951 const int64_t coeff = expr.coeffs(0);
952 const int64_t offset = expr.offset();
955 static_cast<uint64_t
>(std::abs(offset)));
957 constant_factor =
CapProd(constant_factor, gcd);
958 expr.set_coeffs(0, coeff / gcd);
959 expr.set_offset(offset / gcd);
962 *
proto->mutable_exprs(new_size++) = expr;
964 proto->mutable_exprs()->erase(
proto->mutable_exprs()->begin() + new_size,
965 proto->mutable_exprs()->end());
967 if (
ct->int_prod().exprs().empty()) {
969 Domain(constant_factor))) {
973 return RemoveConstraint(
ct);
976 if (constant_factor == 0) {
981 return RemoveConstraint(
ct);
996 if (
ct->int_prod().exprs().size() == 1) {
997 LinearConstraintProto*
const lin =
1003 -constant_factor, lin);
1005 context_->
UpdateRuleStats(
"int_prod: linearize product by constant.");
1006 return RemoveConstraint(
ct);
1009 if (constant_factor != 1) {
1014 const LinearExpressionProto old_target =
ct->int_prod().target();
1015 if (!context_->
IsFixed(old_target)) {
1016 const int ref = old_target.vars(0);
1017 const int64_t coeff = old_target.coeffs(0);
1018 const int64_t offset = old_target.offset();
1029 if (context_->
IsFixed(old_target)) {
1030 const int64_t target_value = context_->
FixedValue(old_target);
1031 if (target_value % constant_factor != 0) {
1033 "int_prod: constant factor does not divide constant target");
1035 proto->clear_target();
1036 proto->mutable_target()->set_offset(target_value / constant_factor);
1038 "int_prod: divide product and fixed target by constant factor");
1041 const AffineRelation::Relation r =
1043 const absl::int128 temp_coeff =
1044 absl::int128(old_target.coeffs(0)) * absl::int128(r.coeff);
1045 CHECK_EQ(temp_coeff % absl::int128(constant_factor), 0);
1046 const absl::int128 temp_offset =
1047 absl::int128(old_target.coeffs(0)) * absl::int128(r.offset) +
1048 absl::int128(old_target.offset());
1049 CHECK_EQ(temp_offset % absl::int128(constant_factor), 0);
1050 const absl::int128 new_coeff = temp_coeff / absl::int128(constant_factor);
1051 const absl::int128 new_offset =
1052 temp_offset / absl::int128(constant_factor);
1065 "int_prod: overflow during simplification.");
1069 proto->mutable_target()->set_coeffs(0,
static_cast<int64_t
>(new_coeff));
1070 proto->mutable_target()->set_vars(0, r.representative);
1071 proto->mutable_target()->set_offset(
static_cast<int64_t
>(new_offset));
1072 context_->
UpdateRuleStats(
"int_prod: divide product by constant factor");
1079 for (
const LinearExpressionProto& expr :
ct->int_prod().exprs()) {
1083 bool domain_modified =
false;
1085 &domain_modified)) {
1088 if (domain_modified) {
1092 if (
ct->int_prod().exprs_size() == 2) {
1093 LinearExpressionProto
a =
ct->int_prod().exprs(0);
1094 LinearExpressionProto
b =
ct->int_prod().exprs(1);
1095 const LinearExpressionProto product =
ct->int_prod().target();
1103 return RemoveConstraint(
ct);
1108 const LinearExpressionProto target_expr =
ct->int_prod().target();
1113 std::vector<int> literals;
1114 for (
const LinearExpressionProto& expr :
ct->int_prod().exprs()) {
1119 literals.push_back(lit);
1127 auto* arg = new_ct->mutable_bool_and();
1128 for (
const int lit : literals) {
1129 arg->add_literals(lit);
1136 for (
const int lit : literals) {
1141 return RemoveConstraint(
ct);
1144bool CpModelPresolver::PresolveIntDiv(ConstraintProto*
ct) {
1147 bool changed = CanonicalizeLinearExpression(
1148 *
ct,
ct->mutable_int_div()->mutable_target());
1149 for (LinearExpressionProto& exp : *(
ct->mutable_int_div()->mutable_exprs())) {
1150 changed |= CanonicalizeLinearExpression(*
ct, &exp);
1153 const LinearExpressionProto target =
ct->int_div().target();
1154 const LinearExpressionProto expr =
ct->int_div().exprs(0);
1155 const LinearExpressionProto div =
ct->int_div().exprs(1);
1162 return RemoveConstraint(
ct);
1168 return RemoveConstraint(
ct);
1172 if (!context_->
IsFixed(div))
return changed;
1174 const int64_t divisor = context_->
FixedValue(div);
1176 LinearConstraintProto*
const lin =
1184 return RemoveConstraint(
ct);
1186 bool domain_modified =
false;
1189 &domain_modified)) {
1192 if (domain_modified) {
1194 "int_div: updated domain of target in target = X / cte");
1200 if (context_->
MinOf(target) >= 0 && context_->
MinOf(expr) >= 0 &&
1202 LinearConstraintProto*
const lin =
1205 lin->add_domain(divisor - 1);
1210 "int_div: linearize positive division with a constant divisor");
1211 return RemoveConstraint(
ct);
1219bool CpModelPresolver::PresolveIntMod(ConstraintProto*
ct) {
1222 bool changed = CanonicalizeLinearExpression(
1223 *
ct,
ct->mutable_int_mod()->mutable_target());
1224 for (LinearExpressionProto& exp : *(
ct->mutable_int_mod()->mutable_exprs())) {
1225 changed |= CanonicalizeLinearExpression(*
ct, &exp);
1228 const LinearExpressionProto target =
ct->int_mod().target();
1229 const LinearExpressionProto expr =
ct->int_mod().exprs(0);
1230 const LinearExpressionProto mod =
ct->int_mod().exprs(1);
1232 bool domain_changed =
false;
1241 if (domain_changed) {
1248bool CpModelPresolver::ExploitEquivalenceRelations(
int c, ConstraintProto*
ct) {
1249 bool changed =
false;
1254 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kLinear) {
1255 for (
int& ref : *
ct->mutable_enforcement_literal()) {
1267 bool work_to_do =
false;
1270 if (r.representative !=
var) {
1275 if (!work_to_do)
return false;
1279 [&changed,
this](
int* ref) {
1290 [&changed,
this](
int* ref) {
1301bool CpModelPresolver::DivideLinearByGcd(ConstraintProto*
ct) {
1306 const int num_vars =
ct->linear().vars().size();
1307 for (
int i = 0; i < num_vars; ++i) {
1308 const int64_t magnitude = std::abs(
ct->linear().coeffs(i));
1310 if (gcd == 1)
break;
1314 for (
int i = 0; i < num_vars; ++i) {
1315 ct->mutable_linear()->set_coeffs(i,
ct->linear().coeffs(i) / gcd);
1319 if (
ct->linear().domain_size() == 0) {
1320 return MarkConstraintAsFalse(
ct);
1326template <
typename ProtoWithVarsAndCoeffs>
1327bool CpModelPresolver::CanonicalizeLinearExpressionInternal(
1328 const ConstraintProto&
ct, ProtoWithVarsAndCoeffs*
proto, int64_t* offset) {
1334 int64_t sum_of_fixed_terms = 0;
1335 bool remapped =
false;
1336 const int old_size =
proto->vars().size();
1338 for (
int i = 0; i < old_size; ++i) {
1339 const int ref =
proto->vars(i);
1341 const int64_t coeff =
1343 if (coeff == 0)
continue;
1346 sum_of_fixed_terms += coeff * context_->
MinOf(
var);
1352 bool removed =
false;
1353 for (
const int enf :
ct.enforcement_literal()) {
1357 sum_of_fixed_terms += coeff;
1366 context_->
UpdateRuleStats(
"linear: enforcement literal in expression");
1371 if (r.representative !=
var) {
1373 sum_of_fixed_terms += coeff * r.
offset;
1375 tmp_terms_.push_back({r.representative, coeff * r.coeff});
1377 proto->clear_vars();
1378 proto->clear_coeffs();
1379 std::sort(tmp_terms_.begin(), tmp_terms_.end());
1380 int current_var = 0;
1381 int64_t current_coeff = 0;
1382 for (
const auto entry : tmp_terms_) {
1384 if (entry.first == current_var) {
1385 current_coeff += entry.second;
1387 if (current_coeff != 0) {
1388 proto->add_vars(current_var);
1389 proto->add_coeffs(current_coeff);
1391 current_var = entry.first;
1392 current_coeff = entry.second;
1395 if (current_coeff != 0) {
1396 proto->add_vars(current_var);
1397 proto->add_coeffs(current_coeff);
1402 if (
proto->vars().size() < old_size) {
1405 *offset = sum_of_fixed_terms;
1406 return remapped ||
proto->vars().size() < old_size;
1409bool CpModelPresolver::CanonicalizeLinearExpression(
1410 const ConstraintProto&
ct, LinearExpressionProto* exp) {
1412 const bool result = CanonicalizeLinearExpressionInternal(
ct, exp, &offset);
1413 exp->set_offset(exp->offset() + offset);
1417bool CpModelPresolver::CanonicalizeLinear(ConstraintProto*
ct) {
1418 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1423 if (
ct->linear().domain().empty()) {
1425 return MarkConstraintAsFalse(
ct);
1430 CanonicalizeLinearExpressionInternal(*
ct,
ct->mutable_linear(), &offset);
1434 ct->mutable_linear());
1436 changed |= DivideLinearByGcd(
ct);
1440bool CpModelPresolver::RemoveSingletonInLinear(ConstraintProto*
ct) {
1441 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1446 std::set<int> index_to_erase;
1447 const int num_vars =
ct->linear().vars().size();
1453 for (
int i = 0; i < num_vars; ++i) {
1454 const int var =
ct->linear().vars(i);
1455 const int64_t coeff =
ct->linear().coeffs(i);
1459 const auto term_domain =
1461 if (!exact)
continue;
1465 if (new_rhs.NumIntervals() > 100)
continue;
1472 index_to_erase.insert(i);
1479 if (index_to_erase.empty()) {
1482 if (!
ct->enforcement_literal().empty())
return false;
1486 if (rhs.Min() != rhs.Max())
return false;
1488 for (
int i = 0; i < num_vars; ++i) {
1489 const int var =
ct->linear().vars(i);
1490 const int64_t coeff =
ct->linear().coeffs(i);
1511 if (objective_coeff % coeff != 0)
continue;
1515 const auto term_domain =
1517 if (!exact)
continue;
1519 if (new_rhs.NumIntervals() > 100)
continue;
1527 objective_coeff))) {
1551 LOG(
WARNING) <<
"This was not supposed to happen and the presolve "
1552 "could be improved.";
1560 context_->
UpdateRuleStats(
"linear: singleton column define objective.");
1563 return RemoveConstraint(
ct);
1575 "linear: singleton column in equality and in objective.");
1577 index_to_erase.insert(i);
1581 if (index_to_erase.empty())
return false;
1592 if (!
ct->enforcement_literal().empty()) {
1593 for (
const int i : index_to_erase) {
1594 const int var =
ct->linear().vars(i);
1609 for (
int i = 0; i < num_vars; ++i) {
1610 if (index_to_erase.count(i)) {
1614 ct->mutable_linear()->set_coeffs(new_size,
ct->linear().coeffs(i));
1615 ct->mutable_linear()->set_vars(new_size,
ct->linear().vars(i));
1618 ct->mutable_linear()->mutable_vars()->Truncate(new_size);
1619 ct->mutable_linear()->mutable_coeffs()->Truncate(new_size);
1621 DivideLinearByGcd(
ct);
1627bool CpModelPresolver::AddVarAffineRepresentativeFromLinearEquality(
1628 int target_index, ConstraintProto*
ct) {
1630 const int num_variables =
ct->linear().vars().size();
1631 for (
int i = 0; i < num_variables; ++i) {
1632 if (i == target_index)
continue;
1633 const int64_t magnitude = std::abs(
ct->linear().coeffs(i));
1635 if (gcd == 1)
return false;
1641 const int ref =
ct->linear().vars(target_index);
1642 const int64_t coeff =
ct->linear().coeffs(target_index);
1643 const int64_t rhs =
ct->linear().domain(0);
1647 if (coeff % gcd == 0)
return false;
1655 return CanonicalizeLinear(
ct);
1665bool CpModelPresolver::PresolveLinearEqualityWithModulo(ConstraintProto*
ct) {
1668 if (
ct->linear().domain().size() != 2)
return false;
1669 if (
ct->linear().domain(0) !=
ct->linear().domain(1))
return false;
1670 if (!
ct->enforcement_literal().empty())
return false;
1672 const int num_variables =
ct->linear().vars().size();
1673 if (num_variables < 2)
return false;
1675 std::vector<int> mod2_indices;
1676 std::vector<int> mod3_indices;
1677 std::vector<int> mod5_indices;
1679 int64_t min_magnitude;
1680 int num_smallest = 0;
1682 for (
int i = 0; i < num_variables; ++i) {
1683 const int64_t magnitude = std::abs(
ct->linear().coeffs(i));
1684 if (num_smallest == 0 || magnitude < min_magnitude) {
1685 min_magnitude = magnitude;
1688 }
else if (magnitude == min_magnitude) {
1692 if (magnitude % 2 != 0) mod2_indices.push_back(i);
1693 if (magnitude % 3 != 0) mod3_indices.push_back(i);
1694 if (magnitude % 5 != 0) mod5_indices.push_back(i);
1697 if (mod2_indices.size() == 2) {
1699 std::vector<int> literals;
1700 for (
const int i : mod2_indices) {
1701 const int ref =
ct->linear().vars(i);
1706 literals.push_back(ref);
1709 const int64_t rhs = std::abs(
ct->linear().domain(0));
1710 context_->
UpdateRuleStats(
"linear: only two odd Booleans in equality");
1722 if (mod2_indices.size() == 1) {
1723 return AddVarAffineRepresentativeFromLinearEquality(mod2_indices[0],
ct);
1725 if (mod3_indices.size() == 1) {
1726 return AddVarAffineRepresentativeFromLinearEquality(mod3_indices[0],
ct);
1728 if (mod5_indices.size() == 1) {
1729 return AddVarAffineRepresentativeFromLinearEquality(mod5_indices[0],
ct);
1731 if (num_smallest == 1) {
1732 return AddVarAffineRepresentativeFromLinearEquality(smallest_index,
ct);
1738bool CpModelPresolver::PresolveLinearOfSizeOne(ConstraintProto*
ct) {
1744 ?
ct->linear().coeffs(0)
1745 : -
ct->linear().coeffs(0);
1750 rhs.InverseMultiplicationBy(coeff))) {
1753 return RemoveConstraint(
ct);
1759 const bool zero_ok = rhs.Contains(0);
1760 const bool one_ok = rhs.Contains(
ct->linear().coeffs(0));
1762 if (!zero_ok && !one_ok) {
1763 return MarkConstraintAsFalse(
ct);
1765 if (zero_ok && one_ok) {
1766 return RemoveConstraint(
ct);
1768 const int ref =
ct->linear().vars(0);
1772 ct->mutable_bool_and()->add_literals(ref);
1783 if (
ct->linear().coeffs(0) == 1 &&
1786 context_->
UpdateRuleStats(
"linear1: remove abs from abs(x) in domain");
1787 const Domain implied_abs_target_domain =
1790 .IntersectionWith(context_->
DomainOf(
ct->linear().vars(0)));
1792 if (implied_abs_target_domain.IsEmpty()) {
1793 return MarkConstraintAsFalse(
ct);
1796 const Domain new_abs_var_domain =
1797 implied_abs_target_domain
1798 .UnionWith(implied_abs_target_domain.Negation())
1799 .IntersectionWith(context_->
DomainOf(abs_arg));
1801 if (new_abs_var_domain.IsEmpty()) {
1802 return MarkConstraintAsFalse(
ct);
1807 *new_ct->mutable_enforcement_literal() =
ct->enforcement_literal();
1808 auto* arg = new_ct->mutable_linear();
1809 arg->add_vars(abs_arg);
1813 return RemoveConstraint(
ct);
1817 if (
ct->enforcement_literal_size() != 1 ||
1818 (
ct->linear().coeffs(0) != 1 &&
ct->linear().coeffs(0) == -1)) {
1826 const int literal =
ct->enforcement_literal(0);
1827 const LinearConstraintProto& linear =
ct->linear();
1828 const int ref = linear.vars(0);
1830 const int64_t coeff =
1833 if (linear.domain_size() == 2 && linear.domain(0) == linear.domain(1)) {
1835 : -linear.domain(0) * coeff;
1846 if (complement.Size() != 1)
return false;
1848 : -complement.Min() * coeff;
1862bool CpModelPresolver::PresolveLinearOfSizeTwo(ConstraintProto*
ct) {
1865 const LinearConstraintProto& arg =
ct->linear();
1866 const int var1 = arg.vars(0);
1867 const int var2 = arg.vars(1);
1868 const int64_t coeff1 = arg.coeffs(0);
1869 const int64_t coeff2 = arg.coeffs(1);
1880 const bool is_equality =
1881 arg.domain_size() == 2 && arg.domain(0) == arg.domain(1);
1884 int64_t value_on_true, coeff;
1887 value_on_true = coeff1;
1892 value_on_true = coeff2;
1900 const Domain rhs_if_true =
1901 rhs.AdditionWith(Domain(-value_on_true)).InverseMultiplicationBy(coeff);
1902 const Domain rhs_if_false = rhs.InverseMultiplicationBy(coeff);
1903 const bool implied_false =
1905 const bool implied_true =
1907 if (implied_true && implied_false) {
1909 return MarkConstraintAsFalse(
ct);
1910 }
else if (implied_true) {
1911 context_->
UpdateRuleStats(
"linear2: Boolean with one feasible value.");
1916 new_ct->mutable_bool_and()->add_literals(lit);
1920 ct->mutable_linear()->Clear();
1921 ct->mutable_linear()->add_vars(
var);
1922 ct->mutable_linear()->add_coeffs(1);
1924 return PresolveLinearOfSizeOne(
ct) ||
true;
1925 }
else if (implied_false) {
1926 context_->
UpdateRuleStats(
"linear2: Boolean with one feasible value.");
1931 new_ct->mutable_bool_and()->add_literals(
NegatedRef(lit));
1935 ct->mutable_linear()->Clear();
1936 ct->mutable_linear()->add_vars(
var);
1937 ct->mutable_linear()->add_coeffs(1);
1939 return PresolveLinearOfSizeOne(
ct) ||
true;
1951 const int64_t rhs = arg.domain(0);
1952 if (
ct->enforcement_literal().empty()) {
1960 }
else if (coeff2 == 1) {
1962 }
else if (coeff1 == -1) {
1964 }
else if (coeff2 == -1) {
1977 if (added)
return RemoveConstraint(
ct);
1987 "linear2: implied ax + by = cte has no solutions");
1988 return MarkConstraintAsFalse(
ct);
1990 const Domain reduced_domain =
1996 .InverseMultiplicationBy(-
a));
1998 if (reduced_domain.IsEmpty()) {
2000 "linear2: implied ax + by = cte has no solutions");
2001 return MarkConstraintAsFalse(
ct);
2004 if (reduced_domain.Size() == 1) {
2005 const int64_t z = reduced_domain.FixedValue();
2006 const int64_t value1 = x0 +
b * z;
2007 const int64_t value2 = y0 -
a * z;
2011 DCHECK_EQ(coeff1 * value1 + coeff2 * value2, rhs);
2015 imply1->mutable_linear()->add_vars(var1);
2016 imply1->mutable_linear()->add_coeffs(1);
2017 imply1->mutable_linear()->add_domain(value1);
2018 imply1->mutable_linear()->add_domain(value1);
2022 imply2->mutable_linear()->add_vars(var2);
2023 imply2->mutable_linear()->add_coeffs(1);
2024 imply2->mutable_linear()->add_domain(value2);
2025 imply2->mutable_linear()->add_domain(value2);
2027 "linear2: implied ax + by = cte has only one solution");
2029 return RemoveConstraint(
ct);
2036bool CpModelPresolver::PresolveSmallLinear(ConstraintProto*
ct) {
2040 if (
ct->linear().vars().empty()) {
2043 if (rhs.Contains(0)) {
2044 return RemoveConstraint(
ct);
2046 return MarkConstraintAsFalse(
ct);
2048 }
else if (
ct->linear().vars().size() == 1) {
2049 return PresolveLinearOfSizeOne(
ct);
2050 }
else if (
ct->linear().vars().size() == 2) {
2051 return PresolveLinearOfSizeTwo(
ct);
2060bool IsLeConstraint(
const Domain& domain,
const Domain& all_values) {
2064 .IsIncludedIn(domain);
2068bool IsGeConstraint(
const Domain& domain,
const Domain& all_values) {
2072 .IsIncludedIn(domain);
2078bool RhsCanBeFixedToMin(int64_t coeff,
const Domain& var_domain,
2079 const Domain& terms,
const Domain& rhs) {
2080 if (var_domain.NumIntervals() != 1)
return false;
2081 if (std::abs(coeff) != 1)
return false;
2089 if (coeff == 1 && terms.Max() + var_domain.Min() <= rhs.Min()) {
2092 if (coeff == -1 && terms.Max() - var_domain.Max() <= rhs.Min()) {
2098bool RhsCanBeFixedToMax(int64_t coeff,
const Domain& var_domain,
2099 const Domain& terms,
const Domain& rhs) {
2100 if (var_domain.NumIntervals() != 1)
return false;
2101 if (std::abs(coeff) != 1)
return false;
2103 if (coeff == 1 && terms.Min() + var_domain.Max() >= rhs.Max()) {
2106 if (coeff == -1 && terms.Min() - var_domain.Min() >= rhs.Max()) {
2113void TakeIntersectionWith(
const absl::flat_hash_set<int>& current,
2114 absl::flat_hash_set<int>* to_clear) {
2115 std::vector<int> new_set;
2116 for (
const int c : *to_clear) {
2117 if (current.contains(c)) new_set.push_back(c);
2120 for (
const int c : new_set) to_clear->insert(c);
2125bool CpModelPresolver::DetectAndProcessOneSidedLinearConstraint(
2126 int c, ConstraintProto*
ct) {
2127 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear) {
2135 Domain implied_rhs(0);
2136 const int num_vars =
ct->linear().vars().size();
2137 for (
int i = 0; i < num_vars; ++i) {
2138 const int ref =
ct->linear().vars(i);
2139 const int64_t coeff =
ct->linear().coeffs(i);
2143 .RelaxIfTooComplex();
2148 if (implied_rhs.IsIncludedIn(old_rhs)) {
2150 return RemoveConstraint(
ct);
2154 const Domain rhs = old_rhs.SimplifyUsingImpliedDomain(implied_rhs);
2155 if (rhs.IsEmpty()) {
2157 return MarkConstraintAsFalse(
ct);
2159 if (rhs != old_rhs) {
2167 const bool is_le_constraint = IsLeConstraint(rhs, implied_rhs);
2168 const bool is_ge_constraint = IsGeConstraint(rhs, implied_rhs);
2169 if (!is_le_constraint && !is_ge_constraint)
return false;
2170 CHECK_NE(is_le_constraint, is_ge_constraint);
2172 bool recanonicalize =
false;
2173 for (
int i = 0; i < num_vars; ++i) {
2174 const int var =
ct->linear().vars(i);
2175 const int64_t var_coeff =
ct->linear().coeffs(i);
2178 if ((var_coeff > 0) == is_ge_constraint) {
2190 const int64_t obj_coeff =
2199 if (obj_coeff <= 0 &&
2208 recanonicalize =
true;
2213 if (obj_coeff >= 0 &&
2222 recanonicalize =
true;
2228 if (recanonicalize)
return CanonicalizeLinear(
ct);
2232bool CpModelPresolver::PropagateDomainsInLinear(
int ct_index,
2233 ConstraintProto*
ct) {
2234 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
2242 const int num_vars =
ct->linear().vars_size();
2243 term_domains.resize(num_vars + 1);
2244 left_domains.resize(num_vars + 1);
2245 left_domains[0] = Domain(0);
2246 for (
int i = 0; i < num_vars; ++i) {
2247 const int var =
ct->linear().vars(i);
2248 const int64_t coeff =
ct->linear().coeffs(i);
2251 left_domains[i + 1] =
2254 const Domain& implied_rhs = left_domains[num_vars];
2258 if (implied_rhs.IsIncludedIn(old_rhs)) {
2260 return RemoveConstraint(
ct);
2264 Domain rhs = old_rhs.SimplifyUsingImpliedDomain(implied_rhs);
2265 if (rhs.IsEmpty()) {
2267 return MarkConstraintAsFalse(
ct);
2269 if (rhs != old_rhs) {
2275 if (
ct->enforcement_literal().size() > 1)
return false;
2277 bool new_bounds =
false;
2278 bool recanonicalize =
false;
2279 Domain negated_rhs = rhs.Negation();
2280 Domain right_domain(0);
2282 Domain implied_term_domain;
2283 term_domains[num_vars] = Domain(0);
2284 for (
int i = num_vars - 1; i >= 0; --i) {
2285 const int var =
ct->linear().vars(i);
2286 const int64_t var_coeff =
ct->linear().coeffs(i);
2288 right_domain.AdditionWith(term_domains[i + 1]).RelaxIfTooComplex();
2289 implied_term_domain = left_domains[i].AdditionWith(right_domain);
2290 new_domain = implied_term_domain.AdditionWith(negated_rhs)
2291 .InverseMultiplicationBy(-var_coeff);
2293 if (
ct->enforcement_literal().empty()) {
2298 }
else if (
ct->enforcement_literal().size() == 1) {
2309 recanonicalize =
true;
2314 if (!
ct->enforcement_literal().empty())
continue;
2326 if (rhs.Min() != rhs.Max() &&
2329 const bool same_sign = (var_coeff > 0) == (obj_coeff > 0);
2331 if (same_sign && RhsCanBeFixedToMin(var_coeff, context_->
DomainOf(
var),
2332 implied_term_domain, rhs)) {
2333 rhs = Domain(rhs.Min());
2336 if (!same_sign && RhsCanBeFixedToMax(var_coeff, context_->
DomainOf(
var),
2337 implied_term_domain, rhs)) {
2338 rhs = Domain(rhs.Max());
2344 negated_rhs = rhs.Negation();
2348 right_domain = Domain(0);
2362 if (
ct->linear().vars().size() <= 2)
continue;
2367 if (rhs.Min() != rhs.Max())
continue;
2373 if (context_->
DomainOf(
var) != new_domain)
continue;
2374 if (std::abs(var_coeff) != 1)
continue;
2381 bool is_in_objective =
false;
2383 is_in_objective =
true;
2389 if (is_in_objective) col_size--;
2390 const int row_size =
ct->linear().vars_size();
2394 const int num_entries_added = (row_size - 1) * (col_size - 1);
2395 const int num_entries_removed = col_size + row_size - 1;
2397 if (num_entries_added > num_entries_removed) {
2403 std::vector<int> others;
2411 if (c == ct_index)
continue;
2413 ConstraintProto::ConstraintCase::kLinear) {
2417 for (
const int ref :
2424 others.push_back(c);
2426 if (abort)
continue;
2429 for (
const int c : others) {
2448 if (is_in_objective &&
2454 absl::StrCat(
"linear: variable substitution ", others.size()));
2467 LinearConstraintProto* mapping_linear_ct =
2470 std::swap(mapping_linear_ct->mutable_vars()->at(0),
2471 mapping_linear_ct->mutable_vars()->at(i));
2472 std::swap(mapping_linear_ct->mutable_coeffs()->at(0),
2473 mapping_linear_ct->mutable_coeffs()->at(i));
2474 return RemoveConstraint(
ct);
2479 if (recanonicalize)
return CanonicalizeLinear(
ct);
2490void CpModelPresolver::ExtractEnforcementLiteralFromLinearConstraint(
2491 int ct_index, ConstraintProto*
ct) {
2492 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
2497 const LinearConstraintProto& arg =
ct->linear();
2498 const int num_vars = arg.vars_size();
2502 if (num_vars <= 1)
return;
2504 int64_t min_sum = 0;
2505 int64_t max_sum = 0;
2506 int64_t max_coeff_magnitude = 0;
2507 for (
int i = 0; i < num_vars; ++i) {
2508 const int ref = arg.vars(i);
2509 const int64_t coeff = arg.coeffs(i);
2510 const int64_t term_a = coeff * context_->
MinOf(ref);
2511 const int64_t term_b = coeff * context_->
MaxOf(ref);
2512 max_coeff_magnitude =
std::max(max_coeff_magnitude, std::abs(coeff));
2513 min_sum +=
std::min(term_a, term_b);
2514 max_sum +=
std::max(term_a, term_b);
2523 const auto& domain =
ct->linear().domain();
2524 const int64_t ub_threshold = domain[domain.size() - 2] - min_sum;
2525 const int64_t lb_threshold = max_sum - domain[1];
2527 if (max_coeff_magnitude <
std::max(ub_threshold, lb_threshold))
return;
2546 const bool lower_bounded = min_sum < rhs_domain.Min();
2547 const bool upper_bounded = max_sum > rhs_domain.Max();
2548 if (!lower_bounded && !upper_bounded)
return;
2549 if (lower_bounded && upper_bounded) {
2553 if (!
ct->name().empty()) {
2554 new_ct1->set_name(absl::StrCat(
ct->name(),
" (part 1)"));
2557 new_ct1->mutable_linear());
2561 if (!
ct->name().empty()) {
2562 new_ct2->set_name(absl::StrCat(
ct->name(),
" (part 2)"));
2565 new_ct2->mutable_linear());
2568 return (
void)RemoveConstraint(
ct);
2574 const int64_t threshold = lower_bounded ? ub_threshold : lb_threshold;
2581 const bool only_booleans =
2588 int64_t rhs_offset = 0;
2589 bool some_integer_encoding_were_extracted =
false;
2590 LinearConstraintProto* mutable_arg =
ct->mutable_linear();
2591 for (
int i = 0; i < arg.vars_size(); ++i) {
2592 int ref = arg.vars(i);
2593 int64_t coeff = arg.coeffs(i);
2600 if (context_->
IsFixed(ref) || coeff < threshold ||
2601 (only_booleans && !is_boolean)) {
2603 mutable_arg->set_vars(new_size, mutable_arg->vars(i));
2604 mutable_arg->set_coeffs(new_size, mutable_arg->coeffs(i));
2612 some_integer_encoding_were_extracted =
true;
2614 "linear: extracted integer enforcement literal");
2616 if (lower_bounded) {
2617 ct->add_enforcement_literal(is_boolean
2620 ref, context_->
MinOf(ref)));
2621 rhs_offset -= coeff * context_->
MinOf(ref);
2623 ct->add_enforcement_literal(is_boolean
2626 ref, context_->
MaxOf(ref)));
2627 rhs_offset -= coeff * context_->
MaxOf(ref);
2630 mutable_arg->mutable_vars()->Truncate(new_size);
2631 mutable_arg->mutable_coeffs()->Truncate(new_size);
2633 if (some_integer_encoding_were_extracted) {
2639void CpModelPresolver::ExtractAtMostOneFromLinear(ConstraintProto*
ct) {
2644 const LinearConstraintProto& arg =
ct->linear();
2645 const int num_vars = arg.vars_size();
2646 int64_t min_sum = 0;
2647 int64_t max_sum = 0;
2648 for (
int i = 0; i < num_vars; ++i) {
2649 const int ref = arg.vars(i);
2650 const int64_t coeff = arg.coeffs(i);
2651 const int64_t term_a = coeff * context_->
MinOf(ref);
2652 const int64_t term_b = coeff * context_->
MaxOf(ref);
2653 min_sum +=
std::min(term_a, term_b);
2654 max_sum +=
std::max(term_a, term_b);
2656 for (
const int type : {0, 1}) {
2657 std::vector<int> at_most_one;
2658 for (
int i = 0; i < num_vars; ++i) {
2659 const int ref = arg.vars(i);
2660 const int64_t coeff = arg.coeffs(i);
2661 if (context_->
MinOf(ref) != 0)
continue;
2662 if (context_->
MaxOf(ref) != 1)
continue;
2667 if (min_sum + 2 * std::abs(coeff) > rhs.Max()) {
2668 at_most_one.push_back(coeff > 0 ? ref :
NegatedRef(ref));
2671 if (max_sum - 2 * std::abs(coeff) < rhs.Min()) {
2672 at_most_one.push_back(coeff > 0 ?
NegatedRef(ref) : ref);
2676 if (at_most_one.size() > 1) {
2684 for (
const int ref : at_most_one) {
2685 new_ct->mutable_at_most_one()->add_literals(ref);
2694bool CpModelPresolver::PresolveLinearOnBooleans(ConstraintProto*
ct) {
2695 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
2700 const LinearConstraintProto& arg =
ct->linear();
2701 const int num_vars = arg.vars_size();
2703 int64_t max_coeff = 0;
2704 int64_t min_sum = 0;
2705 int64_t max_sum = 0;
2706 for (
int i = 0; i < num_vars; ++i) {
2708 const int var = arg.vars(i);
2709 const int64_t coeff = arg.coeffs(i);
2712 if (context_->
MinOf(
var) != 0)
return false;
2713 if (context_->
MaxOf(
var) != 1)
return false;
2717 min_coeff =
std::min(min_coeff, coeff);
2718 max_coeff =
std::max(max_coeff, coeff);
2722 min_coeff =
std::min(min_coeff, -coeff);
2723 max_coeff =
std::max(max_coeff, -coeff);
2735 if ((!rhs_domain.Contains(min_sum) &&
2736 min_sum + min_coeff > rhs_domain.Max()) ||
2737 (!rhs_domain.Contains(max_sum) &&
2738 max_sum - min_coeff < rhs_domain.Min())) {
2739 context_->
UpdateRuleStats(
"linear: all booleans and trivially false");
2740 return MarkConstraintAsFalse(
ct);
2742 if (Domain(min_sum, max_sum).IsIncludedIn(rhs_domain)) {
2744 return RemoveConstraint(
ct);
2751 DCHECK(!rhs_domain.IsEmpty());
2752 if (min_sum + min_coeff > rhs_domain.Max()) {
2755 const auto copy = arg;
2756 ct->mutable_bool_and()->clear_literals();
2757 for (
int i = 0; i < num_vars; ++i) {
2758 ct->mutable_bool_and()->add_literals(
2759 copy.coeffs(i) > 0 ?
NegatedRef(copy.vars(i)) : copy.vars(i));
2761 PresolveBoolAnd(
ct);
2763 }
else if (max_sum - min_coeff < rhs_domain.Min()) {
2766 const auto copy = arg;
2767 ct->mutable_bool_and()->clear_literals();
2768 for (
int i = 0; i < num_vars; ++i) {
2769 ct->mutable_bool_and()->add_literals(
2770 copy.coeffs(i) > 0 ? copy.vars(i) :
NegatedRef(copy.vars(i)));
2772 PresolveBoolAnd(
ct);
2774 }
else if (min_sum + min_coeff >= rhs_domain.Min() &&
2775 rhs_domain.front().end >= max_sum) {
2778 const auto copy = arg;
2779 ct->mutable_bool_or()->clear_literals();
2780 for (
int i = 0; i < num_vars; ++i) {
2781 ct->mutable_bool_or()->add_literals(
2782 copy.coeffs(i) > 0 ? copy.vars(i) :
NegatedRef(copy.vars(i)));
2786 }
else if (max_sum - min_coeff <= rhs_domain.Max() &&
2787 rhs_domain.back().start <= min_sum) {
2790 const auto copy = arg;
2791 ct->mutable_bool_or()->clear_literals();
2792 for (
int i = 0; i < num_vars; ++i) {
2793 ct->mutable_bool_or()->add_literals(
2794 copy.coeffs(i) > 0 ?
NegatedRef(copy.vars(i)) : copy.vars(i));
2799 min_sum + max_coeff <= rhs_domain.Max() &&
2800 min_sum + 2 * min_coeff > rhs_domain.Max() &&
2801 rhs_domain.back().start <= min_sum) {
2804 const auto copy = arg;
2805 ct->mutable_at_most_one()->clear_literals();
2806 for (
int i = 0; i < num_vars; ++i) {
2807 ct->mutable_at_most_one()->add_literals(
2808 copy.coeffs(i) > 0 ? copy.vars(i) :
NegatedRef(copy.vars(i)));
2812 max_sum - max_coeff >= rhs_domain.Min() &&
2813 max_sum - 2 * min_coeff < rhs_domain.Min() &&
2814 rhs_domain.front().end >= max_sum) {
2817 const auto copy = arg;
2818 ct->mutable_at_most_one()->clear_literals();
2819 for (
int i = 0; i < num_vars; ++i) {
2820 ct->mutable_at_most_one()->add_literals(
2821 copy.coeffs(i) > 0 ?
NegatedRef(copy.vars(i)) : copy.vars(i));
2825 min_sum < rhs_domain.Min() &&
2826 min_sum + min_coeff >= rhs_domain.Min() &&
2827 min_sum + 2 * min_coeff > rhs_domain.Max() &&
2828 min_sum + max_coeff <= rhs_domain.Max()) {
2832 for (
int i = 0; i < num_vars; ++i) {
2833 exactly_one->mutable_exactly_one()->add_literals(
2834 arg.coeffs(i) > 0 ? arg.vars(i) :
NegatedRef(arg.vars(i)));
2837 return RemoveConstraint(
ct);
2839 max_sum > rhs_domain.Max() &&
2840 max_sum - min_coeff <= rhs_domain.Max() &&
2841 max_sum - 2 * min_coeff < rhs_domain.Min() &&
2842 max_sum - max_coeff >= rhs_domain.Min()) {
2846 for (
int i = 0; i < num_vars; ++i) {
2847 exactly_one->mutable_exactly_one()->add_literals(
2848 arg.coeffs(i) > 0 ?
NegatedRef(arg.vars(i)) : arg.vars(i));
2851 return RemoveConstraint(
ct);
2858 if (num_vars > 3)
return false;
2863 const int max_mask = (1 << arg.vars_size());
2864 for (
int mask = 0; mask < max_mask; ++mask) {
2866 for (
int i = 0; i < num_vars; ++i) {
2867 if ((mask >> i) & 1)
value += arg.coeffs(i);
2869 if (rhs_domain.Contains(
value))
continue;
2875 *new_ct->mutable_enforcement_literal() =
ct->enforcement_literal();
2877 for (
int i = 0; i < num_vars; ++i) {
2878 new_arg->add_literals(((mask >> i) & 1) ?
NegatedRef(arg.vars(i))
2884 return RemoveConstraint(
ct);
2887bool CpModelPresolver::PresolveInterval(
int c, ConstraintProto*
ct) {
2889 IntervalConstraintProto*
interval =
ct->mutable_interval();
2892 if (!
ct->enforcement_literal().empty() && context_->
SizeMax(c) < 0) {
2893 context_->
UpdateRuleStats(
"interval: negative size implies unperformed");
2894 return MarkConstraintAsFalse(
ct);
2897 bool changed =
false;
2898 if (
ct->enforcement_literal().empty()) {
2906 "interval: performed intervals must have a positive size");
2909 changed |= CanonicalizeLinearExpression(*
ct,
interval->mutable_start());
2910 changed |= CanonicalizeLinearExpression(*
ct,
interval->mutable_size());
2911 changed |= CanonicalizeLinearExpression(*
ct,
interval->mutable_end());
2916bool CpModelPresolver::PresolveInverse(ConstraintProto*
ct) {
2917 const int size =
ct->inverse().f_direct().size();
2918 bool changed =
false;
2921 for (
const int ref :
ct->inverse().f_direct()) {
2923 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
2927 for (
const int ref :
ct->inverse().f_inverse()) {
2929 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
2939 absl::flat_hash_set<int> direct_vars;
2940 for (
const int ref :
ct->inverse().f_direct()) {
2941 const auto [it, inserted] = direct_vars.insert(
PositiveRef(ref));
2947 absl::flat_hash_set<int> inverse_vars;
2948 for (
const int ref :
ct->inverse().f_inverse()) {
2949 const auto [it, inserted] = inverse_vars.insert(
PositiveRef(ref));
2959 const auto filter_inverse_domain =
2960 [
this, size, &changed](
const auto& direct,
const auto& inverse) {
2962 std::vector<absl::flat_hash_set<int64_t>> inverse_values(size);
2963 for (
int i = 0; i < size; ++i) {
2964 const Domain domain = context_->
DomainOf(inverse[i]);
2965 for (
const int64_t j : domain.Values()) {
2966 inverse_values[i].insert(j);
2973 std::vector<int64_t> possible_values;
2974 for (
int i = 0; i < size; ++i) {
2975 possible_values.clear();
2976 const Domain domain = context_->
DomainOf(direct[i]);
2977 bool removed_value =
false;
2978 for (
const int64_t j : domain.Values()) {
2979 if (inverse_values[j].contains(i)) {
2980 possible_values.push_back(j);
2982 removed_value =
true;
2985 if (removed_value) {
2989 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
2997 if (!filter_inverse_domain(
ct->inverse().f_direct(),
2998 ct->inverse().f_inverse())) {
3002 if (!filter_inverse_domain(
ct->inverse().f_inverse(),
3003 ct->inverse().f_direct())) {
3014bool CpModelPresolver::PresolveElement(ConstraintProto*
ct) {
3017 if (
ct->element().vars().empty()) {
3022 const int index_ref =
ct->element().index();
3023 const int target_ref =
ct->element().target();
3028 bool all_constants =
true;
3029 absl::flat_hash_set<int64_t> constant_set;
3030 bool all_included_in_target_domain =
true;
3033 bool reduced_index_domain =
false;
3035 Domain(0,
ct->element().vars_size() - 1),
3036 &reduced_index_domain)) {
3044 std::vector<int64_t> possible_indices;
3045 const Domain& index_domain = context_->
DomainOf(index_ref);
3046 for (
const int64_t index_value : index_domain.Values()) {
3047 const int ref =
ct->element().vars(index_value);
3048 const int64_t target_value =
3049 target_ref == index_ref ? index_value : -index_value;
3051 possible_indices.push_back(target_value);
3054 if (possible_indices.size() < index_domain.Size()) {
3060 "element: reduced index domain when target equals index");
3066 Domain infered_domain;
3067 const Domain& initial_index_domain = context_->
DomainOf(index_ref);
3068 const Domain& target_domain = context_->
DomainOf(target_ref);
3069 std::vector<int64_t> possible_indices;
3070 for (
const int64_t
value : initial_index_domain.Values()) {
3073 const int ref =
ct->element().vars(
value);
3074 const Domain& domain = context_->
DomainOf(ref);
3075 if (domain.IntersectionWith(target_domain).IsEmpty())
continue;
3076 possible_indices.push_back(
value);
3077 if (domain.IsFixed()) {
3078 constant_set.insert(domain.Min());
3080 all_constants =
false;
3082 if (!domain.IsIncludedIn(target_domain)) {
3083 all_included_in_target_domain =
false;
3085 infered_domain = infered_domain.
UnionWith(domain);
3087 if (possible_indices.size() < initial_index_domain.Size()) {
3094 bool domain_modified =
false;
3096 &domain_modified)) {
3099 if (domain_modified) {
3105 if (context_->
IsFixed(index_ref)) {
3106 const int var =
ct->element().vars(context_->
MinOf(index_ref));
3107 if (
var != target_ref) {
3108 LinearConstraintProto*
const lin =
3111 lin->add_coeffs(-1);
3112 lin->add_vars(target_ref);
3119 return RemoveConstraint(
ct);
3125 if (all_constants && constant_set.size() == 1) {
3128 return RemoveConstraint(
ct);
3133 if (context_->
MinOf(index_ref) == 0 && context_->
MaxOf(index_ref) == 1 &&
3135 const int64_t v0 = context_->
MinOf(
ct->element().vars(0));
3136 const int64_t v1 = context_->
MinOf(
ct->element().vars(1));
3138 LinearConstraintProto*
const lin =
3142 lin->add_vars(index_ref);
3143 lin->add_coeffs(v0 - v1);
3144 lin->add_domain(v0);
3145 lin->add_domain(v0);
3147 context_->
UpdateRuleStats(
"element: linearize constant element of size 2");
3148 return RemoveConstraint(
ct);
3152 const AffineRelation::Relation r_index =
3154 if (r_index.representative != index_ref) {
3156 if (context_->
DomainOf(r_index.representative).
Size() >
3162 const int64_t r_min = context_->
MinOf(r_ref);
3163 const int64_t r_max = context_->
MaxOf(r_ref);
3164 const int array_size =
ct->element().vars_size();
3166 context_->
UpdateRuleStats(
"TODO element: representative has bad domain");
3167 }
else if (r_index.offset >= 0 && r_index.offset < array_size &&
3168 r_index.offset + r_max * r_index.coeff >= 0 &&
3169 r_index.offset + r_max * r_index.coeff < array_size) {
3171 ElementConstraintProto*
const element =
3173 for (int64_t v = 0; v <= r_max; ++v) {
3174 const int64_t scaled_index = v * r_index.coeff + r_index.offset;
3176 CHECK_LT(scaled_index, array_size);
3177 element->add_vars(
ct->element().vars(scaled_index));
3179 element->set_index(r_ref);
3180 element->set_target(target_ref);
3182 if (r_index.coeff == 1) {
3188 return RemoveConstraint(
ct);
3199 absl::flat_hash_map<int, int> local_var_occurrence_counter;
3200 local_var_occurrence_counter[
PositiveRef(index_ref)]++;
3201 local_var_occurrence_counter[
PositiveRef(target_ref)]++;
3207 const int ref =
ct->element().vars(
value);
3213 local_var_occurrence_counter.at(
PositiveRef(index_ref)) == 1) {
3214 if (all_constants) {
3218 context_->
UpdateRuleStats(
"element: trivial target domain reduction");
3221 return RemoveConstraint(
ct);
3227 if (!context_->
IsFixed(target_ref) &&
3229 local_var_occurrence_counter.at(
PositiveRef(target_ref)) == 1) {
3230 if (all_included_in_target_domain) {
3234 return RemoveConstraint(
ct);
3243bool CpModelPresolver::PresolveTable(ConstraintProto*
ct) {
3246 if (
ct->table().vars().empty()) {
3248 return RemoveConstraint(
ct);
3251 const int initial_num_vars =
ct->table().vars_size();
3252 bool changed =
true;
3255 std::vector<AffineRelation::Relation> affine_relations;
3256 std::vector<int64_t> old_var_lb;
3257 std::vector<int64_t> old_var_ub;
3259 for (
int v = 0; v < initial_num_vars; ++v) {
3260 const int ref =
ct->table().vars(v);
3262 affine_relations.push_back(r);
3263 old_var_lb.push_back(context_->
MinOf(ref));
3264 old_var_ub.push_back(context_->
MaxOf(ref));
3265 if (r.representative != ref) {
3267 ct->mutable_table()->set_vars(v, r.representative);
3269 "table: replace variable by canonical affine one");
3278 std::vector<int> old_index_of_duplicate_to_new_index_of_first_occurrence(
3279 initial_num_vars, -1);
3281 std::vector<int> old_index_to_new_index(initial_num_vars, -1);
3284 absl::flat_hash_map<int, int> first_visit;
3285 for (
int p = 0; p < initial_num_vars; ++p) {
3286 const int ref =
ct->table().vars(p);
3288 const auto& it = first_visit.find(
var);
3289 if (it != first_visit.end()) {
3290 const int previous = it->second;
3291 old_index_of_duplicate_to_new_index_of_first_occurrence[p] = previous;
3295 ct->mutable_table()->set_vars(num_vars, ref);
3296 first_visit[
var] = num_vars;
3297 old_index_to_new_index[p] = num_vars;
3302 if (num_vars < initial_num_vars) {
3303 ct->mutable_table()->mutable_vars()->Truncate(num_vars);
3310 std::vector<std::vector<int64_t>> new_tuples;
3311 const int initial_num_tuples =
ct->table().values_size() / initial_num_vars;
3312 std::vector<absl::flat_hash_set<int64_t>> new_domains(num_vars);
3315 std::vector<int64_t> tuple(num_vars);
3316 new_tuples.reserve(initial_num_tuples);
3317 for (
int i = 0; i < initial_num_tuples; ++i) {
3318 bool delete_row =
false;
3320 for (
int j = 0; j < initial_num_vars; ++j) {
3321 const int64_t old_value =
ct->table().values(i * initial_num_vars + j);
3325 if (old_value < old_var_lb[j] || old_value > old_var_ub[j]) {
3331 const AffineRelation::Relation& r = affine_relations[j];
3332 const int64_t
value = (old_value - r.offset) / r.coeff;
3333 if (
value * r.coeff + r.offset != old_value) {
3338 const int mapped_position = old_index_to_new_index[j];
3339 if (mapped_position == -1) {
3340 const int new_index_of_first_occurrence =
3341 old_index_of_duplicate_to_new_index_of_first_occurrence[j];
3342 if (
value != tuple[new_index_of_first_occurrence]) {
3347 const int ref =
ct->table().vars(mapped_position);
3352 tuple[mapped_position] =
value;
3359 new_tuples.push_back(tuple);
3360 for (
int j = 0; j < num_vars; ++j) {
3361 new_domains[j].insert(tuple[j]);
3365 if (new_tuples.size() < initial_num_tuples) {
3372 ct->mutable_table()->clear_values();
3373 for (
const std::vector<int64_t>& t : new_tuples) {
3374 for (
const int64_t v : t) {
3375 ct->mutable_table()->add_values(v);
3381 if (
ct->table().negated())
return changed;
3384 for (
int j = 0; j < num_vars; ++j) {
3385 const int ref =
ct->table().vars(j);
3389 new_domains[j].end())),
3397 if (num_vars == 1) {
3400 return RemoveConstraint(
ct);
3405 for (
int j = 0; j < num_vars; ++j) prod *= new_domains[j].size();
3406 if (prod == new_tuples.size()) {
3408 return RemoveConstraint(
ct);
3414 if (new_tuples.size() > 0.7 * prod) {
3416 std::vector<std::vector<int64_t>> var_to_values(num_vars);
3417 for (
int j = 0; j < num_vars; ++j) {
3418 var_to_values[j].assign(new_domains[j].begin(), new_domains[j].end());
3420 std::vector<std::vector<int64_t>> all_tuples(prod);
3421 for (
int i = 0; i < prod; ++i) {
3422 all_tuples[i].resize(num_vars);
3424 for (
int j = 0; j < num_vars; ++j) {
3425 all_tuples[i][j] = var_to_values[j][
index % var_to_values[j].size()];
3426 index /= var_to_values[j].size();
3432 std::vector<std::vector<int64_t>> diff(prod - new_tuples.size());
3433 std::set_difference(all_tuples.begin(), all_tuples.end(),
3434 new_tuples.begin(), new_tuples.end(), diff.begin());
3437 ct->mutable_table()->set_negated(!
ct->table().negated());
3438 ct->mutable_table()->clear_values();
3439 for (
const std::vector<int64_t>& t : diff) {
3440 for (
const int64_t v : t)
ct->mutable_table()->add_values(v);
3447bool CpModelPresolver::PresolveAllDiff(ConstraintProto*
ct) {
3451 AllDifferentConstraintProto& all_diff = *
ct->mutable_all_diff();
3453 bool constraint_has_changed =
false;
3454 for (LinearExpressionProto& exp :
3455 *(
ct->mutable_all_diff()->mutable_exprs())) {
3456 constraint_has_changed |= CanonicalizeLinearExpression(*
ct, &exp);
3460 const int size = all_diff.exprs_size();
3463 return RemoveConstraint(
ct);
3467 return RemoveConstraint(
ct);
3470 bool something_was_propagated =
false;
3471 std::vector<LinearExpressionProto> kept_expressions;
3472 for (
int i = 0; i < size; ++i) {
3473 if (!context_->
IsFixed(all_diff.exprs(i))) {
3474 kept_expressions.push_back(all_diff.exprs(i));
3478 const int64_t
value = context_->
MinOf(all_diff.exprs(i));
3479 bool propagated =
false;
3480 for (
int j = 0; j < size; ++j) {
3481 if (i == j)
continue;
3484 Domain(
value).Complement())) {
3492 something_was_propagated =
true;
3499 kept_expressions.begin(), kept_expressions.end(),
3500 [](
const LinearExpressionProto& expr_a,
3501 const LinearExpressionProto& expr_b) {
3502 DCHECK_EQ(expr_a.vars_size(), 1);
3503 DCHECK_EQ(expr_b.vars_size(), 1);
3504 const int ref_a = expr_a.vars(0);
3505 const int ref_b = expr_b.vars(0);
3506 const int64_t coeff_a = expr_a.coeffs(0);
3507 const int64_t coeff_b = expr_b.coeffs(0);
3508 const int64_t abs_coeff_a = std::abs(coeff_a);
3509 const int64_t abs_coeff_b = std::abs(coeff_b);
3510 const int64_t offset_a = expr_a.offset();
3511 const int64_t offset_b = expr_b.offset();
3512 const int64_t abs_offset_a = std::abs(offset_a);
3513 const int64_t abs_offset_b = std::abs(offset_b);
3514 return std::tie(ref_a, abs_coeff_a, coeff_a, abs_offset_a, offset_a) <
3515 std::tie(ref_b, abs_coeff_b, coeff_b, abs_offset_b, offset_b);
3521 for (
int i = 1; i < kept_expressions.size(); ++i) {
3523 kept_expressions[i - 1], 1)) {
3525 "Duplicate variable in all_diff");
3528 kept_expressions[i - 1], -1)) {
3529 bool domain_modified =
false;
3531 Domain(0).Complement(),
3532 &domain_modified)) {
3535 if (domain_modified) {
3537 "all_diff: remove 0 from expression appearing with its "
3543 if (kept_expressions.size() < all_diff.exprs_size()) {
3544 all_diff.clear_exprs();
3545 for (
const LinearExpressionProto& expr : kept_expressions) {
3546 *all_diff.add_exprs() = expr;
3549 something_was_propagated =
true;
3550 constraint_has_changed =
true;
3551 if (kept_expressions.size() <= 1)
continue;
3555 CHECK_GE(all_diff.exprs_size(), 2);
3557 for (
int i = 1; i < all_diff.exprs_size(); ++i) {
3560 if (all_diff.exprs_size() == domain.Size()) {
3561 absl::flat_hash_map<int64_t, std::vector<LinearExpressionProto>>
3563 for (
const LinearExpressionProto& expr : all_diff.exprs()) {
3564 for (
const int64_t v : context_->
DomainOf(expr.vars(0)).
Values()) {
3565 value_to_exprs[expr.coeffs(0) * v + expr.offset()].push_back(expr);
3568 bool propagated =
false;
3569 for (
const auto& it : value_to_exprs) {
3570 if (it.second.size() == 1 && !context_->
IsFixed(it.second.front())) {
3571 const LinearExpressionProto& expr = it.second.
front();
3580 "all_diff: propagated mandatory values in permutation");
3581 something_was_propagated =
true;
3584 if (!something_was_propagated)
break;
3587 return constraint_has_changed;
3594std::vector<int> GetLiteralsFromSetPPCConstraint(
const ConstraintProto&
ct) {
3595 std::vector<int> sorted_literals;
3597 for (
const int literal :
ct.at_most_one().literals()) {
3598 sorted_literals.push_back(
literal);
3601 for (
const int literal :
ct.bool_or().literals()) {
3602 sorted_literals.push_back(
literal);
3605 for (
const int literal :
ct.exactly_one().literals()) {
3606 sorted_literals.push_back(
literal);
3609 std::sort(sorted_literals.begin(), sorted_literals.end());
3610 return sorted_literals;
3615void AddImplication(
int lhs,
int rhs, CpModelProto*
proto,
3616 absl::flat_hash_map<int, int>* ref_to_bool_and) {
3617 if (ref_to_bool_and->contains(lhs)) {
3618 const int ct_index = (*ref_to_bool_and)[lhs];
3620 }
else if (ref_to_bool_and->contains(
NegatedRef(rhs))) {
3621 const int ct_index = (*ref_to_bool_and)[
NegatedRef(rhs)];
3627 ct->add_enforcement_literal(lhs);
3628 ct->mutable_bool_and()->add_literals(rhs);
3632template <
typename ClauseContainer>
3633void ExtractClauses(
bool use_bool_and,
const ClauseContainer& container,
3634 CpModelProto*
proto) {
3641 absl::flat_hash_map<int, int> ref_to_bool_and;
3642 for (
int i = 0; i < container.NumClauses(); ++i) {
3643 const std::vector<Literal>& clause = container.Clause(i);
3644 if (clause.empty())
continue;
3647 if (use_bool_and && clause.size() == 2) {
3648 const int a = clause[0].IsPositive()
3649 ? clause[0].Variable().value()
3651 const int b = clause[1].IsPositive()
3652 ? clause[1].Variable().value()
3660 for (
const Literal l : clause) {
3661 if (l.IsPositive()) {
3662 ct->mutable_bool_or()->add_literals(l.Variable().value());
3664 ct->mutable_bool_or()->add_literals(
NegatedRef(l.Variable().value()));
3672bool CpModelPresolver::PresolveNoOverlap(ConstraintProto*
ct) {
3674 NoOverlapConstraintProto*
proto =
ct->mutable_no_overlap();
3675 bool changed =
false;
3679 const int initial_num_intervals =
proto->intervals_size();
3682 for (
int i = 0; i < initial_num_intervals; ++i) {
3683 const int interval_index =
proto->intervals(i);
3688 proto->set_intervals(new_size++, interval_index);
3691 if (new_size < initial_num_intervals) {
3692 proto->mutable_intervals()->Truncate(new_size);
3699 if (
proto->intervals_size() > 1) {
3700 std::vector<IndexedInterval> indexed_intervals;
3701 for (
int i = 0; i <
proto->intervals().size(); ++i) {
3703 indexed_intervals.push_back({
index,
3707 std::vector<std::vector<int>> components;
3710 if (components.size() > 1) {
3711 for (
const std::vector<int>& intervals : components) {
3712 if (intervals.size() <= 1)
continue;
3714 NoOverlapConstraintProto* new_no_overlap =
3718 for (
const int i : intervals) {
3723 context_->
UpdateRuleStats(
"no_overlap: split into disjoint components");
3724 return RemoveConstraint(
ct);
3728 std::vector<int> constant_intervals;
3729 int64_t size_min_of_non_constant_intervals =
3731 for (
int i = 0; i <
proto->intervals_size(); ++i) {
3732 const int interval_index =
proto->intervals(i);
3734 constant_intervals.push_back(interval_index);
3736 size_min_of_non_constant_intervals =
3737 std::min(size_min_of_non_constant_intervals,
3738 context_->
SizeMin(interval_index));
3742 if (!constant_intervals.empty()) {
3744 std::sort(constant_intervals.begin(), constant_intervals.end(),
3745 [
this](
int i1,
int i2) {
3746 return context_->StartMin(i1) < context_->StartMin(i2);
3752 for (
int i = 0; i + 1 < constant_intervals.size(); ++i) {
3753 if (context_->
EndMax(constant_intervals[i]) >
3754 context_->
StartMin(constant_intervals[i + 1])) {
3760 if (constant_intervals.size() ==
proto->intervals_size()) {
3762 return RemoveConstraint(
ct);
3765 absl::flat_hash_set<int> intervals_to_remove;
3769 for (
int i = 0; i + 1 < constant_intervals.size(); ++i) {
3770 const int start = i;
3771 while (i + 1 < constant_intervals.size() &&
3772 context_->
StartMin(constant_intervals[i + 1]) -
3773 context_->
EndMax(constant_intervals[i]) <
3774 size_min_of_non_constant_intervals) {
3777 if (i == start)
continue;
3778 for (
int j = start; j <= i; ++j) {
3779 intervals_to_remove.insert(constant_intervals[j]);
3781 const int64_t new_start = context_->
StartMin(constant_intervals[start]);
3782 const int64_t new_end = context_->
EndMax(constant_intervals[i]);
3784 IntervalConstraintProto* new_interval =
3787 new_interval->mutable_size()->set_offset(new_end - new_start);
3788 new_interval->mutable_end()->set_offset(new_end);
3792 if (!intervals_to_remove.empty()) {
3794 const int old_size =
proto->intervals_size();
3795 for (
int i = 0; i < old_size; ++i) {
3796 const int interval_index =
proto->intervals(i);
3797 if (intervals_to_remove.contains(interval_index)) {
3800 proto->set_intervals(new_size++, interval_index);
3803 proto->mutable_intervals()->Truncate(new_size);
3805 "no_overlap: merge constant contiguous intervals");
3806 intervals_to_remove.clear();
3807 constant_intervals.clear();
3813 if (
proto->intervals_size() == 1) {
3815 return RemoveConstraint(
ct);
3817 if (
proto->intervals().empty()) {
3819 return RemoveConstraint(
ct);
3825bool CpModelPresolver::PresolveNoOverlap2D(
int c, ConstraintProto*
ct) {
3830 const NoOverlap2DConstraintProto&
proto =
ct->no_overlap_2d();
3831 const int initial_num_boxes =
proto.x_intervals_size();
3833 bool has_zero_sizes =
false;
3834 bool x_constant =
true;
3835 bool y_constant =
true;
3839 std::vector<Rectangle> bounding_boxes;
3840 std::vector<int> active_boxes;
3841 for (
int i = 0; i <
proto.x_intervals_size(); ++i) {
3842 const int x_interval_index =
proto.x_intervals(i);
3843 const int y_interval_index =
proto.y_intervals(i);
3850 if (
proto.boxes_with_null_area_can_overlap() &&
3851 (context_->
SizeMax(x_interval_index) == 0 ||
3852 context_->
SizeMax(y_interval_index) == 0)) {
3853 if (
proto.boxes_with_null_area_can_overlap())
continue;
3854 has_zero_sizes =
true;
3856 ct->mutable_no_overlap_2d()->set_x_intervals(new_size, x_interval_index);
3857 ct->mutable_no_overlap_2d()->set_y_intervals(new_size, y_interval_index);
3858 bounding_boxes.push_back(
3859 {IntegerValue(context_->
StartMin(x_interval_index)),
3860 IntegerValue(context_->
EndMax(x_interval_index)),
3861 IntegerValue(context_->
StartMin(y_interval_index)),
3862 IntegerValue(context_->
EndMax(y_interval_index))});
3863 active_boxes.push_back(new_size);
3875 bounding_boxes, absl::MakeSpan(active_boxes));
3876 if (components.size() > 1) {
3877 for (
const absl::Span<int> boxes : components) {
3878 if (boxes.size() <= 1)
continue;
3880 NoOverlap2DConstraintProto* new_no_overlap_2d =
3882 for (
const int b : boxes) {
3884 new_no_overlap_2d->add_y_intervals(
proto.y_intervals(
b));
3888 context_->
UpdateRuleStats(
"no_overlap_2d: split into disjoint components");
3889 return RemoveConstraint(
ct);
3892 if (!has_zero_sizes && (x_constant || y_constant)) {
3894 "no_overlap_2d: a dimension is constant, splitting into many no "
3896 std::vector<IndexedInterval> indexed_intervals;
3897 for (
int i = 0; i < new_size; ++i) {
3898 int x =
proto.x_intervals(i);
3899 int y =
proto.y_intervals(i);
3901 indexed_intervals.push_back({x, IntegerValue(context_->
StartMin(y)),
3902 IntegerValue(context_->
EndMax(y))});
3904 std::vector<std::vector<int>> no_overlaps;
3907 for (
const std::vector<int>& no_overlap : no_overlaps) {
3911 for (
const int i : no_overlap) {
3916 return RemoveConstraint(
ct);
3919 if (new_size < initial_num_boxes) {
3921 ct->mutable_no_overlap_2d()->mutable_x_intervals()->Truncate(new_size);
3922 ct->mutable_no_overlap_2d()->mutable_y_intervals()->Truncate(new_size);
3925 if (new_size == 0) {
3927 return RemoveConstraint(
ct);
3930 if (new_size == 1) {
3932 return RemoveConstraint(
ct);
3935 return new_size < initial_num_boxes;
3939LinearExpressionProto ConstantExpressionProto(int64_t
value) {
3940 LinearExpressionProto expr;
3941 expr.set_offset(
value);
3946bool CpModelPresolver::PresolveCumulative(ConstraintProto*
ct) {
3949 CumulativeConstraintProto*
proto =
ct->mutable_cumulative();
3951 bool changed = CanonicalizeLinearExpression(*
ct,
proto->mutable_capacity());
3952 for (LinearExpressionProto& exp :
3953 *(
ct->mutable_cumulative()->mutable_demands())) {
3954 changed |= CanonicalizeLinearExpression(*
ct, &exp);
3957 const int64_t capacity_max = context_->
MaxOf(
proto->capacity());
3961 bool domain_changed =
false;
3963 proto->capacity(), Domain(0, capacity_max), &domain_changed)) {
3966 if (domain_changed) {
3975 int num_zero_demand_removed = 0;
3976 int num_zero_size_removed = 0;
3977 int num_incompatible_demands = 0;
3978 for (
int i = 0; i <
proto->intervals_size(); ++i) {
3981 const LinearExpressionProto& demand_expr =
proto->demands(i);
3982 const int64_t demand_max = context_->
MaxOf(demand_expr);
3983 if (demand_max == 0) {
3984 num_zero_demand_removed++;
3990 num_zero_size_removed++;
3994 if (context_->
MinOf(demand_expr) > capacity_max) {
3996 ConstraintProto* interval_ct =
3998 DCHECK_EQ(interval_ct->enforcement_literal_size(), 1);
3999 const int literal = interval_ct->enforcement_literal(0);
4003 num_incompatible_demands++;
4007 "cumulative: performed demand exceeds capacity.");
4011 proto->set_intervals(new_size,
proto->intervals(i));
4012 *
proto->mutable_demands(new_size) =
proto->demands(i);
4016 if (new_size < proto->intervals_size()) {
4018 proto->mutable_intervals()->Truncate(new_size);
4019 proto->mutable_demands()->erase(
4020 proto->mutable_demands()->begin() + new_size,
4021 proto->mutable_demands()->end());
4024 if (num_zero_demand_removed > 0) {
4026 "cumulative: removed intervals with no demands");
4028 if (num_zero_size_removed > 0) {
4030 "cumulative: removed intervals with a size of zero");
4032 if (num_incompatible_demands > 0) {
4034 "cumulative: removed intervals demands greater than the capacity");
4040 for (
int i = 0; i <
proto->demands_size(); ++i) {
4042 const LinearExpressionProto& demand_expr =
proto->demands(i);
4044 bool domain_changed =
false;
4049 if (domain_changed) {
4051 "cumulative: fit demand in [0..capacity_max]");
4063 if (
proto->intervals_size() > 1) {
4064 std::vector<IndexedInterval> indexed_intervals;
4065 for (
int i = 0; i <
proto->intervals().size(); ++i) {
4067 indexed_intervals.push_back({i, IntegerValue(context_->
StartMin(
index)),
4070 std::vector<std::vector<int>> components;
4073 if (components.size() > 1) {
4074 for (
const std::vector<int>& component : components) {
4075 CumulativeConstraintProto* new_cumulative =
4077 for (
const int i : component) {
4079 *new_cumulative->add_demands() =
proto->demands(i);
4081 *new_cumulative->mutable_capacity() =
proto->capacity();
4084 context_->
UpdateRuleStats(
"cumulative: split into disjoint components");
4085 return RemoveConstraint(
ct);
4093 std::map<int64_t, int64_t> time_to_demand_deltas;
4094 const int64_t capacity_min = context_->
MinOf(
proto->capacity());
4095 for (
int i = 0; i <
proto->intervals_size(); ++i) {
4096 const int interval_index =
proto->intervals(i);
4097 const int64_t demand_max = context_->
MaxOf(
proto->demands(i));
4098 time_to_demand_deltas[context_->
StartMin(interval_index)] += demand_max;
4099 time_to_demand_deltas[context_->
EndMax(interval_index)] -= demand_max;
4108 int num_possible_overloads = 0;
4109 int64_t current_load = 0;
4110 absl::flat_hash_map<int64_t, int64_t> num_possible_overloads_before;
4111 for (
const auto& it : time_to_demand_deltas) {
4112 num_possible_overloads_before[it.first] = num_possible_overloads;
4113 current_load += it.second;
4114 if (current_load > capacity_min) {
4115 ++num_possible_overloads;
4121 if (num_possible_overloads == 0) {
4123 "cumulative: max profile is always under the min capacity");
4124 return RemoveConstraint(
ct);
4134 for (
int i = 0; i <
proto->intervals_size(); ++i) {
4152 const int num_diff = num_possible_overloads_before.at(
end_max) -
4153 num_possible_overloads_before.at(
start_min);
4154 if (num_diff == 0)
continue;
4156 proto->set_intervals(new_size,
proto->intervals(i));
4157 *
proto->mutable_demands(new_size) =
proto->demands(i);
4161 if (new_size < proto->intervals_size()) {
4163 proto->mutable_intervals()->Truncate(new_size);
4164 proto->mutable_demands()->erase(
4165 proto->mutable_demands()->begin() + new_size,
4166 proto->mutable_demands()->end());
4168 "cumulative: remove never conflicting intervals.");
4172 if (
proto->intervals().empty()) {
4174 return RemoveConstraint(
ct);
4178 int64_t max_of_performed_demand_mins = 0;
4179 int64_t sum_of_max_demands = 0;
4180 for (
int i = 0; i <
proto->intervals_size(); ++i) {
4181 const ConstraintProto& interval_ct =
4184 const LinearExpressionProto& demand_expr =
proto->demands(i);
4185 sum_of_max_demands += context_->
MaxOf(demand_expr);
4187 if (interval_ct.enforcement_literal().empty()) {
4188 max_of_performed_demand_mins =
std::max(max_of_performed_demand_mins,
4189 context_->
MinOf(demand_expr));
4193 const LinearExpressionProto& capacity_expr =
proto->capacity();
4194 if (max_of_performed_demand_mins > context_->
MinOf(capacity_expr)) {
4197 capacity_expr, Domain(max_of_performed_demand_mins,
4203 if (max_of_performed_demand_mins > context_->
MaxOf(capacity_expr)) {
4204 context_->
UpdateRuleStats(
"cumulative: cannot fit performed demands");
4208 if (sum_of_max_demands <= context_->MinOf(capacity_expr)) {
4209 context_->
UpdateRuleStats(
"cumulative: capacity exceeds sum of demands");
4210 return RemoveConstraint(
ct);
4216 for (
int i = 0; i <
ct->cumulative().demands_size(); ++i) {
4217 const LinearExpressionProto& demand_expr =
ct->cumulative().demands(i);
4218 if (!context_->
IsFixed(demand_expr)) {
4224 if (gcd == 1)
break;
4228 for (
int i = 0; i <
ct->cumulative().demands_size(); ++i) {
4229 const int64_t
demand = context_->
MinOf(
ct->cumulative().demands(i));
4230 *
proto->mutable_demands(i) = ConstantExpressionProto(
demand / gcd);
4233 const int64_t old_capacity = context_->
MinOf(
proto->capacity());
4234 *
proto->mutable_capacity() = ConstantExpressionProto(old_capacity / gcd);
4236 "cumulative: divide demands and capacity by gcd");
4240 const int num_intervals =
proto->intervals_size();
4241 const LinearExpressionProto& capacity_expr =
proto->capacity();
4243 std::vector<LinearExpressionProto> start_exprs(num_intervals);
4245 int num_duration_one = 0;
4246 int num_greater_half_capacity = 0;
4248 bool has_optional_interval =
false;
4249 bool all_starts_are_variables =
true;
4250 for (
int i = 0; i < num_intervals; ++i) {
4254 const ConstraintProto&
ct =
4256 const IntervalConstraintProto&
interval =
ct.interval();
4259 const LinearExpressionProto& demand_expr =
proto->demands(i);
4268 const int64_t demand_min = context_->
MinOf(demand_expr);
4269 const int64_t demand_max = context_->
MaxOf(demand_expr);
4270 if (demand_min > capacity_max / 2) {
4271 num_greater_half_capacity++;
4273 if (demand_min > capacity_max) {
4274 context_->
UpdateRuleStats(
"cumulative: demand_min exceeds capacity max");
4278 CHECK_EQ(
ct.enforcement_literal().size(), 1);
4284 }
else if (demand_max > capacity_max) {
4285 if (
ct.enforcement_literal().empty()) {
4287 "cumulative: demand_max exceeds capacity max.");
4297 "cumulative: demand_max of optional interval exceeds capacity.");
4302 if (num_greater_half_capacity == num_intervals) {
4303 if (num_duration_one == num_intervals && !has_optional_interval &&
4304 all_starts_are_variables) {
4308 for (
const LinearExpressionProto& expr : start_exprs) {
4312 return RemoveConstraint(
ct);
4317 for (
int i = 0; i <
proto->demands_size(); ++i) {
4318 const LinearExpressionProto& demand_expr =
proto->demands(i);
4319 const int64_t demand_max = context_->
MaxOf(demand_expr);
4320 if (demand_max > context_->
MinOf(capacity_expr)) {
4321 ConstraintProto* capacity_gt =
4326 capacity_gt->mutable_linear()->add_domain(0);
4327 capacity_gt->mutable_linear()->add_domain(
4330 capacity_gt->mutable_linear());
4332 capacity_gt->mutable_linear());
4342 return RemoveConstraint(
ct);
4349bool CpModelPresolver::PresolveRoutes(ConstraintProto*
ct) {
4352 RoutesConstraintProto&
proto = *
ct->mutable_routes();
4354 const int old_size =
proto.literals_size();
4356 std::vector<bool> has_incoming_or_outgoing_arcs;
4357 const int num_arcs =
proto.literals_size();
4358 for (
int i = 0; i < num_arcs; ++i) {
4359 const int ref =
proto.literals(i);
4366 proto.set_literals(new_size, ref);
4370 if (
tail >= has_incoming_or_outgoing_arcs.size()) {
4371 has_incoming_or_outgoing_arcs.resize(
tail + 1,
false);
4373 if (
head >= has_incoming_or_outgoing_arcs.size()) {
4374 has_incoming_or_outgoing_arcs.resize(
head + 1,
false);
4376 has_incoming_or_outgoing_arcs[
tail] =
true;
4377 has_incoming_or_outgoing_arcs[
head] =
true;
4380 if (old_size > 0 && new_size == 0) {
4385 "routes: graph with nodes and no arcs");
4388 if (new_size < num_arcs) {
4389 proto.mutable_literals()->Truncate(new_size);
4390 proto.mutable_tails()->Truncate(new_size);
4391 proto.mutable_heads()->Truncate(new_size);
4397 for (
int n = 0; n < has_incoming_or_outgoing_arcs.size(); ++n) {
4398 if (!has_incoming_or_outgoing_arcs[n]) {
4400 "routes: node ", n,
" misses incoming or outgoing arcs"));
4407bool CpModelPresolver::PresolveCircuit(ConstraintProto*
ct) {
4410 CircuitConstraintProto&
proto = *
ct->mutable_circuit();
4414 ct->mutable_circuit()->mutable_heads());
4418 std::vector<std::vector<int>> incoming_arcs;
4419 std::vector<std::vector<int>> outgoing_arcs;
4421 const int num_arcs =
proto.literals_size();
4422 for (
int i = 0; i < num_arcs; ++i) {
4423 const int ref =
proto.literals(i);
4431 incoming_arcs[
head].push_back(ref);
4432 outgoing_arcs[
tail].push_back(ref);
4436 for (
int i = 0; i < num_nodes; ++i) {
4437 if (incoming_arcs[i].empty() || outgoing_arcs[i].empty()) {
4438 return MarkConstraintAsFalse(
ct);
4447 bool loop_again =
true;
4448 int num_fixed_at_true = 0;
4449 while (loop_again) {
4451 for (
const auto* node_to_refs : {&incoming_arcs, &outgoing_arcs}) {
4452 for (
const std::vector<int>& refs : *node_to_refs) {
4453 if (refs.size() == 1) {
4455 ++num_fixed_at_true;
4464 for (
const int ref : refs) {
4474 if (num_true == 1) {
4475 for (
const int ref : refs) {
4476 if (ref != true_ref) {
4477 if (!context_->
IsFixed(ref)) {
4488 if (num_fixed_at_true > 0) {
4495 int circuit_start = -1;
4496 std::vector<int>
next(num_nodes, -1);
4497 std::vector<int> new_in_degree(num_nodes, 0);
4498 std::vector<int> new_out_degree(num_nodes, 0);
4499 for (
int i = 0; i < num_arcs; ++i) {
4500 const int ref =
proto.literals(i);
4508 circuit_start =
proto.tails(i);
4512 ++new_out_degree[
proto.tails(i)];
4513 ++new_in_degree[
proto.heads(i)];
4516 proto.set_literals(new_size,
proto.literals(i));
4526 for (
int i = 0; i < num_nodes; ++i) {
4527 if (new_in_degree[i] == 0 || new_out_degree[i] == 0) {
4533 if (circuit_start != -1) {
4534 std::vector<bool> visited(num_nodes,
false);
4535 int current = circuit_start;
4536 while (current != -1 && !visited[current]) {
4537 visited[current] =
true;
4538 current =
next[current];
4540 if (current == circuit_start) {
4543 std::vector<bool> has_self_arc(num_nodes,
false);
4544 for (
int i = 0; i < num_arcs; ++i) {
4545 if (visited[
proto.tails(i)])
continue;
4547 has_self_arc[
proto.tails(i)] =
true;
4553 for (
int n = 0; n < num_nodes; ++n) {
4554 if (!visited[n] && !has_self_arc[n]) {
4556 return MarkConstraintAsFalse(
ct);
4560 return RemoveConstraint(
ct);
4564 if (num_true == new_size) {
4566 return RemoveConstraint(
ct);
4572 for (
int i = 0; i < num_nodes; ++i) {
4573 for (
const std::vector<int>* arc_literals :
4574 {&incoming_arcs[i], &outgoing_arcs[i]}) {
4575 std::vector<int> literals;
4576 for (
const int ref : *arc_literals) {
4582 literals.push_back(ref);
4584 if (literals.size() == 2 && literals[0] !=
NegatedRef(literals[1])) {
4593 if (new_size < num_arcs) {
4594 proto.mutable_tails()->Truncate(new_size);
4595 proto.mutable_heads()->Truncate(new_size);
4596 proto.mutable_literals()->Truncate(new_size);
4603bool CpModelPresolver::PresolveAutomaton(ConstraintProto*
ct) {
4606 AutomatonConstraintProto&
proto = *
ct->mutable_automaton();
4607 if (
proto.vars_size() == 0 ||
proto.transition_label_size() == 0) {
4611 bool all_have_same_affine_relation =
true;
4612 std::vector<AffineRelation::Relation> affine_relations;
4613 for (
int v = 0; v <
proto.vars_size(); ++v) {
4614 const int var =
ct->automaton().vars(v);
4616 affine_relations.push_back(r);
4617 if (r.representative ==
var) {
4618 all_have_same_affine_relation =
false;
4621 if (v > 0 && (r.coeff != affine_relations[v - 1].coeff ||
4622 r.offset != affine_relations[v - 1].offset)) {
4623 all_have_same_affine_relation =
false;
4628 if (all_have_same_affine_relation) {
4629 for (
int v = 0; v <
proto.vars_size(); ++v) {
4632 const AffineRelation::Relation rep = affine_relations.front();
4634 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
4635 const int64_t label =
proto.transition_label(t);
4636 int64_t inverse_label = (label - rep.offset) / rep.coeff;
4637 if (inverse_label * rep.coeff + rep.offset == label) {
4638 if (new_size != t) {
4639 proto.set_transition_tail(new_size,
proto.transition_tail(t));
4640 proto.set_transition_head(new_size,
proto.transition_head(t));
4642 proto.set_transition_label(new_size, inverse_label);
4646 if (new_size <
proto.transition_tail_size()) {
4647 proto.mutable_transition_tail()->Truncate(new_size);
4648 proto.mutable_transition_label()->Truncate(new_size);
4649 proto.mutable_transition_head()->Truncate(new_size);
4657 for (
int v = 1; v <
proto.vars_size(); ++v) {
4662 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
4663 const int64_t label =
proto.transition_label(t);
4664 if (hull.Contains(label)) {
4665 if (new_size != t) {
4666 proto.set_transition_tail(new_size,
proto.transition_tail(t));
4667 proto.set_transition_label(new_size, label);
4668 proto.set_transition_head(new_size,
proto.transition_head(t));
4673 if (new_size <
proto.transition_tail_size()) {
4674 proto.mutable_transition_tail()->Truncate(new_size);
4675 proto.mutable_transition_label()->Truncate(new_size);
4676 proto.mutable_transition_head()->Truncate(new_size);
4681 const int n =
proto.vars_size();
4682 const std::vector<int> vars = {
proto.vars().begin(),
proto.vars().end()};
4685 std::vector<std::set<int64_t>> reachable_states(n + 1);
4686 reachable_states[0].insert(
proto.starting_state());
4687 reachable_states[n] = {
proto.final_states().begin(),
4688 proto.final_states().end()};
4692 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
4693 const int64_t
tail =
proto.transition_tail(t);
4694 const int64_t label =
proto.transition_label(t);
4695 const int64_t
head =
proto.transition_head(t);
4698 reachable_states[
time + 1].insert(
head);
4702 std::vector<std::set<int64_t>> reached_values(n);
4706 std::set<int64_t> new_set;
4707 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
4708 const int64_t
tail =
proto.transition_tail(t);
4709 const int64_t label =
proto.transition_label(t);
4710 const int64_t
head =
proto.transition_head(t);
4715 new_set.insert(
tail);
4716 reached_values[
time].insert(label);
4718 reachable_states[
time].swap(new_set);
4721 bool removed_values =
false;
4726 {reached_values[time].begin(), reached_values[time].end()}),
4731 if (removed_values) {
4737bool CpModelPresolver::PresolveReservoir(ConstraintProto*
ct) {
4741 ReservoirConstraintProto&
proto = *
ct->mutable_reservoir();
4742 bool changed =
false;
4743 for (LinearExpressionProto& exp : *(
proto.mutable_time_exprs())) {
4744 changed |= CanonicalizeLinearExpression(*
ct, &exp);
4747 if (
proto.active_literals().empty()) {
4749 for (
int i = 0; i <
proto.time_exprs_size(); ++i) {
4750 proto.add_active_literals(true_literal);
4755 const auto& demand_is_null = [&](
int i) {
4756 return proto.level_changes(i) == 0 ||
4762 for (
int i = 0; i <
proto.level_changes_size(); ++i) {
4763 if (demand_is_null(i)) num_zeros++;
4766 if (num_zeros > 0) {
4769 for (
int i = 0; i <
proto.level_changes_size(); ++i) {
4770 if (demand_is_null(i))
continue;
4771 proto.set_level_changes(new_size,
proto.level_changes(i));
4772 *
proto.mutable_time_exprs(new_size) =
proto.time_exprs(i);
4773 proto.set_active_literals(new_size,
proto.active_literals(i));
4777 proto.mutable_level_changes()->Truncate(new_size);
4778 proto.mutable_time_exprs()->erase(
4779 proto.mutable_time_exprs()->begin() + new_size,
4780 proto.mutable_time_exprs()->end());
4781 proto.mutable_active_literals()->Truncate(new_size);
4784 "reservoir: remove zero level_changes or inactive events.");
4787 const int num_events =
proto.level_changes_size();
4789 proto.level_changes().empty() ? 0 : std::abs(
proto.level_changes(0));
4790 int num_positives = 0;
4791 int num_negatives = 0;
4792 int64_t max_sum_of_positive_level_changes = 0;
4793 int64_t min_sum_of_negative_level_changes = 0;
4794 for (
int i = 0; i < num_events; ++i) {
4799 max_sum_of_positive_level_changes +=
demand;
4803 min_sum_of_negative_level_changes +=
demand;
4807 if (min_sum_of_negative_level_changes >=
proto.min_level() &&
4808 max_sum_of_positive_level_changes <=
proto.max_level()) {
4810 return RemoveConstraint(
ct);
4813 if (min_sum_of_negative_level_changes >
proto.max_level() ||
4814 max_sum_of_positive_level_changes <
proto.min_level()) {
4819 if (min_sum_of_negative_level_changes >
proto.min_level()) {
4820 proto.set_min_level(min_sum_of_negative_level_changes);
4822 "reservoir: increase min_level to reachable value");
4825 if (max_sum_of_positive_level_changes <
proto.max_level()) {
4826 proto.set_max_level(max_sum_of_positive_level_changes);
4827 context_->
UpdateRuleStats(
"reservoir: reduce max_level to reachable value");
4830 if (
proto.min_level() <= 0 &&
proto.max_level() >= 0 &&
4831 (num_positives == 0 || num_negatives == 0)) {
4836 int64_t fixed_contrib = 0;
4837 for (
int i = 0; i <
proto.level_changes_size(); ++i) {
4841 const int active =
proto.active_literals(i);
4843 sum->add_vars(active);
4847 sum->add_coeffs(-
demand);
4851 sum->add_domain(
proto.min_level() - fixed_contrib);
4852 sum->add_domain(
proto.max_level() - fixed_contrib);
4854 return RemoveConstraint(
ct);
4858 for (
int i = 0; i <
proto.level_changes_size(); ++i) {
4859 proto.set_level_changes(i,
proto.level_changes(i) / gcd);
4865 const Domain reduced_domain = Domain({
proto.min_level(),
proto.max_level()})
4866 .InverseMultiplicationBy(gcd);
4867 proto.set_min_level(reduced_domain.Min());
4868 proto.set_max_level(reduced_domain.Max());
4870 "reservoir: simplify level_changes and levels by gcd.");
4873 if (num_positives == 1 && num_negatives > 0) {
4875 "TODO reservoir: one producer, multiple consumers.");
4878 absl::flat_hash_set<std::tuple<int, int64_t, int64_t, int>> time_active_set;
4879 for (
int i = 0; i <
proto.level_changes_size(); ++i) {
4880 const LinearExpressionProto&
time =
proto.time_exprs(i);
4884 const std::tuple<int, int64_t, int64_t, int> key = std::make_tuple(
4887 proto.active_literals(i));
4888 if (time_active_set.contains(key)) {
4889 context_->
UpdateRuleStats(
"TODO reservoir: merge synchronized events.");
4892 time_active_set.insert(key);
4902void CpModelPresolver::ExtractBoolAnd() {
4903 absl::flat_hash_map<int, int> ref_to_bool_and;
4905 std::vector<int> to_remove;
4906 for (
int c = 0; c < num_constraints; ++c) {
4910 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr &&
4911 ct.bool_or().literals().size() == 2) {
4915 to_remove.push_back(c);
4919 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne &&
4920 ct.at_most_one().literals().size() == 2) {
4921 AddImplication(
ct.at_most_one().literals(0),
4924 to_remove.push_back(c);
4930 for (
const int c : to_remove) {
4939void CpModelPresolver::Probe() {
4947 auto* implication_graph =
model.GetOrCreate<BinaryImplicationGraph>();
4948 auto* sat_solver =
model.GetOrCreate<SatSolver>();
4949 auto* mapping =
model.GetOrCreate<CpModelMapping>();
4950 auto* prober =
model.GetOrCreate<Prober>();
4951 prober->ProbeBooleanVariables(1.0);
4953 model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
4954 if (sat_solver->IsModelUnsat() || !implication_graph->DetectEquivalences()) {
4959 CHECK_EQ(sat_solver->CurrentDecisionLevel(), 0);
4960 for (
int i = 0; i < sat_solver->LiteralTrail().
Index(); ++i) {
4961 const Literal l = sat_solver->LiteralTrail()[i];
4962 const int var = mapping->GetProtoVariableFromBooleanVariable(l.Variable());
4970 auto* integer_trail =
model.GetOrCreate<IntegerTrail>();
4971 for (
int var = 0;
var < num_variables; ++
var) {
4974 if (!mapping->IsBoolean(
var)) {
4977 integer_trail->InitialVariableDomain(mapping->Integer(
var)))) {
4984 const Literal l = mapping->Literal(
var);
4985 const Literal r = implication_graph->RepresentativeOf(l);
4988 mapping->GetProtoVariableFromBooleanVariable(r.Variable());
4998void CpModelPresolver::PresolvePureSatPart() {
5004 SatPostsolver sat_postsolver(num_variables);
5005 SatPresolver sat_presolver(&sat_postsolver, logger_);
5006 sat_presolver.SetNumVariables(num_variables);
5007 sat_presolver.SetTimeLimit(context_->
time_limit());
5009 SatParameters params = context_->
params();
5016 if (params.debug_postsolve_with_full_solver()) {
5023 params.set_presolve_use_bva(
false);
5024 sat_presolver.SetParameters(params);
5027 absl::flat_hash_set<int> used_variables;
5028 auto convert = [&used_variables](
int ref) {
5030 if (
RefIsPositive(ref))
return Literal(BooleanVariable(ref),
true);
5031 return Literal(BooleanVariable(
NegatedRef(ref)),
false);
5041 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr ||
5042 ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolAnd) {
5061 std::vector<Literal> clause;
5062 int num_removed_constraints = 0;
5066 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr) {
5067 ++num_removed_constraints;
5069 for (
const int ref :
ct.bool_or().literals()) {
5070 clause.push_back(convert(ref));
5072 for (
const int ref :
ct.enforcement_literal()) {
5073 clause.push_back(convert(ref).Negated());
5075 sat_presolver.AddClause(clause);
5082 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolAnd) {
5083 ++num_removed_constraints;
5084 std::vector<Literal> clause;
5085 for (
const int ref :
ct.enforcement_literal()) {
5086 clause.push_back(convert(ref).Negated());
5089 for (
const int ref :
ct.bool_and().literals()) {
5090 clause.back() = convert(ref);
5091 sat_presolver.AddClause(clause);
5101 if (num_removed_constraints == 0)
return;
5111 std::vector<bool> can_be_removed(num_variables,
false);
5112 for (
int i = 0; i < num_variables; ++i) {
5114 can_be_removed[i] =
true;
5120 if (used_variables.contains(i) && context_->
IsFixed(i)) {
5122 sat_presolver.AddClause({convert(i)});
5124 sat_presolver.AddClause({convert(
NegatedRef(i))});
5132 const int num_passes = params.presolve_use_bva() ? 4 : 1;
5133 for (
int i = 0; i < num_passes; ++i) {
5134 const int old_num_clause = sat_postsolver.NumClauses();
5135 if (!sat_presolver.Presolve(can_be_removed)) {
5136 VLOG(1) <<
"UNSAT during SAT presolve.";
5139 if (old_num_clause == sat_postsolver.NumClauses())
break;
5143 const int new_num_variables = sat_presolver.NumVariables();
5145 VLOG(1) <<
"New variables added by the SAT presolver.";
5147 i < new_num_variables; ++i) {
5148 IntegerVariableProto* var_proto =
5151 var_proto->add_domain(1);
5157 ExtractClauses(
true, sat_presolver, context_->
working_model);
5165 ExtractClauses(
false, sat_postsolver,
5173void CpModelPresolver::ExpandObjective() {
5192 int unique_expanded_constraint = -1;
5193 const bool objective_was_a_single_variable =
5200 absl::flat_hash_set<int> relevant_constraints;
5201 std::vector<int> var_to_num_relevant_constraints(num_variables, 0);
5202 for (
int ct_index = 0; ct_index < num_constraints; ++ct_index) {
5205 if (!
ct.enforcement_literal().empty() ||
5206 ct.constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
5207 ct.linear().domain().size() != 2 ||
5208 ct.linear().domain(0) !=
ct.linear().domain(1)) {
5212 relevant_constraints.insert(ct_index);
5213 const int num_terms =
ct.linear().vars_size();
5214 for (
int i = 0; i < num_terms; ++i) {
5215 var_to_num_relevant_constraints[
PositiveRef(
ct.linear().vars(i))]++;
5219 std::set<int> var_to_process;
5221 const int var = entry.first;
5223 if (var_to_num_relevant_constraints[
var] != 0) {
5224 var_to_process.insert(
var);
5229 int num_expansions = 0;
5230 int last_expanded_objective_var;
5231 absl::flat_hash_set<int> processed_vars;
5232 std::vector<int> new_vars_in_objective;
5233 while (!relevant_constraints.empty()) {
5235 int objective_var = -1;
5236 while (!var_to_process.empty()) {
5237 const int var = *var_to_process.begin();
5238 CHECK(!processed_vars.contains(
var));
5239 if (var_to_num_relevant_constraints[
var] == 0) {
5240 processed_vars.insert(
var);
5241 var_to_process.erase(
var);
5246 var_to_process.erase(
var);
5249 objective_var =
var;
5253 if (objective_var == -1)
break;
5255 processed_vars.insert(objective_var);
5256 var_to_process.erase(objective_var);
5258 int expanded_linear_index = -1;
5259 int64_t objective_coeff_in_expanded_constraint;
5260 int64_t size_of_expanded_constraint = 0;
5261 const auto& non_deterministic_list =
5263 std::vector<int> constraints_with_objective(non_deterministic_list.begin(),
5264 non_deterministic_list.end());
5265 std::sort(constraints_with_objective.begin(),
5266 constraints_with_objective.end());
5267 for (
const int ct_index : constraints_with_objective) {
5268 if (relevant_constraints.count(ct_index) == 0)
continue;
5269 const ConstraintProto&
ct =
5274 relevant_constraints.erase(ct_index);
5275 const int num_terms =
ct.linear().vars_size();
5276 for (
int i = 0; i < num_terms; ++i) {
5277 var_to_num_relevant_constraints[
PositiveRef(
ct.linear().vars(i))]--;
5289 bool is_present =
false;
5290 int64_t objective_coeff;
5291 for (
int i = 0; i < num_terms; ++i) {
5292 const int ref =
ct.linear().vars(i);
5293 const int64_t coeff =
ct.linear().coeffs(i);
5295 CHECK(!is_present) <<
"Duplicate variables not supported.";
5297 objective_coeff = (ref == objective_var) ? coeff : -coeff;
5310 if (std::abs(objective_coeff) == 1 &&
5311 num_terms > size_of_expanded_constraint) {
5312 expanded_linear_index = ct_index;
5313 size_of_expanded_constraint = num_terms;
5314 objective_coeff_in_expanded_constraint = objective_coeff;
5318 if (expanded_linear_index != -1) {
5321 CHECK_EQ(std::abs(objective_coeff_in_expanded_constraint), 1);
5322 const ConstraintProto&
ct =
5325 objective_var, objective_coeff_in_expanded_constraint,
ct,
5326 &new_vars_in_objective)) {
5331 context_->
UpdateRuleStats(
"objective: expanded objective constraint.");
5334 for (
const int var : new_vars_in_objective) {
5335 if (!processed_vars.contains(
var)) var_to_process.insert(
var);
5348 for (
int i = 0; i < size_of_expanded_constraint; ++i) {
5349 const int ref =
ct.linear().vars(i);
5354 -
ct.linear().coeffs(i)))
5355 .RelaxIfTooComplex();
5357 implied_domain = implied_domain.InverseMultiplicationBy(
5358 objective_coeff_in_expanded_constraint);
5362 if (implied_domain.IsIncludedIn(context_->
DomainOf(objective_var))) {
5364 context_->
UpdateRuleStats(
"objective: removed objective constraint.");
5370 unique_expanded_constraint = expanded_linear_index;
5375 last_expanded_objective_var = objective_var;
5381 if (num_expansions == 1 && objective_was_a_single_variable &&
5382 unique_expanded_constraint != -1) {
5384 "objective: removed unique objective constraint.");
5386 unique_expanded_constraint);
5388 mutable_ct->
Clear();
5401void CpModelPresolver::MergeNoOverlapConstraints() {
5405 int old_num_no_overlaps = 0;
5406 int old_num_intervals = 0;
5409 std::vector<int> disjunctive_index;
5410 std::vector<std::vector<Literal>> cliques;
5411 for (
int c = 0; c < num_constraints; ++c) {
5413 if (
ct.constraint_case() != ConstraintProto::ConstraintCase::kNoOverlap) {
5416 std::vector<Literal> clique;
5417 for (
const int i :
ct.no_overlap().intervals()) {
5418 clique.push_back(Literal(BooleanVariable(i),
true));
5420 cliques.push_back(clique);
5421 disjunctive_index.push_back(c);
5423 old_num_no_overlaps++;
5424 old_num_intervals += clique.size();
5426 if (old_num_no_overlaps == 0)
return;
5430 local_model.GetOrCreate<Trail>()->Resize(num_constraints);
5431 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
5432 graph->Resize(num_constraints);
5433 for (
const std::vector<Literal>& clique : cliques) {
5436 CHECK(graph->AddAtMostOne(clique));
5438 CHECK(graph->DetectEquivalences());
5439 graph->TransformIntoMaxCliques(
5443 int new_num_no_overlaps = 0;
5444 int new_num_intervals = 0;
5445 for (
int i = 0; i < cliques.size(); ++i) {
5446 const int ct_index = disjunctive_index[i];
5447 ConstraintProto*
ct =
5450 if (cliques[i].empty())
continue;
5451 for (
const Literal l : cliques[i]) {
5452 CHECK(l.IsPositive());
5453 ct->mutable_no_overlap()->add_intervals(l.Variable().value());
5455 new_num_no_overlaps++;
5456 new_num_intervals += cliques[i].size();
5458 if (old_num_intervals != new_num_intervals ||
5459 old_num_no_overlaps != new_num_no_overlaps) {
5460 VLOG(1) << absl::StrCat(
"Merged ", old_num_no_overlaps,
" no-overlaps (",
5461 old_num_intervals,
" intervals) into ",
5462 new_num_no_overlaps,
" no-overlaps (",
5463 new_num_intervals,
" intervals).");
5472void CpModelPresolver::TransformIntoMaxCliques() {
5475 auto convert = [](
int ref) {
5476 if (
RefIsPositive(ref))
return Literal(BooleanVariable(ref),
true);
5477 return Literal(BooleanVariable(
NegatedRef(ref)),
false);
5482 std::vector<std::vector<Literal>> cliques;
5484 for (
int c = 0; c < num_constraints; ++c) {
5486 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne) {
5487 std::vector<Literal> clique;
5488 for (
const int ref :
ct->at_most_one().literals()) {
5489 clique.push_back(convert(ref));
5491 cliques.push_back(clique);
5492 if (RemoveConstraint(
ct)) {
5495 }
else if (
ct->constraint_case() ==
5496 ConstraintProto::ConstraintCase::kBoolAnd) {
5497 if (
ct->enforcement_literal().size() != 1)
continue;
5498 const Literal enforcement = convert(
ct->enforcement_literal(0));
5499 for (
const int ref :
ct->bool_and().literals()) {
5500 if (ref ==
ct->enforcement_literal(0))
continue;
5501 cliques.push_back({enforcement, convert(ref).Negated()});
5503 if (RemoveConstraint(
ct)) {
5509 int64_t num_literals_before = 0;
5510 const int num_old_cliques = cliques.size();
5515 local_model.GetOrCreate<Trail>()->Resize(num_variables);
5516 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
5517 graph->Resize(num_variables);
5518 for (
const std::vector<Literal>& clique : cliques) {
5519 num_literals_before += clique.size();
5520 if (!graph->AddAtMostOne(clique)) {
5524 if (!graph->DetectEquivalences()) {
5527 graph->TransformIntoMaxCliques(
5533 for (
int var = 0;
var < num_variables; ++
var) {
5534 const Literal l = Literal(BooleanVariable(
var),
true);
5535 if (graph->RepresentativeOf(l) != l) {
5536 const Literal r = graph->RepresentativeOf(l);
5538 var, r.IsPositive() ? r.Variable().value()
5543 int num_new_cliques = 0;
5544 int64_t num_literals_after = 0;
5545 for (
const std::vector<Literal>& clique : cliques) {
5546 if (clique.empty())
continue;
5548 num_literals_after += clique.size();
5550 for (
const Literal
literal : clique) {
5552 ct->mutable_at_most_one()->add_literals(
literal.Variable().value());
5554 ct->mutable_at_most_one()->add_literals(
5560 PresolveAtMostOne(
ct);
5563 if (num_new_cliques != num_old_cliques) {
5564 context_->
UpdateRuleStats(
"at_most_one: transformed into max clique.");
5567 if (num_old_cliques != num_new_cliques ||
5568 num_literals_before != num_literals_after) {
5569 SOLVER_LOG(logger_,
"[MaxClique] Merged ", num_old_cliques,
"(",
5570 num_literals_before,
" literals) into ", num_new_cliques,
"(",
5571 num_literals_after,
" literals) at_most_ones.");
5577bool IsAffineIntAbs(ConstraintProto*
ct) {
5579 ct->lin_max().exprs_size() != 2 ||
5580 ct->lin_max().target().vars_size() > 1 ||
5581 ct->lin_max().exprs(0).vars_size() != 1 ||
5582 ct->lin_max().exprs(1).vars_size() != 1) {
5586 const LinearArgumentProto& lin_max =
ct->lin_max();
5587 if (lin_max.exprs(0).offset() != -lin_max.exprs(1).offset())
return false;
5593 const int64_t left_coeff =
RefIsPositive(lin_max.exprs(0).vars(0))
5594 ? lin_max.exprs(0).coeffs(0)
5595 : -lin_max.exprs(0).coeffs(0);
5596 const int64_t right_coeff =
RefIsPositive(lin_max.exprs(1).vars(0))
5597 ? lin_max.exprs(1).coeffs(0)
5598 : -lin_max.exprs(1).coeffs(0);
5599 return left_coeff == -right_coeff;
5609 if (ExploitEquivalenceRelations(c,
ct)) {
5614 if (PresolveEnforcementLiteral(
ct)) {
5619 switch (
ct->constraint_case()) {
5620 case ConstraintProto::ConstraintCase::kBoolOr:
5621 return PresolveBoolOr(
ct);
5622 case ConstraintProto::ConstraintCase::kBoolAnd:
5623 return PresolveBoolAnd(
ct);
5624 case ConstraintProto::ConstraintCase::kAtMostOne:
5625 return PresolveAtMostOne(
ct);
5626 case ConstraintProto::ConstraintCase::kExactlyOne:
5627 return PresolveExactlyOne(
ct);
5628 case ConstraintProto::ConstraintCase::kBoolXor:
5629 return PresolveBoolXor(
ct);
5630 case ConstraintProto::ConstraintCase::kLinMax:
5631 if (CanonicalizeLinMax(
ct)) {
5634 if (IsAffineIntAbs(
ct)) {
5635 return PresolveIntAbs(
ct);
5637 return PresolveLinMax(
ct);
5639 case ConstraintProto::ConstraintCase::kIntProd:
5640 return PresolveIntProd(
ct);
5641 case ConstraintProto::ConstraintCase::kIntDiv:
5642 return PresolveIntDiv(
ct);
5643 case ConstraintProto::ConstraintCase::kIntMod:
5644 return PresolveIntMod(
ct);
5645 case ConstraintProto::ConstraintCase::kLinear: {
5654 for (
const int ref :
ct->linear().vars()) {
5660 if (CanonicalizeLinear(
ct)) {
5663 if (PropagateDomainsInLinear(c,
ct)) {
5666 if (PresolveSmallLinear(
ct)) {
5669 if (PresolveLinearEqualityWithModulo(
ct)) {
5673 if (RemoveSingletonInLinear(
ct)) {
5678 if (PresolveSmallLinear(
ct)) {
5682 if (PresolveSmallLinear(
ct)) {
5685 if (PresolveLinearOnBooleans(
ct)) {
5689 const int old_num_enforcement_literals =
ct->enforcement_literal_size();
5690 ExtractEnforcementLiteralFromLinearConstraint(c,
ct);
5691 if (
ct->constraint_case() ==
5692 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
5696 if (
ct->enforcement_literal_size() > old_num_enforcement_literals &&
5697 PresolveSmallLinear(
ct)) {
5706 DetectAndProcessOneSidedLinearConstraint(c,
ct)) {
5711 case ConstraintProto::ConstraintCase::kInterval:
5712 return PresolveInterval(c,
ct);
5713 case ConstraintProto::ConstraintCase::kInverse:
5714 return PresolveInverse(
ct);
5715 case ConstraintProto::ConstraintCase::kElement:
5716 return PresolveElement(
ct);
5717 case ConstraintProto::ConstraintCase::kTable:
5718 return PresolveTable(
ct);
5719 case ConstraintProto::ConstraintCase::kAllDiff:
5720 return PresolveAllDiff(
ct);
5721 case ConstraintProto::ConstraintCase::kNoOverlap:
5722 return PresolveNoOverlap(
ct);
5723 case ConstraintProto::ConstraintCase::kNoOverlap2D:
5724 return PresolveNoOverlap2D(c,
ct);
5725 case ConstraintProto::ConstraintCase::kCumulative:
5726 return PresolveCumulative(
ct);
5727 case ConstraintProto::ConstraintCase::kCircuit:
5728 return PresolveCircuit(
ct);
5729 case ConstraintProto::ConstraintCase::kRoutes:
5730 return PresolveRoutes(
ct);
5731 case ConstraintProto::ConstraintCase::kAutomaton:
5732 return PresolveAutomaton(
ct);
5733 case ConstraintProto::ConstraintCase::kReservoir:
5734 return PresolveReservoir(
ct);
5744bool CpModelPresolver::ProcessSetPPCSubset(
5745 int c1,
int c2,
const std::vector<int>& c2_minus_c1,
5746 const std::vector<int>& original_constraint_index,
5747 std::vector<bool>* marked_for_removal) {
5750 CHECK(!(*marked_for_removal)[c1]);
5751 CHECK(!(*marked_for_removal)[c2]);
5754 original_constraint_index[c1]);
5756 original_constraint_index[c2]);
5766 for (
const int literal : c2_minus_c1) {
5773 ConstraintProto copy = *ct2;
5779 (*marked_for_removal)[c1] =
true;
5790 (*marked_for_removal)[c2] =
true;
5800 (*marked_for_removal)[c1] =
true;
5817 int num_matches = 0;
5818 for (
int i = 0; i < ct2->
linear().vars().size(); ++i) {
5820 if (literals.contains(
var)) {
5828 if (num_matches != literals.size())
return true;
5834 for (
int i = 0; i < ct2->
linear().vars().size(); ++i) {
5837 if (literals.contains(
var)) {
5839 if (coeff == min_coeff)
continue;
5866bool CpModelPresolver::ProcessSetPPC() {
5871 std::vector<uint64_t> signatures;
5875 std::vector<std::vector<int>> constraint_literals;
5879 std::vector<std::vector<int>> literals_to_constraints;
5882 std::vector<bool> removed;
5886 std::vector<int> original_constraint_index;
5890 int num_setppc_constraints = 0;
5891 std::vector<int> temp_literals;
5893 for (
int c = 0; c < num_constraints; ++c) {
5905 constraint_literals.push_back(GetLiteralsFromSetPPCConstraint(*
ct));
5916 const int size =
ct->linear().vars().size();
5917 if (size <= 2)
continue;
5922 temp_literals.clear();
5923 for (
int i = 0; i < size; ++i) {
5924 const int var =
ct->linear().vars(i);
5925 const int64_t coeff =
ct->linear().coeffs(i);
5928 if (coeff < 0)
continue;
5929 temp_literals.push_back(
var);
5931 if (temp_literals.size() <= 2)
continue;
5932 constraint_literals.push_back(temp_literals);
5937 uint64_t signature = 0;
5938 for (
const int literal : constraint_literals.back()) {
5940 signature |= (int64_t{1} << (positive_literal % 64));
5942 if (positive_literal >= literals_to_constraints.size()) {
5943 literals_to_constraints.resize(positive_literal + 1);
5945 literals_to_constraints[positive_literal].push_back(
5946 num_setppc_constraints);
5948 signatures.push_back(signature);
5949 removed.push_back(
false);
5950 original_constraint_index.push_back(c);
5951 num_setppc_constraints++;
5953 VLOG(1) <<
"#setppc constraints: " << num_setppc_constraints;
5956 absl::flat_hash_set<std::pair<int, int>> compared_constraints;
5957 for (
const std::vector<int>& literal_to_constraints :
5958 literals_to_constraints) {
5959 for (
int index1 = 0; index1 < literal_to_constraints.size(); ++index1) {
5962 const int c1 = literal_to_constraints[index1];
5963 if (removed[c1])
continue;
5964 const std::vector<int>& c1_literals = constraint_literals[c1];
5966 for (
int index2 = index1 + 1; index2 < literal_to_constraints.size();
5968 const int c2 = literal_to_constraints[index2];
5969 if (removed[c2])
continue;
5970 if (removed[c1])
break;
5973 if (c1 == c2)
continue;
5976 if (compared_constraints.contains(std::pair<int, int>(c1, c2))) {
5979 compared_constraints.insert({c1, c2});
5983 if (compared_constraints.size() >= 50000)
return true;
5985 const bool smaller = (signatures[c1] & ~signatures[c2]) == 0;
5986 const bool larger = (signatures[c2] & ~signatures[c1]) == 0;
5987 if (!(smaller || larger))
continue;
5990 const std::vector<int>& c2_literals = constraint_literals[c2];
5994 std::vector<int> c1_minus_c2;
5996 std::vector<int> c2_minus_c1;
5999 if (c1_minus_c2.empty()) {
6000 if (!ProcessSetPPCSubset(c1, c2, c2_minus_c1,
6001 original_constraint_index, &removed)) {
6004 }
else if (c2_minus_c1.empty()) {
6005 if (!ProcessSetPPCSubset(c2, c1, c1_minus_c2,
6006 original_constraint_index, &removed)) {
6025bool CpModelPresolver::ProcessEncodingFromLinear(
6026 const int linear_encoding_ct_index,
6027 const ConstraintProto& at_most_or_exactly_one, int64_t* num_unique_terms,
6028 int64_t* num_multiple_terms) {
6030 bool in_exactly_one =
false;
6031 absl::flat_hash_map<int, int> var_to_ref;
6033 for (
const int ref : at_most_or_exactly_one.at_most_one().literals()) {
6038 in_exactly_one =
true;
6039 for (
const int ref : at_most_or_exactly_one.exactly_one().literals()) {
6046 const ConstraintProto& linear_encoding =
6050 std::vector<std::pair<int, int64_t>> ref_to_coeffs;
6051 const int num_terms = linear_encoding.linear().vars().size();
6052 for (
int i = 0; i < num_terms; ++i) {
6053 const int ref = linear_encoding.linear().vars(i);
6054 const int64_t coeff = linear_encoding.linear().coeffs(i);
6055 const auto it = var_to_ref.find(
PositiveRef(ref));
6057 if (it == var_to_ref.end()) {
6060 target_ref = coeff == 1 ? ref :
NegatedRef(ref);
6066 if (it->second == ref) {
6068 ref_to_coeffs.push_back({ref, coeff});
6072 ref_to_coeffs.push_back({
NegatedRef(ref), -coeff});
6080 context_->
UpdateRuleStats(
"encoding: candidate linear is all Boolean now.");
6085 std::vector<int64_t> all_values;
6086 std::map<int64_t, std::vector<int>> value_to_refs;
6087 for (
const auto& [ref, coeff] : ref_to_coeffs) {
6088 const int64_t
value = rhs - coeff;
6089 all_values.push_back(
value);
6090 value_to_refs[
value].push_back(ref);
6094 for (
const auto& [
var, ref] : var_to_ref) {
6095 all_values.push_back(rhs);
6096 value_to_refs[rhs].push_back(ref);
6098 if (!in_exactly_one) {
6101 all_values.push_back(rhs);
6106 bool domain_reduced =
false;
6110 if (domain_reduced) {
6116 context_->
UpdateRuleStats(
"encoding: candidate linear is all Boolean now.");
6121 absl::flat_hash_set<int64_t> value_set;
6122 for (
const int64_t v : context_->
DomainOf(target_ref).
Values()) {
6123 value_set.insert(v);
6125 for (
const auto& [
value, literals] : value_to_refs) {
6127 if (!value_set.contains(
value)) {
6128 for (
const int lit : literals) {
6134 if (literals.size() == 1 && (in_exactly_one ||
value != rhs)) {
6137 ++*num_unique_terms;
6142 ++*num_multiple_terms;
6143 const int associated_lit =
6145 for (
const int lit : literals) {
6151 if (in_exactly_one ||
value != rhs) {
6158 for (
const int lit : literals) bool_or->
add_literals(lit);
6159 bool_or->add_literals(
NegatedRef(associated_lit));
6175uint64_t ComputeSignature(
const std::vector<int>& integers) {
6176 uint64_t signature = 0;
6177 for (
const int i : integers) signature |= (int64_t{1} << (i & 63));
6183void CpModelPresolver::ExtractEncodingFromLinear() {
6192 std::vector<int> vars;
6193 bool is_exactly_one;
6195 std::vector<Superset> potential_supersets;
6199 std::vector<int> vars;
6201 std::vector<Subset> potential_subsets;
6205 for (
int c = 0; c < num_constraints; ++c) {
6207 switch (
ct.constraint_case()) {
6209 std::vector<int> vars;
6210 for (
const int ref :
ct.at_most_one().literals()) {
6213 potential_supersets.push_back({c, std::move(vars),
false});
6217 std::vector<int> vars;
6218 for (
const int ref :
ct.exactly_one().literals()) {
6221 potential_supersets.push_back({c, std::move(vars),
true});
6226 if (!
ct.enforcement_literal().empty())
continue;
6227 if (
ct.linear().domain().size() != 2)
continue;
6228 if (
ct.linear().domain(0) !=
ct.linear().domain(1))
continue;
6232 std::vector<int> vars;
6233 bool is_candidate =
true;
6234 int num_integers = 0;
6235 const int num_terms =
ct.linear().vars().size();
6236 for (
int i = 0; i < num_terms; ++i) {
6237 const int ref =
ct.linear().vars(i);
6242 if (std::abs(
ct.linear().coeffs(i)) != 1) {
6243 is_candidate =
false;
6246 if (num_integers == 2) {
6247 is_candidate =
false;
6255 if (is_candidate && num_integers == 1 && vars.size() > 1) {
6256 potential_subsets.push_back({c, std::move(vars)});
6265 if (potential_supersets.empty())
return;
6266 if (potential_subsets.empty())
return;
6270 std::sort(potential_supersets.begin(), potential_supersets.end(),
6271 [](
const Superset&
a,
const Superset&
b) {
6272 const int size_a = a.vars.size();
6273 const int size_b = b.vars.size();
6274 return std::tie(size_a, a.is_exactly_one) <
6275 std::tie(size_b, b.is_exactly_one);
6277 std::sort(potential_subsets.begin(), potential_subsets.end(),
6278 [](
const Subset&
a,
const Subset&
b) {
6279 return a.vars.size() < b.vars.size();
6283 int64_t num_exactly_one_encodings = 0;
6284 int64_t num_at_most_one_encodings = 0;
6285 int64_t num_literals = 0;
6286 int64_t num_unique_terms = 0;
6287 int64_t num_multiple_terms = 0;
6293 int subset_index = 0;
6294 std::vector<uint64_t> signatures;
6295 std::vector<std::vector<int>> one_watcher;
6296 std::vector<bool> is_in_superset;
6297 for (
const Superset& superset : potential_supersets) {
6299 const int superset_size = superset.vars.size();
6300 for (; subset_index < potential_subsets.size(); ++subset_index) {
6301 const std::vector<int>& vars = potential_subsets[subset_index].vars;
6302 if (vars.size() > superset_size)
break;
6305 int best_choice = -1;
6306 for (
const int var : vars) {
6307 if (one_watcher.size() <=
var) one_watcher.resize(
var + 1);
6308 if (best_choice == -1 ||
6309 one_watcher[
var].size() < one_watcher[best_choice].size()) {
6313 one_watcher[best_choice].push_back(subset_index);
6314 CHECK_EQ(signatures.size(), subset_index);
6315 signatures.push_back(ComputeSignature(vars));
6319 DCHECK(absl::c_all_of(is_in_superset, [](
bool b) {
return !
b; }));
6320 for (
const int var : superset.vars) {
6321 if (
var >= is_in_superset.size()) {
6322 is_in_superset.resize(
var + 1,
false);
6324 is_in_superset[
var] =
true;
6327 const int max_size =
std::max(one_watcher.size(), is_in_superset.size());
6328 one_watcher.resize(max_size);
6329 is_in_superset.resize(max_size,
false);
6332 const uint64_t superset_signature = ComputeSignature(superset.vars);
6333 for (
const int superset_var : superset.vars) {
6334 for (
int i = 0; i < one_watcher[superset_var].size(); ++i) {
6335 const int subset_index = one_watcher[superset_var][i];
6336 const Subset& subset = potential_subsets[subset_index];
6337 CHECK_LE(subset.vars.size(), superset_size);
6340 if ((signatures[subset_index] & ~superset_signature) != 0)
continue;
6343 bool is_included =
true;
6344 for (
const int subset_var : subset.vars) {
6345 if (!is_in_superset[subset_var]) {
6346 is_included =
false;
6350 if (!is_included)
continue;
6352 if (!superset.is_exactly_one) {
6353 ++num_at_most_one_encodings;
6355 ++num_exactly_one_encodings;
6357 num_literals += subset.vars.size();
6360 if (!ProcessEncodingFromLinear(
6362 &num_unique_terms, &num_multiple_terms)) {
6369 one_watcher[superset_var].back());
6370 one_watcher[superset_var].pop_back();
6376 for (
const int var : superset.vars) {
6377 is_in_superset[
var] =
false;
6381 SOLVER_LOG(logger_,
"[ExtractEncodingFromLinear]",
6382 " #potential_supersets=", potential_supersets.size(),
6383 " #potential_subsets=", potential_subsets.size(),
6384 " #at_most_one_encodings=", num_at_most_one_encodings,
6385 " #exactly_one_encodings=", num_exactly_one_encodings,
6386 " #unique_terms=", num_unique_terms,
6387 " #multiple_terms=", num_multiple_terms,
6388 " #literals=", num_literals,
" time=",
wall_timer.
Get(),
"s");
6391void CpModelPresolver::TryToSimplifyDomain(
int var) {
6400 if (r.representative !=
var)
return;
6420 absl::flat_hash_set<int64_t> values_set;
6421 absl::flat_hash_map<int64_t, std::vector<int>> value_to_equal_literals;
6422 absl::flat_hash_map<int64_t, std::vector<int>> value_to_not_equal_literals;
6425 if (c < 0)
continue;
6429 int64_t coeff =
ct.linear().coeffs(0);
6430 if (std::abs(coeff) != 1 ||
ct.enforcement_literal().size() != 1) {
6438 if (rhs.IsFixed()) {
6444 values_set.insert(rhs.FixedValue());
6445 value_to_equal_literals[rhs.FixedValue()].push_back(
6446 ct.enforcement_literal(0));
6449 const Domain complement =
6451 if (complement.IsEmpty()) {
6456 if (complement.IsFixed()) {
6458 values_set.insert(complement.FixedValue());
6459 value_to_not_equal_literals[complement.FixedValue()].push_back(
6460 ct.enforcement_literal(0));
6470 }
else if (value_to_not_equal_literals.empty() &&
6471 value_to_equal_literals.empty()) {
6476 std::vector<int64_t> encoded_values(values_set.begin(), values_set.end());
6477 std::sort(encoded_values.begin(), encoded_values.end());
6478 CHECK(!encoded_values.empty());
6479 const bool is_fully_encoded =
6485 for (
const int64_t v : encoded_values) {
6487 const auto eq_it = value_to_equal_literals.find(v);
6488 if (eq_it != value_to_equal_literals.end()) {
6489 for (
const int lit : eq_it->second) {
6493 const auto neq_it = value_to_not_equal_literals.find(v);
6494 if (neq_it != value_to_not_equal_literals.end()) {
6495 for (
const int lit : neq_it->second) {
6503 Domain other_values;
6504 if (!is_fully_encoded) {
6514 if (is_fully_encoded) {
6518 obj_coeff > 0 ? encoded_values.front() : encoded_values.back();
6527 Domain(obj_coeff > 0 ? other_values.Min() : other_values.Max());
6528 min_value = other_values.FixedValue();
6533 int64_t accumulated = std::abs(min_value);
6534 for (
const int64_t
value : encoded_values) {
6538 "TODO variables: only used in objective and in encoding");
6543 ConstraintProto encoding_ct;
6544 LinearConstraintProto* linear = encoding_ct.mutable_linear();
6545 const int64_t coeff_in_equality = -1;
6546 linear->add_vars(
var);
6547 linear->add_coeffs(coeff_in_equality);
6549 linear->add_domain(-min_value);
6550 linear->add_domain(-min_value);
6551 for (
const int64_t
value : encoded_values) {
6552 if (
value == min_value)
continue;
6554 const int64_t coeff =
value - min_value;
6556 linear->add_vars(enf);
6557 linear->add_coeffs(coeff);
6560 linear->set_domain(0, encoding_ct.linear().domain(0) - coeff);
6561 linear->set_domain(1, encoding_ct.linear().domain(1) - coeff);
6563 linear->add_coeffs(-coeff);
6569 "TODO variables: only used in objective and in encoding");
6573 "variables: only used in objective and in encoding");
6580 for (
const int c : copy) {
6581 if (c < 0)
continue;
6588 for (
const int64_t
value : encoded_values) {
6591 ct->add_enforcement_literal(enf);
6592 ct->mutable_linear()->add_vars(
var);
6593 ct->mutable_linear()->add_coeffs(1);
6594 ct->mutable_linear()->add_domain(
value);
6595 ct->mutable_linear()->add_domain(
value);
6600 if (is_fully_encoded) {
6602 for (
const int64_t
value : encoded_values) {
6606 PresolveExactlyOne(new_ct);
6609 ConstraintProto* mapping_ct =
6612 mapping_ct->mutable_linear()->add_coeffs(1);
6615 for (
const int64_t
value : encoded_values) {
6618 new_ct->mutable_at_most_one()->add_literals(
literal);
6620 PresolveAtMostOne(new_ct);
6645 Domain union_of_domain;
6646 int num_positive = 0;
6647 std::vector<int> constraint_indices_to_remove;
6653 constraint_indices_to_remove.push_back(c);
6655 if (
ct.enforcement_literal().size() != 1 ||
6658 ct.linear().vars().size() != 1) {
6662 if (
ct.enforcement_literal(0) ==
var) ++num_positive;
6663 if (ct_var != -1 &&
PositiveRef(
ct.linear().vars(0)) != ct_var) {
6668 union_of_domain = union_of_domain.UnionWith(
6671 ?
ct.linear().coeffs(0)
6672 : -
ct.linear().coeffs(0)));
6674 if (!abort && num_positive == 1) {
6678 context_->
UpdateRuleStats(
"variables: removable enforcement literal");
6679 for (
const int c : constraint_indices_to_remove) {
6698 if (domain.Size() == 2 && (domain.Min() != 0 || domain.Max() != 1)) {
6703 if (domain.NumIntervals() != domain.Size())
return;
6705 const int64_t var_min = domain.Min();
6706 int64_t gcd = domain[1].start - var_min;
6708 const ClosedInterval& i = domain[
index];
6710 const int64_t shifted_value = i.start - var_min;
6714 if (gcd == 1)
break;
6716 if (gcd == 1)
return;
6723void CpModelPresolver::EncodeAllAffineRelations() {
6724 int64_t num_added = 0;
6729 if (r.representative ==
var)
continue;
6736 if (!PresolveAffineRelationIfAny(
var))
break;
6743 auto* arg =
ct->mutable_linear();
6746 arg->add_vars(r.representative);
6747 arg->add_coeffs(-r.coeff);
6748 arg->add_domain(r.offset);
6749 arg->add_domain(r.offset);
6757 if (num_added > 0) {
6758 SOLVER_LOG(logger_, num_added,
" affine relations still in the model.");
6763bool CpModelPresolver::PresolveAffineRelationIfAny(
int var) {
6767 if (r.representative ==
var)
return true;
6786 auto* arg =
ct->mutable_linear();
6789 arg->add_vars(r.representative);
6790 arg->add_coeffs(-r.coeff);
6791 arg->add_domain(r.offset);
6792 arg->add_domain(r.offset);
6798void CpModelPresolver::PresolveToFixPoint() {
6802 const int64_t max_num_operations =
6810 absl::flat_hash_set<std::pair<int, int>> var_constraint_pair_already_called;
6817 std::deque<int> queue;
6818 for (
int c = 0; c < in_queue.size(); ++c) {
6820 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
6831 std::shuffle(queue.begin(), queue.end(), *context_->
random());
6833 std::sort(queue.begin(), queue.end(), [
this](
int a,
int b) {
6834 const int score_a = context_->ConstraintToVars(a).size();
6835 const int score_b = context_->ConstraintToVars(b).size();
6836 return score_a < score_b || (score_a == score_b && a < b);
6846 const int c = queue.front();
6847 in_queue[c] =
false;
6850 const int old_num_constraint =
6854 SOLVER_LOG(logger_,
"Unsat after presolving constraint #", c,
6855 " (warning, dump might be inconsistent): ",
6860 const int new_num_constraints =
6862 if (new_num_constraints > old_num_constraint) {
6864 in_queue.resize(new_num_constraints,
true);
6865 for (
int c = old_num_constraint; c < new_num_constraints; ++c) {
6888 for (
int v = 0; v < current_num_variables; ++v) {
6890 if (!PresolveAffineRelationIfAny(v))
return;
6895 TryToSimplifyDomain(v);
6904 for (
int v = 0; v < num_vars; ++v) {
6906 if (constraints.size() != 1)
continue;
6907 const int c = *constraints.begin();
6908 if (c < 0)
continue;
6913 if (var_constraint_pair_already_called.contains(
6914 std::pair<int, int>(v, c))) {
6917 var_constraint_pair_already_called.insert({v, c});
6925 for (
int i = 0; i < 2; ++i) {
6936 if (c >= 0 && !in_queue[c]) {
6944 if (!queue.empty() || i == 1)
break;
6954 VarDomination var_dom;
6955 DualBoundStrengthening dual_bound_strengthening;
6957 &dual_bound_strengthening);
6958 if (!dual_bound_strengthening.Strengthen(context_))
return;
6970 std::sort(queue.begin(), queue.end());
6985 for (
int c = 0; c < num_constraints; ++c) {
6987 switch (
ct->constraint_case()) {
6988 case ConstraintProto::ConstraintCase::kNoOverlap:
6990 if (PresolveNoOverlap(
ct)) {
6994 case ConstraintProto::ConstraintCase::kNoOverlap2D:
6996 if (PresolveNoOverlap2D(c,
ct)) {
7000 case ConstraintProto::ConstraintCase::kCumulative:
7002 if (PresolveCumulative(
ct)) {
7006 case ConstraintProto::ConstraintCase::kBoolOr: {
7009 for (
const auto& pair :
7011 bool modified =
false;
7034 const CpModelProto& in_model,
const std::vector<int>& ignored_constraints) {
7035 const absl::flat_hash_set<int> ignored_constraints_set(
7036 ignored_constraints.begin(), ignored_constraints.end());
7041 if (ignored_constraints_set.contains(c))
continue;
7044 if (OneEnforcementLiteralIsFalse(
ct) &&
7048 switch (
ct.constraint_case()) {
7053 if (!CopyBoolOr(
ct))
return CreateUnsatModel();
7057 if (!CopyBoolAnd(
ct))
return CreateUnsatModel();
7061 if (!CopyLinear(
ct))
return CreateUnsatModel();
7065 if (!CopyAtMostOne(
ct))
return CreateUnsatModel();
7069 if (!CopyExactlyOne(
ct))
return CreateUnsatModel();
7073 if (!CopyInterval(
ct, c))
return CreateUnsatModel();
7084 for (
int c = starting_constraint_index_;
7089 const auto& it = interval_mapping_.find(*ref);
7090 if (it != interval_mapping_.end()) {
7102 temp_enforcement_literals_.clear();
7105 skipped_non_zero_++;
7108 temp_enforcement_literals_.push_back(lit);
7111 temp_enforcement_literals_.end());
7114bool ModelCopy::OneEnforcementLiteralIsFalse(
const ConstraintProto&
ct)
const {
7115 for (
const int lit :
ct.enforcement_literal()) {
7123bool ModelCopy::CopyBoolOr(
const ConstraintProto&
ct) {
7124 temp_literals_.clear();
7125 for (
const int lit :
ct.enforcement_literal()) {
7129 for (
const int lit :
ct.bool_or().literals()) {
7134 skipped_non_zero_++;
7136 temp_literals_.push_back(lit);
7143 ->Add(temp_literals_.begin(), temp_literals_.end());
7144 return !temp_literals_.empty();
7147bool ModelCopy::CopyBoolAnd(
const ConstraintProto&
ct) {
7148 bool at_least_one_false =
false;
7149 int num_non_fixed_literals = 0;
7150 for (
const int lit :
ct.bool_and().literals()) {
7152 at_least_one_false =
true;
7156 num_non_fixed_literals++;
7160 if (at_least_one_false) {
7165 for (
const int lit :
ct.enforcement_literal()) {
7167 skipped_non_zero_++;
7172 return !bool_or->literals().empty();
7173 }
else if (num_non_fixed_literals > 0) {
7175 CopyEnforcementLiterals(
ct, new_ct);
7176 BoolArgumentProto* bool_and = new_ct->mutable_bool_and();
7177 bool_and->mutable_literals()->Reserve(num_non_fixed_literals);
7178 for (
const int lit :
ct.bool_and().literals()) {
7180 skipped_non_zero_++;
7183 bool_and->add_literals(lit);
7189bool ModelCopy::CopyLinear(
const ConstraintProto&
ct) {
7190 non_fixed_variables_.clear();
7191 non_fixed_coefficients_.clear();
7193 for (
int i = 0; i <
ct.linear().vars_size(); ++i) {
7194 const int ref =
ct.linear().vars(i);
7195 const int64_t coeff =
ct.linear().coeffs(i);
7197 offset += coeff * context_->
MinOf(ref);
7198 skipped_non_zero_++;
7200 non_fixed_variables_.push_back(ref);
7201 non_fixed_coefficients_.push_back(coeff);
7205 const Domain new_domain =
7207 if (non_fixed_variables_.empty() && !new_domain.Contains(0)) {
7208 if (
ct.enforcement_literal().empty()) {
7211 temp_literals_.clear();
7212 for (
const int literal :
ct.enforcement_literal()) {
7214 skipped_non_zero_++;
7222 ->Add(temp_literals_.begin(), temp_literals_.end());
7223 return !temp_literals_.empty();
7229 CopyEnforcementLiterals(
ct, new_ct);
7230 LinearConstraintProto* linear = new_ct->mutable_linear();
7231 linear->mutable_vars()->Add(non_fixed_variables_.begin(),
7232 non_fixed_variables_.end());
7233 linear->mutable_coeffs()->Add(non_fixed_coefficients_.begin(),
7234 non_fixed_coefficients_.end());
7239bool ModelCopy::CopyAtMostOne(
const ConstraintProto&
ct) {
7241 temp_literals_.clear();
7242 for (
const int lit :
ct.at_most_one().literals()) {
7244 skipped_non_zero_++;
7247 temp_literals_.push_back(lit);
7251 if (temp_literals_.size() <= 1)
return true;
7252 if (num_true > 1)
return false;
7256 CopyEnforcementLiterals(
ct, new_ct);
7257 new_ct->mutable_at_most_one()->mutable_literals()->Add(temp_literals_.begin(),
7258 temp_literals_.end());
7262bool ModelCopy::CopyExactlyOne(
const ConstraintProto&
ct) {
7264 temp_literals_.clear();
7265 for (
const int lit :
ct.exactly_one().literals()) {
7267 skipped_non_zero_++;
7270 temp_literals_.push_back(lit);
7274 if (temp_literals_.empty() || num_true > 1)
return false;
7275 if (temp_literals_.size() == 1 && num_true == 1)
return true;
7279 CopyEnforcementLiterals(
ct, new_ct);
7280 new_ct->mutable_exactly_one()->mutable_literals()->Add(temp_literals_.begin(),
7281 temp_literals_.end());
7285bool ModelCopy::CopyInterval(
const ConstraintProto&
ct,
int c) {
7287 CHECK_EQ(starting_constraint_index_, 0)
7288 <<
"Adding new interval constraints to partially filled model is not "
7295bool ModelCopy::CreateUnsatModel() {
7309 return context->NotifyThatModelIsUnsat();
7314 if (!in_model.
name().empty()) {
7315 context->working_model->set_name(in_model.
name());
7321 *
context->working_model->mutable_floating_point_objective() =
7325 *
context->working_model->mutable_search_strategy() =
7344 std::vector<int>* postsolve_mapping) {
7350 std::vector<int>* postsolve_mapping)
7351 : postsolve_mapping_(postsolve_mapping),
7385 for (
const auto& decision_strategy :
7401 PresolveEnforcementLiteral(
ct);
7402 switch (
ct->constraint_case()) {
7403 case ConstraintProto::ConstraintCase::kBoolOr:
7406 case ConstraintProto::ConstraintCase::kBoolAnd:
7407 PresolveBoolAnd(
ct);
7409 case ConstraintProto::ConstraintCase::kAtMostOne:
7410 PresolveAtMostOne(
ct);
7412 case ConstraintProto::ConstraintCase::kExactlyOne:
7413 PresolveExactlyOne(
ct);
7415 case ConstraintProto::ConstraintCase::kLinear:
7416 CanonicalizeLinear(
ct);
7424 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
7434 "The floating point objective cannot be scaled with enough "
7457 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
7460 EncodeAllAffineRelations();
7466 for (
int iter = 0; iter < context_->
params().max_presolve_iterations();
7471 int old_num_non_empty_constraints = 0;
7475 if (type == ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET)
continue;
7476 old_num_non_empty_constraints++;
7483 PresolveToFixPoint();
7487 ExtractEncodingFromLinear();
7496 PresolveToFixPoint();
7505 PresolvePureSatPart();
7519 for (
int c = 0; c < old_size; ++c) {
7521 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear) {
7524 ExtractAtMostOneFromLinear(
ct);
7529 if (iter == 0) TransformIntoMaxCliques();
7546 PresolveToFixPoint();
7555 old_num_non_empty_constraints)) {
7559 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
7562 MergeNoOverlapConstraints();
7563 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
7568 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
7572 EncodeAllAffineRelations();
7573 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
7579 const std::vector<std::pair<int, int>> duplicates =
7581 for (
const auto [dup, rep] : duplicates) {
7589 if (type == ConstraintProto::ConstraintCase::kInterval) {
7601 context_->
UpdateRuleStats(
"duplicate: merged rhs of linear constraint");
7604 if (!MarkConstraintAsFalse(
7606 SOLVER_LOG(logger_,
"Unsat after merging two linear constraints");
7626 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
7636 absl::flat_hash_set<int> used_variables;
7641 strategy.clear_transformations();
7642 for (
const int ref : copy.
variables()) {
7650 if (used_variables.contains(
var))
continue;
7651 used_variables.insert(
var);
7659 if (strategy.variable_selection_strategy() !=
7662 strategy.add_transformations();
7663 t->
set_index(strategy.variables_size());
7667 strategy.add_variables(rep);
7674 strategy.add_variables(ref);
7691 postsolve_mapping_->clear();
7693 int num_free_variables = 0;
7695 if (mapping[i] != -1)
continue;
7704 mapping[r] = postsolve_mapping_->size();
7705 postsolve_mapping_->push_back(r);
7717 ++num_free_variables;
7723 mapping[i] = postsolve_mapping_->size();
7724 postsolve_mapping_->push_back(i);
7726 context_->
UpdateRuleStats(absl::StrCat(
"presolve: ", num_free_variables,
7727 " unused variables removed."));
7730 std::shuffle(postsolve_mapping_->begin(), postsolve_mapping_->end(),
7732 for (
int i = 0; i < postsolve_mapping_->size(); ++i) {
7733 mapping[(*postsolve_mapping_)[i]] = i;
7757 if (!error.empty()) {
7758 SOLVER_LOG(logger_,
"Error while validating postsolved model: ", error);
7764 if (!error.empty()) {
7766 "Error while validating mapping_model model: ", error);
7780 auto mapping_function = [&mapping](
int* ref) {
7793 mapping_function(&mutable_ref);
7799 mapping_function(&mutable_ref);
7807 std::vector<int> new_indices(copy.
variables().size(), -1);
7808 for (
int i = 0; i < copy.
variables().size(); ++i) {
7812 new_indices[i] = strategy.variables_size();
7816 strategy.clear_transformations();
7818 CHECK_LT(transform.index(), new_indices.size());
7819 const int new_index = new_indices[transform.index()];
7820 if (new_index == -1)
continue;
7821 auto* new_transform = strategy.add_transformations();
7822 *new_transform = transform;
7823 CHECK_LT(new_index, strategy.variables().size());
7824 new_transform->set_index(new_index);
7831 absl::flat_hash_set<int> used_vars;
7834 for (
int i = 0; i < mutable_hint->vars_size(); ++i) {
7835 const int old_ref = mutable_hint->
vars(i);
7836 int64_t old_value = mutable_hint->values(i);
7840 if (old_value <
context.MinOf(old_ref)) {
7841 old_value =
context.MinOf(old_ref);
7843 if (old_value >
context.MaxOf(old_ref)) {
7844 old_value =
context.MaxOf(old_ref);
7853 const int image = mapping[
var];
7855 if (!used_vars.insert(image).second)
continue;
7856 mutable_hint->set_vars(new_size, image);
7857 mutable_hint->set_values(new_size,
value);
7862 mutable_hint->mutable_vars()->Truncate(new_size);
7863 mutable_hint->mutable_values()->Truncate(new_size);
7870 std::vector<IntegerVariableProto> new_variables;
7871 for (
int i = 0; i < mapping.size(); ++i) {
7872 const int image = mapping[i];
7873 if (image < 0)
continue;
7874 if (image >= new_variables.size()) {
7891ConstraintProto CopyConstraintForDuplicateDetection(
const ConstraintProto&
ct) {
7892 ConstraintProto copy =
ct;
7895 copy.mutable_linear()->clear_domain();
7903 std::vector<std::pair<int, int>> result;
7907 absl::flat_hash_map<uint64_t, int> equiv_constraints;
7911 for (
int c = 0; c < num_constraints; ++c) {
7921 s = copy.SerializeAsString();
7923 const uint64_t
hash = absl::Hash<std::string>()(s);
7924 const auto [it, inserted] = equiv_constraints.insert({
hash, c});
7927 const int other_c_with_same_hash = it->second;
7928 copy = CopyConstraintForDuplicateDetection(
7930 if (s == copy.SerializeAsString()) {
7931 result.push_back({c, other_c_with_same_hash});
#define DCHECK_NE(val1, val2)
#define CHECK_LT(val1, val2)
#define CHECK_EQ(val1, val2)
#define CHECK_GE(val1, val2)
#define CHECK_GT(val1, val2)
#define DCHECK_GE(val1, val2)
#define CHECK_NE(val1, val2)
#define DCHECK_GT(val1, val2)
#define DCHECK_LT(val1, val2)
#define DCHECK(condition)
#define CHECK_LE(val1, val2)
#define DCHECK_EQ(val1, val2)
#define VLOG(verboselevel)
We call domain any subset of Int64 = [kint64min, kint64max].
Domain InverseMultiplicationBy(const int64_t coeff) const
Returns {x ∈ Int64, ∃ e ∈ D, x * coeff = e}.
bool Contains(int64_t value) const
Returns true iff value is in Domain.
Domain AdditionWith(const Domain &domain) const
Returns {x ∈ Int64, ∃ a ∈ D, ∃ b ∈ domain, x = a + b}.
ClosedInterval front() const
int64_t Size() const
Returns the number of elements in the domain.
Domain UnionWith(const Domain &domain) const
Returns the union of D and domain.
Domain MultiplicationBy(int64_t coeff, bool *exact=nullptr) const
Returns {x ∈ Int64, ∃ e ∈ D, x = e * coeff}.
Domain IntersectionWith(const Domain &domain) const
Returns the intersection of D and domain.
bool IsEmpty() const
Returns true if this is the empty set.
int64_t SmallestValue() const
Returns the value closest to zero.
Domain RelaxIfTooComplex() const
If NumIntervals() is too large, this return a superset of the domain.
static Domain FromValues(std::vector< int64_t > values)
Creates a domain from the union of an unsorted list of integer values.
Domain DivisionBy(int64_t coeff) const
Returns {x ∈ Int64, ∃ e ∈ D, x = e / coeff}.
DomainIteratorBeginEnd Values() const &
Domain PositiveModuloBySuperset(const Domain &modulo) const
Returns a superset of {x ∈ Int64, ∃ e ∈ D, ∃ m ∈ modulo, x = e % m }.
static int64_t GCD64(int64_t x, int64_t y)
bool LoggingIsEnabled() const
bool LimitReached()
Returns true when the external limit is true, or the deterministic time is over the deterministic lim...
void AdvanceDeterministicTime(double deterministic_duration)
Advances the deterministic time.
::operations_research::sat::LinearExpressionProto * add_exprs()
::PROTOBUF_NAMESPACE_ID::RepeatedField< int32_t > * mutable_literals()
void add_literals(int32_t value)
int32_t literals(int index) const
const ::operations_research::sat::BoolArgumentProto & at_most_one() const
::operations_research::sat::IntervalConstraintProto * mutable_interval()
::operations_research::sat::NoOverlap2DConstraintProto * mutable_no_overlap_2d()
::operations_research::sat::ElementConstraintProto * mutable_element()
::operations_research::sat::NoOverlapConstraintProto * mutable_no_overlap()
ConstraintCase constraint_case() const
const ::operations_research::sat::LinearConstraintProto & linear() const
void set_name(ArgT0 &&arg0, ArgT... args)
int32_t enforcement_literal(int index) const
::PROTOBUF_NAMESPACE_ID::RepeatedField< int32_t > * mutable_enforcement_literal()
::operations_research::sat::BoolArgumentProto * mutable_bool_and()
const ::operations_research::sat::BoolArgumentProto & exactly_one() const
PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final
::operations_research::sat::AllDifferentConstraintProto * mutable_all_diff()
void Swap(ConstraintProto *other)
::operations_research::sat::BoolArgumentProto * mutable_exactly_one()
::operations_research::sat::LinearConstraintProto * mutable_linear()
void add_enforcement_literal(int32_t value)
::operations_research::sat::BoolArgumentProto * mutable_bool_or()
::operations_research::sat::CumulativeConstraintProto * mutable_cumulative()
CpSolverStatus Presolve()
void RemoveEmptyConstraints()
CpModelPresolver(PresolveContext *context, std::vector< int > *postsolve_mapping)
bool PresolveOneConstraint(int c)
::PROTOBUF_NAMESPACE_ID::RepeatedField< int32_t > * mutable_assumptions()
::operations_research::sat::DecisionStrategyProto * add_search_strategy()
const std::string & name() const
const ::operations_research::sat::CpObjectiveProto & objective() const
::operations_research::sat::DecisionStrategyProto * mutable_search_strategy(int index)
const ::operations_research::sat::IntegerVariableProto & variables(int index) const
bool has_floating_point_objective() const
const ::operations_research::sat::DecisionStrategyProto & search_strategy(int index) const
const ::operations_research::sat::PartialVariableAssignment & solution_hint() const
bool has_objective() const
::operations_research::sat::PartialVariableAssignment * mutable_solution_hint()
::operations_research::sat::ConstraintProto * mutable_constraints(int index)
const ::operations_research::sat::SymmetryProto & symmetry() const
bool has_solution_hint() const
::operations_research::sat::IntegerVariableProto * mutable_variables(int index)
int variables_size() const
::operations_research::sat::ConstraintProto * add_constraints()
const ::operations_research::sat::FloatObjectiveProto & floating_point_objective() const
::operations_research::sat::IntegerVariableProto * add_variables()
::operations_research::sat::CpObjectiveProto * mutable_objective()
int32_t assumptions(int index) const
void clear_solution_hint()
int constraints_size() const
bool has_symmetry() const
const ::operations_research::sat::ConstraintProto & constraints(int index) const
::PROTOBUF_NAMESPACE_ID::RepeatedField< int32_t > * mutable_vars()
void add_intervals(int32_t value)
int32_t variables(int index) const
static constexpr VariableSelectionStrategy CHOOSE_FIRST
const ::operations_research::sat::DecisionStrategyProto_AffineTransformation & transformations(int index) const
std::vector< std::pair< int, Domain > > ProcessClause(absl::Span< const int > clause)
void MarkProcessingAsDoneForNow()
int NumDeductions() const
void AddDeduction(int literal_ref, int var, Domain domain)
void Swap(IntegerVariableProto *other)
void CopyFrom(const IntegerVariableProto &from)
void add_domain(int64_t value)
::operations_research::sat::LinearExpressionProto * mutable_start()
::PROTOBUF_NAMESPACE_ID::RepeatedField< int64_t > * mutable_coeffs()
void set_coeffs(int index, int64_t value)
void add_vars(int32_t value)
int64_t domain(int index) const
int32_t vars(int index) const
int64_t coeffs(int index) const
::PROTOBUF_NAMESPACE_ID::RepeatedField< int32_t > * mutable_vars()
void add_domain(int64_t value)
void set_vars(int index, int32_t value)
void set_offset(int64_t value)
ModelCopy(PresolveContext *context)
bool ImportAndSimplifyConstraints(const CpModelProto &in_model, const std::vector< int > &ignored_constraints)
void add_x_intervals(int32_t value)
void add_intervals(int32_t value)
int32_t vars(int index) const
bool VariableIsRemovable(int ref) const
int64_t MaxOf(int ref) const
bool CanonicalizeAffineVariable(int ref, int64_t coeff, int64_t mod, int64_t rhs)
SparseBitset< int64_t > modified_domains
bool ExpressionIsALiteral(const LinearExpressionProto &expr, int *literal=nullptr) const
bool StoreAbsRelation(int target_ref, int ref)
bool VariableWithCostIsUnique(int ref) const
bool ConstraintIsInactive(int ct_index) const
bool ConstraintVariableUsageIsConsistent()
const absl::flat_hash_set< int > & VarToConstraints(int var) const
bool ModelIsExpanded() const
void AddImplication(int a, int b)
bool ModelIsUnsat() const
ABSL_MUST_USE_RESULT bool IntersectDomainWith(int ref, const Domain &domain, bool *domain_modified=nullptr)
std::vector< absl::flat_hash_set< int > > var_to_lb_only_constraints
bool ConstraintVariableGraphIsUpToDate() const
bool StoreLiteralImpliesVarNEqValue(int literal, int var, int64_t value)
ABSL_MUST_USE_RESULT bool CanonicalizeObjective(bool simplify_domain=true)
bool StoreBooleanEqualityRelation(int ref_a, int ref_b)
int64_t StartMin(int ct_ref) const
bool DomainOfVarIsIncludedIn(int var, const Domain &domain)
bool VariableWithCostIsUniqueAndRemovable(int ref) const
void WriteObjectiveToProto() const
int GetLiteralRepresentative(int ref) const
ABSL_MUST_USE_RESULT bool SetLiteralToTrue(int lit)
std::vector< int > tmp_literals
ABSL_MUST_USE_RESULT bool ScaleFloatingPointObjective()
std::vector< absl::flat_hash_set< int > > var_to_ub_only_constraints
bool ObjectiveDomainIsConstraining() const
CpModelProto * mapping_model
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)
int GetOrCreateVarValueEncoding(int ref, int64_t value)
void UpdateNewConstraintsVariableUsage()
bool VariableIsUniqueAndRemovable(int ref) const
void RemoveVariableFromAffineRelation(int var)
ABSL_MUST_USE_RESULT bool NotifyThatModelIsUnsat(const std::string &message="")
bool PropagateAffineRelation(int ref)
Domain DomainOf(int ref) const
int64_t num_presolve_operations
void InitializeNewDomains()
int GetVariableRepresentative(int ref) const
void MarkVariableAsRemoved(int ref)
bool InsertVarValueEncoding(int literal, int ref, int64_t value)
ModelRandomGenerator * random()
DomainDeductions deductions
std::vector< Domain > tmp_left_domains
void CanonicalizeDomainOfSizeTwo(int var)
const std::vector< int > & ConstraintToVars(int c) const
int64_t FixedValue(int ref) const
bool LiteralIsTrue(int lit) const
const absl::flat_hash_map< int, int64_t > & ObjectiveMap() const
CpModelProto * working_model
bool IntervalIsConstant(int ct_ref) const
bool DomainContains(int ref, int64_t value) const
bool LiteralIsFalse(int lit) const
void UpdateRuleStats(const std::string &name, int num_times=1)
void RemoveAllVariablesFromAffineRelationConstraint()
AffineRelation::Relation GetAffineRelation(int ref) const
bool VariableIsNotUsedAnymore(int ref) const
void UpdateConstraintVariableUsage(int c)
bool keep_all_feasible_solutions
bool IsFixed(int ref) const
std::vector< Domain > tmp_term_domains
bool StoreAffineRelation(int ref_x, int ref_y, int64_t coeff, int64_t offset, bool debug_no_recursion=false)
ABSL_MUST_USE_RESULT bool SetLiteralToFalse(int lit)
int LiteralForExpressionMax(const LinearExpressionProto &expr) const
int64_t EndMax(int ct_ref) const
bool ConstraintIsOptional(int ct_ref) const
int GetOrCreateConstantVar(int64_t cst)
const SatParameters & params() const
bool ExpressionIsAffineBoolean(const LinearExpressionProto &expr) const
int64_t SizeMax(int ct_ref) const
bool ExploitExactlyOneInObjective(absl::Span< const int > exactly_one)
Domain DomainSuperSetOf(const LinearExpressionProto &expr) const
absl::flat_hash_set< int > tmp_literal_set
void ReadObjectiveFromProto()
int64_t SizeMin(int ct_ref) const
bool CanBeUsedAsLiteral(int ref) const
bool VariableWasRemoved(int ref) const
void RegisterVariablesUsedInAssumptions()
void ExploitFixedDomain(int var)
int64_t MinOf(int ref) const
bool VariableIsOnlyUsedInEncodingAndMaybeInObjective(int ref) const
bool GetAbsRelation(int target_ref, int *ref)
bool StoreLiteralImpliesVarEqValue(int literal, int var, int64_t value)
const Domain & ObjectiveDomain() const
double merge_no_overlap_work_limit() const
bool enumerate_all_solutions() const
bool cp_model_use_sat_presolve() const
bool permute_presolve_constraint_order() const
::operations_research::sat::SatParameters_SearchBranching search_branching() const
bool keep_all_feasible_solutions_in_presolve() const
int32_t debug_max_num_presolve_operations() const
int32_t max_presolve_iterations() const
double merge_at_most_one_work_limit() const
bool cp_model_presolve() const
bool fill_tightened_domains_in_response() const
void set_presolve_blocked_clause(bool value)
int32_t symmetry_level() const
bool presolve_extract_integer_enforcement() const
bool permute_variable_randomly() const
int32_t presolve_substitution_level() const
int32_t cp_model_probing_level() const
static constexpr SearchBranching FIXED_SEARCH
CpModelProto const * model_proto
ModelSharedTimeLimit * time_limit
GurobiMPCallbackContext * context
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
void STLSetDifference(const In1 &a, const In2 &b, Out *out, Compare compare)
bool ContainsKey(const Collection &collection, const Key &key)
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
bool DetectAndExploitSymmetriesInPresolve(PresolveContext *context)
bool RefIsPositive(int ref)
void GetOverlappingIntervalComponents(std::vector< IndexedInterval > *intervals, std::vector< std::vector< int > > *components)
const LiteralIndex kNoLiteralIndex(-1)
std::vector< absl::Span< int > > GetOverlappingRectangleComponents(const std::vector< Rectangle > &rectangles, absl::Span< int > active_rectangles)
bool HasEnforcementLiteral(const ConstraintProto &ct)
void ExpandCpModel(PresolveContext *context)
constexpr int kAffineRelationConstraint
void ApplyToAllLiteralIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
void DetectDominanceRelations(const PresolveContext &context, VarDomination *var_domination, DualBoundStrengthening *dual_bound_strengthening)
bool LinearExpressionProtosAreEqual(const LinearExpressionProto &a, const LinearExpressionProto &b, int64_t b_scaling)
void ApplyToAllIntervalIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
bool SolveDiophantineEquationOfSizeTwo(int64_t &a, int64_t &b, int64_t &cte, int64_t &x0, int64_t &y0)
void CopyEverythingExceptVariablesAndConstraintsFieldsIntoContext(const CpModelProto &in_model, PresolveContext *context)
int ReindexArcs(IntContainer *tails, IntContainer *heads)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
bool ImportConstraintsWithBasicPresolveIntoContext(const CpModelProto &in_model, PresolveContext *context)
std::vector< std::pair< int, int > > FindDuplicateConstraints(const CpModelProto &model_proto)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
void ApplyToAllVariableIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
CpSolverStatus PresolveCpModel(PresolveContext *context, std::vector< int > *postsolve_mapping)
bool LoadModelForProbing(PresolveContext *context, Model *local_model)
void ConstructOverlappingSets(bool already_sorted, std::vector< IndexedInterval > *intervals, std::vector< std::vector< int > > *result)
constexpr int kObjectiveConstraint
void AddLinearExpressionToLinearConstraint(const LinearExpressionProto &expr, int64_t coefficient, LinearConstraintProto *linear)
bool ExploitDominanceRelations(const VarDomination &var_domination, PresolveContext *context)
int GetSingleRefFromExpression(const LinearExpressionProto &expr)
bool ExpressionContainsSingleRef(const LinearExpressionProto &expr)
void ApplyVariableMapping(const std::vector< int > &mapping, const PresolveContext &context)
std::string ValidateCpModel(const CpModelProto &model)
bool SubstituteVariable(int var, int64_t var_coeff_in_definition, const ConstraintProto &definition, ConstraintProto *ct)
Collection of objects used to extend the Constraint Solver library.
int64_t CapAdd(int64_t x, int64_t y)
int64_t CapSub(int64_t x, int64_t y)
int64_t CapProd(int64_t x, int64_t y)
#define SOLVER_LOG(logger,...)