27#include "absl/base/attributes.h"
28#include "absl/container/btree_map.h"
29#include "absl/container/btree_set.h"
30#include "absl/container/flat_hash_map.h"
31#include "absl/container/flat_hash_set.h"
32#include "absl/hash/hash.h"
33#include "absl/meta/type_traits.h"
34#include "absl/numeric/int128.h"
35#include "absl/strings/str_cat.h"
36#include "absl/types/span.h"
45#include "ortools/sat/cp_model.pb.h"
59#include "ortools/sat/sat_parameters.pb.h"
74bool CpModelPresolver::RemoveConstraint(ConstraintProto*
ct) {
85 std::vector<int> interval_mapping(context_->
working_model->constraints_size(),
87 int new_num_constraints = 0;
88 const int old_num_non_empty_constraints =
90 for (
int c = 0; c < old_num_non_empty_constraints; ++c) {
91 const auto type = context_->
working_model->constraints(c).constraint_case();
92 if (type == ConstraintProto::CONSTRAINT_NOT_SET)
continue;
93 if (type == ConstraintProto::kDummyConstraint)
continue;
94 if (type == ConstraintProto::kInterval) {
95 interval_mapping[c] = new_num_constraints;
97 context_->
working_model->mutable_constraints(new_num_constraints++)
100 context_->
working_model->mutable_constraints()->DeleteSubrange(
101 new_num_constraints, old_num_non_empty_constraints - new_num_constraints);
102 for (ConstraintProto& ct_ref :
105 [&interval_mapping](
int* ref) {
106 *ref = interval_mapping[*ref];
113bool CpModelPresolver::PresolveEnforcementLiteral(ConstraintProto*
ct) {
118 const int old_size =
ct->enforcement_literal().size();
119 for (
const int literal :
ct->enforcement_literal()) {
128 return RemoveConstraint(
ct);
135 return RemoveConstraint(
ct);
142 const int64_t obj_coeff =
146 context_->
UpdateRuleStats(
"enforcement literal with unique direction");
148 return RemoveConstraint(
ct);
152 ct->set_enforcement_literal(new_size++,
literal);
154 ct->mutable_enforcement_literal()->Truncate(new_size);
155 return new_size != old_size;
158bool CpModelPresolver::PresolveBoolXor(ConstraintProto*
ct) {
163 bool changed =
false;
164 int num_true_literals = 0;
166 for (
const int literal :
ct->bool_xor().literals()) {
187 ct->mutable_bool_xor()->set_literals(new_size++,
literal);
191 if (num_true_literals % 2 == 0) {
195 return RemoveConstraint(
ct);
197 }
else if (new_size == 1) {
198 if (num_true_literals % 2 == 0) {
201 "bool_xor: cannot fix last literal");
206 "bool_xor: cannot fix last literal");
210 return RemoveConstraint(
ct);
211 }
else if (new_size == 2) {
212 const int a =
ct->bool_xor().literals(0);
213 const int b =
ct->bool_xor().literals(1);
215 if (num_true_literals % 2 == 0) {
219 return RemoveConstraint(
ct);
223 if (num_true_literals % 2 == 1) {
227 return RemoveConstraint(
ct);
230 if (num_true_literals % 2 == 0) {
237 return RemoveConstraint(
ct);
240 if (num_true_literals % 2 == 1) {
242 ct->mutable_bool_xor()->set_literals(new_size++, true_literal);
244 if (num_true_literals > 1) {
245 context_->
UpdateRuleStats(
"bool_xor: remove even number of true literals");
248 ct->mutable_bool_xor()->mutable_literals()->Truncate(new_size);
252bool CpModelPresolver::PresolveBoolOr(ConstraintProto*
ct) {
259 for (
const int literal :
ct->enforcement_literal()) {
262 ct->clear_enforcement_literal();
270 bool changed =
false;
273 for (
const int literal :
ct->bool_or().literals()) {
280 return RemoveConstraint(
ct);
288 return RemoveConstraint(
ct);
292 return RemoveConstraint(
ct);
311 return RemoveConstraint(
ct);
324 ct->mutable_bool_or()->mutable_literals()->Clear();
326 ct->mutable_bool_or()->add_literals(lit);
335ABSL_MUST_USE_RESULT
bool CpModelPresolver::MarkConstraintAsFalse(
336 ConstraintProto*
ct) {
339 ct->mutable_bool_or()->clear_literals();
340 for (
const int lit :
ct->enforcement_literal()) {
343 ct->clear_enforcement_literal();
351bool CpModelPresolver::PresolveBoolAnd(ConstraintProto*
ct) {
356 for (
const int literal :
ct->bool_and().literals()) {
359 return RemoveConstraint(
ct);
362 bool changed =
false;
364 for (
const int literal :
ct->bool_and().literals()) {
367 return MarkConstraintAsFalse(
ct);
387 ct->mutable_bool_and()->mutable_literals()->Clear();
389 ct->mutable_bool_and()->add_literals(lit);
398 if (
ct->enforcement_literal().size() == 1 &&
399 ct->bool_and().literals().size() == 1) {
400 const int enforcement =
ct->enforcement_literal(0);
410 ct->bool_and().literals(0));
418bool CpModelPresolver::PresolveAtMostOrExactlyOne(ConstraintProto*
ct) {
419 bool is_at_most_one =
ct->constraint_case() == ConstraintProto::kAtMostOne;
420 const std::string
name = is_at_most_one ?
"at_most_one: " :
"exactly_one: ";
421 auto* literals = is_at_most_one
422 ?
ct->mutable_at_most_one()->mutable_literals()
423 :
ct->mutable_exactly_one()->mutable_literals();
427 for (
const int literal : *literals) {
434 int num_positive = 0;
435 int num_negative = 0;
436 for (
const int other : *literals) {
457 return RemoveConstraint(
ct);
462 bool changed =
false;
463 bool transform_to_at_most_one =
false;
465 for (
const int literal : *literals) {
468 for (
const int other : *literals) {
473 return RemoveConstraint(
ct);
486 if (is_at_most_one && !is_removable &&
490 const int64_t
coeff = it->second;
497 if (is_at_most_one) {
504 is_at_most_one =
true;
505 transform_to_at_most_one =
true;
516 if (!is_at_most_one && !transform_to_at_most_one &&
521 if (transform_to_at_most_one) {
524 literals =
ct->mutable_at_most_one()->mutable_literals();
536bool CpModelPresolver::PresolveAtMostOne(ConstraintProto*
ct) {
540 const bool changed = PresolveAtMostOrExactlyOne(
ct);
541 if (
ct->constraint_case() != ConstraintProto::kAtMostOne)
return changed;
544 const auto& literals =
ct->at_most_one().literals();
545 if (literals.empty()) {
547 return RemoveConstraint(
ct);
551 if (literals.size() == 1) {
553 return RemoveConstraint(
ct);
559bool CpModelPresolver::PresolveExactlyOne(ConstraintProto*
ct) {
562 const bool changed = PresolveAtMostOrExactlyOne(
ct);
563 if (
ct->constraint_case() != ConstraintProto::kExactlyOne)
return changed;
566 const auto& literals =
ct->exactly_one().literals();
567 if (literals.empty()) {
572 if (literals.size() == 1) {
575 return RemoveConstraint(
ct);
579 if (literals.size() == 2) {
583 return RemoveConstraint(
ct);
589bool CpModelPresolver::CanonicalizeLinearArgument(
const ConstraintProto&
ct,
590 LinearArgumentProto*
proto) {
594 bool changed = CanonicalizeLinearExpression(
ct,
proto->mutable_target());
595 for (LinearExpressionProto& exp : *(
proto->mutable_exprs())) {
596 changed |= CanonicalizeLinearExpression(
ct, &exp);
601bool CpModelPresolver::PresolveLinMax(ConstraintProto*
ct) {
607 const LinearExpressionProto& target =
ct->lin_max().target();
609 int64_t infered_min = context_->
MinOf(target);
611 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
616 if (target.vars().empty()) {
617 if (!Domain(infered_min, infered_max).Contains(target.offset())) {
619 return MarkConstraintAsFalse(
ct);
622 if (target.vars().size() <= 1) {
624 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
625 rhs_domain = rhs_domain.UnionWith(
627 {infered_min, infered_max}));
629 bool reduced =
false;
640 const int64_t target_min = context_->
MinOf(target);
641 const int64_t target_max = context_->
MaxOf(target);
642 bool changed =
false;
649 bool has_greater_or_equal_to_target_min =
false;
651 int index_to_keep = -1;
652 for (
int i = 0; i <
ct->lin_max().exprs_size(); ++i) {
653 const LinearExpressionProto& expr =
ct->lin_max().exprs(i);
654 if (context_->
MinOf(expr) >= target_min) {
655 const int64_t expr_max = context_->
MaxOf(expr);
656 if (expr_max > max_at_index_to_keep) {
657 max_at_index_to_keep = expr_max;
660 has_greater_or_equal_to_target_min =
true;
665 for (
int i = 0; i <
ct->lin_max().exprs_size(); ++i) {
666 const LinearExpressionProto& expr =
ct->lin_max().exprs(i);
667 const int64_t expr_max = context_->
MaxOf(expr);
670 if (expr_max < target_min)
continue;
671 if (expr_max == target_min && has_greater_or_equal_to_target_min &&
672 i != index_to_keep) {
675 *
ct->mutable_lin_max()->mutable_exprs(new_size) = expr;
678 if (new_size < ct->lin_max().exprs_size()) {
680 ct->mutable_lin_max()->mutable_exprs()->DeleteSubrange(
681 new_size,
ct->lin_max().exprs_size() - new_size);
686 if (
ct->lin_max().exprs().empty()) {
688 return MarkConstraintAsFalse(
ct);
693 if (
ct->lin_max().exprs().size() == 1) {
695 ConstraintProto* new_ct = context_->
working_model->add_constraints();
697 auto* arg = new_ct->mutable_linear();
698 const LinearExpressionProto&
a =
ct->lin_max().target();
699 const LinearExpressionProto&
b =
ct->lin_max().exprs(0);
700 for (
int i = 0; i <
a.vars().size(); ++i) {
701 arg->add_vars(
a.vars(i));
702 arg->add_coeffs(
a.coeffs(i));
704 for (
int i = 0; i <
b.vars().size(); ++i) {
705 arg->add_vars(
b.vars(i));
706 arg->add_coeffs(-
b.coeffs(i));
708 arg->add_domain(
b.offset() -
a.offset());
709 arg->add_domain(
b.offset() -
a.offset());
711 return RemoveConstraint(
ct);
719 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
720 const int64_t value_min = context_->
MinOf(expr);
721 bool modified =
false;
729 const int64_t value_max = context_->
MaxOf(expr);
730 if (value_max > target_max) {
731 context_->
UpdateRuleStats(
"TODO lin_max: linear expression above max.");
735 if (abort)
return changed;
739 if (target_min == target_max) {
740 bool all_booleans =
true;
741 std::vector<int> literals;
742 const int64_t fixed_target = target_min;
743 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
744 const int64_t value_min = context_->
MinOf(expr);
745 const int64_t value_max = context_->
MaxOf(expr);
746 CHECK_LE(value_max, fixed_target) <<
"Presolved above";
747 if (value_max < fixed_target)
continue;
749 if (value_min == value_max && value_max == fixed_target) {
751 return RemoveConstraint(
ct);
757 all_booleans =
false;
761 if (literals.empty()) {
762 return MarkConstraintAsFalse(
ct);
767 for (
const int lit : literals) {
768 ct->mutable_bool_or()->add_literals(lit);
781 bool min_is_reachable =
false;
782 std::vector<int> min_literals;
783 std::vector<int> literals_above_min;
784 std::vector<int> max_literals;
786 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
787 const int64_t value_min = context_->
MinOf(expr);
788 const int64_t value_max = context_->
MaxOf(expr);
791 if (value_min > target_min) {
799 if (value_min == value_max) {
800 if (value_min == target_min) min_is_reachable =
true;
811 if (value_min == target_min) {
816 if (value_max == target_max) {
817 max_literals.push_back(ref);
818 literals_above_min.push_back(ref);
819 }
else if (value_max > target_min) {
820 literals_above_min.push_back(ref);
821 }
else if (value_max == target_min) {
822 min_literals.push_back(ref);
829 ConstraintProto* clause = context_->
working_model->add_constraints();
830 clause->add_enforcement_literal(target_ref);
831 clause->mutable_bool_or();
832 for (
const int lit : max_literals) {
833 clause->mutable_bool_or()->add_literals(lit);
837 for (
const int lit : literals_above_min) {
841 if (!min_is_reachable) {
843 ConstraintProto* clause = context_->
working_model->add_constraints();
844 clause->add_enforcement_literal(
NegatedRef(target_ref));
845 clause->mutable_bool_or();
846 for (
const int lit : min_literals) {
847 clause->mutable_bool_or()->add_literals(lit);
852 return RemoveConstraint(
ct);
860bool CpModelPresolver::PresolveIntAbs(ConstraintProto*
ct) {
863 const LinearExpressionProto& target_expr =
ct->lin_max().target();
864 const LinearExpressionProto& expr =
ct->lin_max().exprs(0);
870 const Domain new_target_domain =
871 expr_domain.
UnionWith(expr_domain.Negation())
873 bool target_domain_modified =
false;
875 &target_domain_modified)) {
878 if (expr_domain.IsFixed()) {
880 return RemoveConstraint(
ct);
882 if (target_domain_modified) {
883 context_->
UpdateRuleStats(
"int_abs: propagate domain from x to abs(x)");
889 const Domain target_domain =
892 const Domain new_expr_domain =
893 target_domain.
UnionWith(target_domain.Negation());
894 bool expr_domain_modified =
false;
896 &expr_domain_modified)) {
901 if (context_->
IsFixed(target_expr)) {
903 return RemoveConstraint(
ct);
905 if (expr_domain_modified) {
906 context_->
UpdateRuleStats(
"int_abs: propagate domain from abs(x) to x");
911 if (context_->
MinOf(expr) >= 0) {
913 ConstraintProto* new_ct = context_->
working_model->add_constraints();
914 new_ct->set_name(
ct->name());
915 auto* arg = new_ct->mutable_linear();
920 if (!CanonicalizeLinear(new_ct))
return false;
922 return RemoveConstraint(
ct);
925 if (context_->
MaxOf(expr) <= 0) {
927 ConstraintProto* new_ct = context_->
working_model->add_constraints();
928 new_ct->set_name(
ct->name());
929 auto* arg = new_ct->mutable_linear();
934 if (!CanonicalizeLinear(new_ct))
return false;
936 return RemoveConstraint(
ct);
948 return RemoveConstraint(
ct);
969bool CpModelPresolver::PresolveIntProd(ConstraintProto*
ct) {
974 bool domain_modified =
false;
977 for (
const LinearExpressionProto& expr :
ct->int_prod().exprs()) {
988 int64_t constant_factor = 1;
990 bool changed =
false;
991 LinearArgumentProto*
proto =
ct->mutable_int_prod();
992 for (
int i = 0; i <
ct->int_prod().exprs().size(); ++i) {
993 LinearExpressionProto expr =
ct->int_prod().exprs(i);
1000 const int64_t
coeff = expr.coeffs(0);
1001 const int64_t offset = expr.offset();
1004 static_cast<uint64_t
>(std::abs(offset)));
1006 constant_factor =
CapProd(constant_factor, gcd);
1007 expr.set_coeffs(0,
coeff / gcd);
1008 expr.set_offset(offset / gcd);
1011 *
proto->mutable_exprs(new_size++) = expr;
1013 proto->mutable_exprs()->erase(
proto->mutable_exprs()->begin() + new_size,
1014 proto->mutable_exprs()->end());
1016 if (
ct->int_prod().exprs().empty()) {
1018 Domain(constant_factor))) {
1022 return RemoveConstraint(
ct);
1025 if (constant_factor == 0) {
1030 return RemoveConstraint(
ct);
1041 constant_factor = 1;
1045 if (
ct->int_prod().exprs().size() == 1) {
1046 LinearConstraintProto*
const lin =
1047 context_->
working_model->add_constraints()->mutable_linear();
1052 -constant_factor, lin);
1054 context_->
UpdateRuleStats(
"int_prod: linearize product by constant.");
1055 return RemoveConstraint(
ct);
1058 if (constant_factor != 1) {
1066 const LinearExpressionProto old_target =
ct->int_prod().target();
1067 if (!context_->
IsFixed(old_target)) {
1068 const int ref = old_target.vars(0);
1069 const int64_t
coeff = old_target.coeffs(0);
1070 const int64_t offset = old_target.offset();
1081 if (context_->
IsFixed(old_target)) {
1082 const int64_t target_value = context_->
FixedValue(old_target);
1083 if (target_value % constant_factor != 0) {
1085 "int_prod: constant factor does not divide constant target");
1088 proto->clear_target();
1089 proto->mutable_target()->set_offset(target_value / constant_factor);
1091 "int_prod: divide product and fixed target by constant factor");
1094 const AffineRelation::Relation r =
1096 const absl::int128 temp_coeff =
1097 absl::int128(old_target.coeffs(0)) * absl::int128(r.coeff);
1098 CHECK_EQ(temp_coeff % absl::int128(constant_factor), 0);
1099 const absl::int128 temp_offset =
1100 absl::int128(old_target.coeffs(0)) * absl::int128(r.offset) +
1101 absl::int128(old_target.offset());
1102 CHECK_EQ(temp_offset % absl::int128(constant_factor), 0);
1103 const absl::int128 new_coeff = temp_coeff / absl::int128(constant_factor);
1104 const absl::int128 new_offset =
1105 temp_offset / absl::int128(constant_factor);
1118 "int_prod: overflow during simplification.");
1122 proto->mutable_target()->set_coeffs(0,
static_cast<int64_t
>(new_coeff));
1123 proto->mutable_target()->set_vars(0, r.representative);
1124 proto->mutable_target()->set_offset(
static_cast<int64_t
>(new_offset));
1125 context_->
UpdateRuleStats(
"int_prod: divide product by constant factor");
1132 bool is_square =
false;
1133 if (
ct->int_prod().exprs_size() == 2 &&
1135 ct->int_prod().exprs(1))) {
1140 for (
const LinearExpressionProto& expr :
ct->int_prod().exprs()) {
1146 &domain_modified)) {
1149 if (domain_modified) {
1151 is_square ?
"int_square" :
"int_prod",
": reduced target domain."));
1156 const int64_t target_max = context_->
MaxOf(
ct->int_prod().target());
1159 bool expr_reduced =
false;
1161 {-sqrt_max, sqrt_max}, &expr_reduced)) {
1169 if (
ct->int_prod().exprs_size() == 2) {
1170 LinearExpressionProto
a =
ct->int_prod().exprs(0);
1171 LinearExpressionProto
b =
ct->int_prod().exprs(1);
1172 const LinearExpressionProto product =
ct->int_prod().target();
1179 context_->
UpdateRuleStats(
"int_square: fix variable to zero or one.");
1180 return RemoveConstraint(
ct);
1185 const LinearExpressionProto target_expr =
ct->int_prod().target();
1190 std::vector<int> literals;
1191 for (
const LinearExpressionProto& expr :
ct->int_prod().exprs()) {
1196 literals.push_back(lit);
1202 ConstraintProto* new_ct = context_->
working_model->add_constraints();
1203 new_ct->add_enforcement_literal(target);
1204 auto* arg = new_ct->mutable_bool_and();
1205 for (
const int lit : literals) {
1206 arg->add_literals(lit);
1210 ConstraintProto* new_ct = context_->
working_model->add_constraints();
1211 auto* arg = new_ct->mutable_bool_or();
1212 arg->add_literals(target);
1213 for (
const int lit : literals) {
1218 return RemoveConstraint(
ct);
1221bool CpModelPresolver::PresolveIntDiv(ConstraintProto*
ct) {
1224 const LinearExpressionProto target =
ct->int_div().target();
1225 const LinearExpressionProto expr =
ct->int_div().exprs(0);
1226 const LinearExpressionProto div =
ct->int_div().exprs(1);
1233 return RemoveConstraint(
ct);
1239 return RemoveConstraint(
ct);
1243 if (!context_->
IsFixed(div))
return false;
1245 const int64_t divisor = context_->
FixedValue(div);
1247 LinearConstraintProto*
const lin =
1248 context_->
working_model->add_constraints()->mutable_linear();
1255 return RemoveConstraint(
ct);
1257 bool domain_modified =
false;
1260 &domain_modified)) {
1263 if (domain_modified) {
1265 "int_div: updated domain of target in target = X / cte");
1271 if (context_->
MinOf(target) >= 0 && context_->
MinOf(expr) >= 0 &&
1273 LinearConstraintProto*
const lin =
1274 context_->
working_model->add_constraints()->mutable_linear();
1276 lin->add_domain(divisor - 1);
1281 "int_div: linearize positive division with a constant divisor");
1282 return RemoveConstraint(
ct);
1290bool CpModelPresolver::PresolveIntMod(ConstraintProto*
ct) {
1293 const LinearExpressionProto target =
ct->int_mod().target();
1294 const LinearExpressionProto expr =
ct->int_mod().exprs(0);
1295 const LinearExpressionProto mod =
ct->int_mod().exprs(1);
1297 bool domain_changed =
false;
1306 if (domain_changed) {
1315bool CpModelPresolver::ExploitEquivalenceRelations(
int c, ConstraintProto*
ct) {
1316 bool changed =
false;
1321 if (
ct->constraint_case() == ConstraintProto::kLinear) {
1322 for (
int& ref : *
ct->mutable_enforcement_literal()) {
1334 bool work_to_do =
false;
1337 if (r.representative !=
var) {
1342 if (!work_to_do)
return false;
1346 [&changed,
this](
int* ref) {
1357bool CpModelPresolver::DivideLinearByGcd(ConstraintProto*
ct) {
1362 const int num_vars =
ct->linear().vars().size();
1363 for (
int i = 0; i < num_vars; ++i) {
1364 const int64_t magnitude = std::abs(
ct->linear().coeffs(i));
1366 if (gcd == 1)
break;
1370 for (
int i = 0; i < num_vars; ++i) {
1371 ct->mutable_linear()->set_coeffs(i,
ct->linear().coeffs(i) / gcd);
1375 if (
ct->linear().domain_size() == 0) {
1376 return MarkConstraintAsFalse(
ct);
1382template <
typename ProtoWithVarsAndCoeffs>
1383bool CpModelPresolver::CanonicalizeLinearExpressionInternal(
1384 const ConstraintProto&
ct, ProtoWithVarsAndCoeffs*
proto, int64_t* offset) {
1390 int64_t sum_of_fixed_terms = 0;
1391 bool remapped =
false;
1392 const int old_size =
proto->vars().size();
1394 for (
int i = 0; i < old_size; ++i) {
1402 const int ref =
proto->vars(i);
1404 const int64_t
coeff =
1406 if (
coeff == 0)
continue;
1414 if (r.representative !=
var) {
1416 sum_of_fixed_terms +=
coeff * r.offset;
1419 new_var = r.representative;
1420 new_coeff =
coeff * r.coeff;
1425 bool removed =
false;
1426 for (
const int enf :
ct.enforcement_literal()) {
1430 sum_of_fixed_terms += new_coeff;
1439 context_->
UpdateRuleStats(
"linear: enforcement literal in expression");
1443 tmp_terms_.push_back({new_var, new_coeff});
1445 proto->clear_vars();
1446 proto->clear_coeffs();
1447 std::sort(tmp_terms_.begin(), tmp_terms_.end());
1448 int current_var = 0;
1449 int64_t current_coeff = 0;
1450 for (
const auto& entry : tmp_terms_) {
1452 if (entry.first == current_var) {
1453 current_coeff += entry.second;
1455 if (current_coeff != 0) {
1456 proto->add_vars(current_var);
1457 proto->add_coeffs(current_coeff);
1459 current_var = entry.first;
1460 current_coeff = entry.second;
1463 if (current_coeff != 0) {
1464 proto->add_vars(current_var);
1465 proto->add_coeffs(current_coeff);
1470 if (
proto->vars().size() < old_size) {
1473 *offset = sum_of_fixed_terms;
1474 return remapped ||
proto->vars().size() < old_size;
1477bool CpModelPresolver::CanonicalizeLinearExpression(
1478 const ConstraintProto&
ct, LinearExpressionProto* exp) {
1480 const bool result = CanonicalizeLinearExpressionInternal(
ct, exp, &offset);
1481 exp->set_offset(exp->offset() + offset);
1485bool CpModelPresolver::CanonicalizeLinear(ConstraintProto*
ct) {
1486 if (
ct->constraint_case() != ConstraintProto::kLinear)
return false;
1489 if (
ct->linear().domain().empty()) {
1491 return MarkConstraintAsFalse(
ct);
1496 CanonicalizeLinearExpressionInternal(*
ct,
ct->mutable_linear(), &offset);
1500 ct->mutable_linear());
1502 changed |= DivideLinearByGcd(
ct);
1506bool CpModelPresolver::RemoveSingletonInLinear(ConstraintProto*
ct) {
1507 if (
ct->constraint_case() != ConstraintProto::kLinear ||
1512 absl::btree_set<int> index_to_erase;
1513 const int num_vars =
ct->linear().vars().size();
1519 for (
int i = 0; i < num_vars; ++i) {
1520 const int var =
ct->linear().vars(i);
1521 const int64_t
coeff =
ct->linear().coeffs(i);
1525 const auto term_domain =
1527 if (!exact)
continue;
1531 if (new_rhs.NumIntervals() > 100)
continue;
1538 index_to_erase.insert(i);
1545 if (index_to_erase.empty()) {
1547 if (context_->
params().presolve_substitution_level() <= 0)
return false;
1548 if (!
ct->enforcement_literal().empty())
return false;
1552 if (rhs.Min() != rhs.Max())
return false;
1554 for (
int i = 0; i < num_vars; ++i) {
1555 const int var =
ct->linear().vars(i);
1556 const int64_t
coeff =
ct->linear().coeffs(i);
1577 if (objective_coeff %
coeff != 0)
continue;
1581 const auto term_domain =
1583 if (!exact)
continue;
1585 if (new_rhs.NumIntervals() > 100)
continue;
1593 objective_coeff))) {
1617 LOG(
WARNING) <<
"This was not supposed to happen and the presolve "
1618 "could be improved.";
1626 context_->
UpdateRuleStats(
"linear: singleton column define objective.");
1629 return RemoveConstraint(
ct);
1641 "linear: singleton column in equality and in objective.");
1643 index_to_erase.insert(i);
1647 if (index_to_erase.empty())
return false;
1658 if (!
ct->enforcement_literal().empty()) {
1659 for (
const int i : index_to_erase) {
1660 const int var =
ct->linear().vars(i);
1661 auto* l = context_->
mapping_model->add_constraints()->mutable_linear();
1675 for (
int i = 0; i < num_vars; ++i) {
1676 if (index_to_erase.count(i)) {
1680 ct->mutable_linear()->set_coeffs(new_size,
ct->linear().coeffs(i));
1681 ct->mutable_linear()->set_vars(new_size,
ct->linear().vars(i));
1684 ct->mutable_linear()->mutable_vars()->Truncate(new_size);
1685 ct->mutable_linear()->mutable_coeffs()->Truncate(new_size);
1687 DivideLinearByGcd(
ct);
1693bool CpModelPresolver::AddVarAffineRepresentativeFromLinearEquality(
1694 int target_index, ConstraintProto*
ct) {
1696 const int num_variables =
ct->linear().vars().size();
1697 for (
int i = 0; i < num_variables; ++i) {
1698 if (i == target_index)
continue;
1699 const int64_t magnitude = std::abs(
ct->linear().coeffs(i));
1701 if (gcd == 1)
return false;
1707 const int ref =
ct->linear().vars(target_index);
1708 const int64_t
coeff =
ct->linear().coeffs(target_index);
1709 const int64_t rhs =
ct->linear().domain(0);
1713 if (
coeff % gcd == 0)
return false;
1721 return CanonicalizeLinear(
ct);
1731bool CpModelPresolver::PresolveLinearEqualityWithModulo(ConstraintProto*
ct) {
1733 if (
ct->constraint_case() != ConstraintProto::kLinear)
return false;
1734 if (
ct->linear().domain().size() != 2)
return false;
1735 if (
ct->linear().domain(0) !=
ct->linear().domain(1))
return false;
1736 if (!
ct->enforcement_literal().empty())
return false;
1738 const int num_variables =
ct->linear().vars().size();
1739 if (num_variables < 2)
return false;
1741 std::vector<int> mod2_indices;
1742 std::vector<int> mod3_indices;
1743 std::vector<int> mod5_indices;
1745 int64_t min_magnitude;
1746 int num_smallest = 0;
1748 for (
int i = 0; i < num_variables; ++i) {
1749 const int64_t magnitude = std::abs(
ct->linear().coeffs(i));
1750 if (num_smallest == 0 || magnitude < min_magnitude) {
1751 min_magnitude = magnitude;
1754 }
else if (magnitude == min_magnitude) {
1758 if (magnitude % 2 != 0) mod2_indices.push_back(i);
1759 if (magnitude % 3 != 0) mod3_indices.push_back(i);
1760 if (magnitude % 5 != 0) mod5_indices.push_back(i);
1763 if (mod2_indices.size() == 2) {
1765 std::vector<int> literals;
1766 for (
const int i : mod2_indices) {
1767 const int ref =
ct->linear().vars(i);
1772 literals.push_back(ref);
1775 const int64_t rhs = std::abs(
ct->linear().domain(0));
1776 context_->
UpdateRuleStats(
"linear: only two odd Booleans in equality");
1788 if (mod2_indices.size() == 1) {
1789 return AddVarAffineRepresentativeFromLinearEquality(mod2_indices[0],
ct);
1791 if (mod3_indices.size() == 1) {
1792 return AddVarAffineRepresentativeFromLinearEquality(mod3_indices[0],
ct);
1794 if (mod5_indices.size() == 1) {
1795 return AddVarAffineRepresentativeFromLinearEquality(mod5_indices[0],
ct);
1797 if (num_smallest == 1) {
1798 return AddVarAffineRepresentativeFromLinearEquality(smallest_index,
ct);
1804bool CpModelPresolver::PresolveLinearOfSizeOne(ConstraintProto*
ct) {
1810 ?
ct->linear().coeffs(0)
1811 : -
ct->linear().coeffs(0);
1816 rhs.InverseMultiplicationBy(
coeff))) {
1819 return RemoveConstraint(
ct);
1825 const bool zero_ok = rhs.Contains(0);
1826 const bool one_ok = rhs.Contains(
ct->linear().coeffs(0));
1828 if (!zero_ok && !one_ok) {
1829 return MarkConstraintAsFalse(
ct);
1831 if (zero_ok && one_ok) {
1832 return RemoveConstraint(
ct);
1834 const int ref =
ct->linear().vars(0);
1838 ct->mutable_bool_and()->add_literals(ref);
1849 if (
ct->linear().coeffs(0) == 1 &&
1854 context_->
UpdateRuleStats(
"linear1: remove abs from abs(x) in domain");
1855 const Domain implied_abs_target_domain =
1858 .IntersectionWith(context_->
DomainOf(
ct->linear().vars(0)));
1860 if (implied_abs_target_domain.IsEmpty()) {
1861 return MarkConstraintAsFalse(
ct);
1864 const Domain new_abs_var_domain =
1865 implied_abs_target_domain
1866 .UnionWith(implied_abs_target_domain.Negation())
1867 .IntersectionWith(context_->
DomainOf(abs_arg));
1869 if (new_abs_var_domain.IsEmpty()) {
1870 return MarkConstraintAsFalse(
ct);
1875 ct->mutable_linear()->add_vars(abs_arg);
1876 ct->mutable_linear()->add_coeffs(1);
1882 if (
ct->enforcement_literal_size() != 1 ||
1883 (
ct->linear().coeffs(0) != 1 &&
ct->linear().coeffs(0) == -1)) {
1891 const int literal =
ct->enforcement_literal(0);
1892 const LinearConstraintProto& linear =
ct->linear();
1893 const int ref = linear.vars(0);
1895 const int64_t
coeff =
1898 if (linear.domain_size() == 2 && linear.domain(0) == linear.domain(1)) {
1900 : -linear.domain(0) *
coeff;
1911 if (complement.Size() != 1)
return false;
1913 : -complement.Min() *
coeff;
1927bool CpModelPresolver::PresolveLinearOfSizeTwo(ConstraintProto*
ct) {
1930 const LinearConstraintProto& arg =
ct->linear();
1931 const int var1 = arg.vars(0);
1932 const int var2 = arg.vars(1);
1933 const int64_t coeff1 = arg.coeffs(0);
1934 const int64_t coeff2 = arg.coeffs(1);
1945 const bool is_equality =
1946 arg.domain_size() == 2 && arg.domain(0) == arg.domain(1);
1949 int64_t value_on_true,
coeff;
1952 value_on_true = coeff1;
1957 value_on_true = coeff2;
1965 const Domain rhs_if_true =
1966 rhs.AdditionWith(Domain(-value_on_true)).InverseMultiplicationBy(
coeff);
1967 const Domain rhs_if_false = rhs.InverseMultiplicationBy(
coeff);
1968 const bool implied_false =
1970 const bool implied_true =
1972 if (implied_true && implied_false) {
1974 return MarkConstraintAsFalse(
ct);
1975 }
else if (implied_true) {
1976 context_->
UpdateRuleStats(
"linear2: Boolean with one feasible value.");
1979 ConstraintProto* new_ct = context_->
working_model->add_constraints();
1980 *new_ct->mutable_enforcement_literal() =
ct->enforcement_literal();
1981 new_ct->mutable_bool_and()->add_literals(lit);
1985 ct->mutable_linear()->Clear();
1986 ct->mutable_linear()->add_vars(
var);
1987 ct->mutable_linear()->add_coeffs(1);
1989 return PresolveLinearOfSizeOne(
ct) ||
true;
1990 }
else if (implied_false) {
1991 context_->
UpdateRuleStats(
"linear2: Boolean with one feasible value.");
1994 ConstraintProto* new_ct = context_->
working_model->add_constraints();
1995 *new_ct->mutable_enforcement_literal() =
ct->enforcement_literal();
1996 new_ct->mutable_bool_and()->add_literals(
NegatedRef(lit));
2000 ct->mutable_linear()->Clear();
2001 ct->mutable_linear()->add_vars(
var);
2002 ct->mutable_linear()->add_coeffs(1);
2004 return PresolveLinearOfSizeOne(
ct) ||
true;
2016 const int64_t rhs = arg.domain(0);
2017 if (
ct->enforcement_literal().empty()) {
2025 }
else if (coeff2 == 1) {
2027 }
else if (coeff1 == -1) {
2029 }
else if (coeff2 == -1) {
2042 if (added)
return RemoveConstraint(
ct);
2052 "linear2: implied ax + by = cte has no solutions");
2053 return MarkConstraintAsFalse(
ct);
2055 const Domain reduced_domain =
2061 .InverseMultiplicationBy(-
a));
2063 if (reduced_domain.IsEmpty()) {
2065 "linear2: implied ax + by = cte has no solutions");
2066 return MarkConstraintAsFalse(
ct);
2069 if (reduced_domain.Size() == 1) {
2070 const int64_t z = reduced_domain.FixedValue();
2071 const int64_t value1 = x0 +
b * z;
2072 const int64_t value2 = y0 -
a * z;
2076 DCHECK_EQ(coeff1 * value1 + coeff2 * value2, rhs);
2078 ConstraintProto* imply1 = context_->
working_model->add_constraints();
2079 *imply1->mutable_enforcement_literal() =
ct->enforcement_literal();
2080 imply1->mutable_linear()->add_vars(var1);
2081 imply1->mutable_linear()->add_coeffs(1);
2082 imply1->mutable_linear()->add_domain(value1);
2083 imply1->mutable_linear()->add_domain(value1);
2085 ConstraintProto* imply2 = context_->
working_model->add_constraints();
2086 *imply2->mutable_enforcement_literal() =
ct->enforcement_literal();
2087 imply2->mutable_linear()->add_vars(var2);
2088 imply2->mutable_linear()->add_coeffs(1);
2089 imply2->mutable_linear()->add_domain(value2);
2090 imply2->mutable_linear()->add_domain(value2);
2092 "linear2: implied ax + by = cte has only one solution");
2094 return RemoveConstraint(
ct);
2101bool CpModelPresolver::PresolveSmallLinear(ConstraintProto*
ct) {
2102 if (
ct->constraint_case() != ConstraintProto::kLinear)
return false;
2105 if (
ct->linear().vars().empty()) {
2108 if (rhs.Contains(0)) {
2109 return RemoveConstraint(
ct);
2111 return MarkConstraintAsFalse(
ct);
2113 }
else if (
ct->linear().vars().size() == 1) {
2114 return PresolveLinearOfSizeOne(
ct);
2115 }
else if (
ct->linear().vars().size() == 2) {
2116 return PresolveLinearOfSizeTwo(
ct);
2123void CpModelPresolver::TryToReduceCoefficientsOfLinearConstraint(
2124 int c, ConstraintProto*
ct) {
2125 if (
ct->constraint_case() != ConstraintProto::kLinear)
return;
2129 const LinearConstraintProto& lin =
ct->linear();
2131 if (rhs.NumIntervals() != 1)
return;
2138 std::vector<int64_t> coeffs;
2139 std::vector<int64_t> lbs;
2140 std::vector<int64_t> ubs;
2142 const int num_terms = lin.vars().size();
2143 for (
int i = 0; i < num_terms; ++i) {
2144 const int64_t
coeff = lin.coeffs(i);
2146 coeffs.push_back(
coeff);
2147 lbs.push_back(context_->
MinOf(lin.vars(i)));
2148 ubs.push_back(context_->
MaxOf(lin.vars(i)));
2150 coeffs.push_back(-
coeff);
2151 lbs.push_back(-context_->
MaxOf(lin.vars(i)));
2152 ubs.push_back(-context_->
MinOf(lin.vars(i)));
2154 const int64_t magnitude = std::abs(lin.coeffs(i));
2156 if (magnitude >= 10) {
2157 base =
std::min(base, std::abs(lin.coeffs(i)));
2164 if (DivideLinearByGcd(
ct)) {
2174 rhs.Max(), &new_ub)) {
2179 int64_t minus_new_lb;
2180 for (
int i = 0; i < num_terms; ++i) {
2186 base, coeffs, lbs, ubs, -rhs.Min(), &minus_new_lb)) {
2193 LinearConstraintProto* mutable_linear =
ct->mutable_linear();
2194 for (
int i = 0; i < num_terms; ++i) {
2195 const int64_t new_coeff =
ClosestMultiple(lin.coeffs(i), base) / base;
2196 if (new_coeff != 0) {
2197 mutable_linear->set_vars(new_size, lin.vars(i));
2198 mutable_linear->set_coeffs(new_size, new_coeff);
2202 mutable_linear->mutable_vars()->Truncate(new_size);
2203 mutable_linear->mutable_coeffs()->Truncate(new_size);
2211bool IsLeConstraint(
const Domain& domain,
const Domain& all_values) {
2215 .IsIncludedIn(domain);
2219bool IsGeConstraint(
const Domain& domain,
const Domain& all_values) {
2223 .IsIncludedIn(domain);
2229bool RhsCanBeFixedToMin(int64_t
coeff,
const Domain& var_domain,
2230 const Domain& terms,
const Domain& rhs) {
2231 if (var_domain.NumIntervals() != 1)
return false;
2232 if (std::abs(
coeff) != 1)
return false;
2240 if (
coeff == 1 && terms.Max() + var_domain.Min() <= rhs.Min()) {
2243 if (
coeff == -1 && terms.Max() - var_domain.Max() <= rhs.Min()) {
2249bool RhsCanBeFixedToMax(int64_t
coeff,
const Domain& var_domain,
2250 const Domain& terms,
const Domain& rhs) {
2251 if (var_domain.NumIntervals() != 1)
return false;
2252 if (std::abs(
coeff) != 1)
return false;
2254 if (
coeff == 1 && terms.Min() + var_domain.Max() >= rhs.Max()) {
2257 if (
coeff == -1 && terms.Min() - var_domain.Min() >= rhs.Max()) {
2264void TakeIntersectionWith(
const absl::flat_hash_set<int>& current,
2265 absl::flat_hash_set<int>* to_clear) {
2266 std::vector<int> new_set;
2267 for (
const int c : *to_clear) {
2268 if (current.contains(c)) new_set.push_back(c);
2271 for (
const int c : new_set) to_clear->insert(c);
2276bool CpModelPresolver::DetectAndProcessOneSidedLinearConstraint(
2277 int c, ConstraintProto*
ct) {
2278 if (
ct->constraint_case() != ConstraintProto::kLinear)
return false;
2284 Domain implied_rhs(0);
2285 const int num_vars =
ct->linear().vars().size();
2286 for (
int i = 0; i < num_vars; ++i) {
2287 const int ref =
ct->linear().vars(i);
2288 const int64_t
coeff =
ct->linear().coeffs(i);
2292 .RelaxIfTooComplex();
2297 if (implied_rhs.IsIncludedIn(old_rhs)) {
2299 return RemoveConstraint(
ct);
2303 const Domain rhs = old_rhs.SimplifyUsingImpliedDomain(implied_rhs);
2304 if (rhs.IsEmpty()) {
2306 return MarkConstraintAsFalse(
ct);
2308 if (rhs != old_rhs) {
2316 const bool is_le_constraint = IsLeConstraint(rhs, implied_rhs);
2317 const bool is_ge_constraint = IsGeConstraint(rhs, implied_rhs);
2318 if (!is_le_constraint && !is_ge_constraint)
return false;
2319 CHECK_NE(is_le_constraint, is_ge_constraint);
2326 absl::flat_hash_set<int> enforcement_set;
2328 for (
const int ref :
ct->enforcement_literal()) {
2329 enforcement_set.insert(ref);
2333 bool recanonicalize =
false;
2334 for (
int i = 0; i < num_vars; ++i) {
2335 const int var =
ct->linear().vars(i);
2336 const int64_t var_coeff =
ct->linear().coeffs(i);
2339 if ((var_coeff > 0) == is_ge_constraint) {
2353 const int64_t obj_coeff =
2362 if (obj_coeff <= 0 &&
2371 recanonicalize =
true;
2376 if (obj_coeff >= 0 &&
2385 recanonicalize =
true;
2391 if (recanonicalize)
return CanonicalizeLinear(
ct);
2395bool CpModelPresolver::PropagateDomainsInLinear(
int ct_index,
2396 ConstraintProto*
ct) {
2397 if (
ct->constraint_case() != ConstraintProto::kLinear)
return false;
2403 const int num_vars =
ct->linear().vars_size();
2404 term_domains.resize(num_vars + 1);
2405 left_domains.resize(num_vars + 1);
2406 left_domains[0] = Domain(0);
2407 for (
int i = 0; i < num_vars; ++i) {
2408 const int var =
ct->linear().vars(i);
2409 const int64_t
coeff =
ct->linear().coeffs(i);
2412 left_domains[i + 1] =
2415 const Domain& implied_rhs = left_domains[num_vars];
2419 if (implied_rhs.IsIncludedIn(old_rhs)) {
2421 return RemoveConstraint(
ct);
2425 Domain rhs = old_rhs.SimplifyUsingImpliedDomain(implied_rhs);
2426 if (rhs.IsEmpty()) {
2428 return MarkConstraintAsFalse(
ct);
2430 if (rhs != old_rhs) {
2436 if (
ct->enforcement_literal().size() > 1)
return false;
2438 bool new_bounds =
false;
2439 bool recanonicalize =
false;
2440 Domain negated_rhs = rhs.Negation();
2441 Domain right_domain(0);
2443 Domain implied_term_domain;
2444 term_domains[num_vars] = Domain(0);
2445 for (
int i = num_vars - 1; i >= 0; --i) {
2446 const int var =
ct->linear().vars(i);
2447 const int64_t var_coeff =
ct->linear().coeffs(i);
2449 right_domain.AdditionWith(term_domains[i + 1]).RelaxIfTooComplex();
2450 implied_term_domain = left_domains[i].AdditionWith(right_domain);
2451 new_domain = implied_term_domain.AdditionWith(negated_rhs)
2452 .InverseMultiplicationBy(-var_coeff);
2454 if (
ct->enforcement_literal().empty()) {
2459 }
else if (
ct->enforcement_literal().size() == 1) {
2470 recanonicalize =
true;
2475 if (!
ct->enforcement_literal().empty())
continue;
2487 if (rhs.Min() != rhs.Max() &&
2490 const bool same_sign = (var_coeff > 0) == (obj_coeff > 0);
2492 if (same_sign && RhsCanBeFixedToMin(var_coeff, context_->
DomainOf(
var),
2493 implied_term_domain, rhs)) {
2494 rhs = Domain(rhs.Min());
2497 if (!same_sign && RhsCanBeFixedToMax(var_coeff, context_->
DomainOf(
var),
2498 implied_term_domain, rhs)) {
2499 rhs = Domain(rhs.Max());
2505 negated_rhs = rhs.Negation();
2509 right_domain = Domain(0);
2523 if (
ct->linear().vars().size() <= 2)
continue;
2528 if (rhs.Min() != rhs.Max())
continue;
2534 if (context_->
DomainOf(
var) != new_domain)
continue;
2535 if (std::abs(var_coeff) != 1)
continue;
2536 if (context_->
params().presolve_substitution_level() <= 0)
continue;
2542 bool is_in_objective =
false;
2544 is_in_objective =
true;
2550 if (is_in_objective) col_size--;
2551 const int row_size =
ct->linear().vars_size();
2555 const int num_entries_added = (row_size - 1) * (col_size - 1);
2556 const int num_entries_removed = col_size + row_size - 1;
2558 if (num_entries_added > num_entries_removed) {
2564 std::vector<int> others;
2572 if (c == ct_index)
continue;
2573 if (context_->
working_model->constraints(c).constraint_case() !=
2574 ConstraintProto::kLinear) {
2578 for (
const int ref :
2579 context_->
working_model->constraints(c).enforcement_literal()) {
2585 others.push_back(c);
2587 if (abort)
continue;
2590 for (
const int c : others) {
2600 CanonicalizeLinear(context_->
working_model->mutable_constraints(c));
2609 if (is_in_objective &&
2615 absl::StrCat(
"linear: variable substitution ", others.size()));
2626 const int ct_index = context_->
mapping_model->constraints().size();
2628 LinearConstraintProto* mapping_linear_ct =
2631 std::swap(mapping_linear_ct->mutable_vars()->at(0),
2632 mapping_linear_ct->mutable_vars()->at(i));
2633 std::swap(mapping_linear_ct->mutable_coeffs()->at(0),
2634 mapping_linear_ct->mutable_coeffs()->at(i));
2635 return RemoveConstraint(
ct);
2640 if (recanonicalize)
return CanonicalizeLinear(
ct);
2651void CpModelPresolver::ExtractEnforcementLiteralFromLinearConstraint(
2652 int ct_index, ConstraintProto*
ct) {
2653 if (
ct->constraint_case() != ConstraintProto::kLinear)
return;
2656 const LinearConstraintProto& arg =
ct->linear();
2657 const int num_vars = arg.vars_size();
2661 if (num_vars <= 1)
return;
2663 int64_t min_sum = 0;
2664 int64_t max_sum = 0;
2665 int64_t max_coeff_magnitude = 0;
2666 for (
int i = 0; i < num_vars; ++i) {
2667 const int ref = arg.vars(i);
2668 const int64_t
coeff = arg.coeffs(i);
2669 const int64_t term_a =
coeff * context_->
MinOf(ref);
2670 const int64_t term_b =
coeff * context_->
MaxOf(ref);
2671 max_coeff_magnitude =
std::max(max_coeff_magnitude, std::abs(
coeff));
2672 min_sum +=
std::min(term_a, term_b);
2673 max_sum +=
std::max(term_a, term_b);
2682 const auto& domain =
ct->linear().domain();
2683 const int64_t ub_threshold = domain[domain.size() - 2] - min_sum;
2684 const int64_t lb_threshold = max_sum - domain[1];
2686 if (max_coeff_magnitude <
std::max(ub_threshold, lb_threshold))
return;
2705 const bool lower_bounded = min_sum < rhs_domain.Min();
2706 const bool upper_bounded = max_sum > rhs_domain.Max();
2707 if (!lower_bounded && !upper_bounded)
return;
2708 if (lower_bounded && upper_bounded) {
2710 ConstraintProto* new_ct1 = context_->
working_model->add_constraints();
2712 if (!
ct->name().empty()) {
2713 new_ct1->set_name(absl::StrCat(
ct->name(),
" (part 1)"));
2716 new_ct1->mutable_linear());
2718 ConstraintProto* new_ct2 = context_->
working_model->add_constraints();
2720 if (!
ct->name().empty()) {
2721 new_ct2->set_name(absl::StrCat(
ct->name(),
" (part 2)"));
2724 new_ct2->mutable_linear());
2735 const int64_t threshold = lower_bounded ? ub_threshold : lb_threshold;
2742 const bool only_booleans =
2743 !context_->
params().presolve_extract_integer_enforcement() ||
2749 int64_t rhs_offset = 0;
2750 bool some_integer_encoding_were_extracted =
false;
2751 LinearConstraintProto* mutable_arg =
ct->mutable_linear();
2752 for (
int i = 0; i < arg.vars_size(); ++i) {
2753 int ref = arg.vars(i);
2754 int64_t
coeff = arg.coeffs(i);
2762 (only_booleans && !is_boolean)) {
2764 mutable_arg->set_vars(new_size, mutable_arg->vars(i));
2765 mutable_arg->set_coeffs(new_size, mutable_arg->coeffs(i));
2773 some_integer_encoding_were_extracted =
true;
2775 "linear: extracted integer enforcement literal");
2777 if (lower_bounded) {
2778 ct->add_enforcement_literal(is_boolean
2781 ref, context_->
MinOf(ref)));
2784 ct->add_enforcement_literal(is_boolean
2787 ref, context_->
MaxOf(ref)));
2791 mutable_arg->mutable_vars()->Truncate(new_size);
2792 mutable_arg->mutable_coeffs()->Truncate(new_size);
2794 if (some_integer_encoding_were_extracted) {
2800void CpModelPresolver::ExtractAtMostOneFromLinear(ConstraintProto*
ct) {
2805 const LinearConstraintProto& arg =
ct->linear();
2806 const int num_vars = arg.vars_size();
2807 int64_t min_sum = 0;
2808 int64_t max_sum = 0;
2809 for (
int i = 0; i < num_vars; ++i) {
2810 const int ref = arg.vars(i);
2811 const int64_t
coeff = arg.coeffs(i);
2812 const int64_t term_a =
coeff * context_->
MinOf(ref);
2813 const int64_t term_b =
coeff * context_->
MaxOf(ref);
2814 min_sum +=
std::min(term_a, term_b);
2815 max_sum +=
std::max(term_a, term_b);
2817 for (
const int type : {0, 1}) {
2818 std::vector<int> at_most_one;
2819 for (
int i = 0; i < num_vars; ++i) {
2820 const int ref = arg.vars(i);
2821 const int64_t
coeff = arg.coeffs(i);
2822 if (context_->
MinOf(ref) != 0)
continue;
2823 if (context_->
MaxOf(ref) != 1)
continue;
2828 if (min_sum + 2 * std::abs(
coeff) > rhs.Max()) {
2832 if (max_sum - 2 * std::abs(
coeff) < rhs.Min()) {
2837 if (at_most_one.size() > 1) {
2843 ConstraintProto* new_ct = context_->
working_model->add_constraints();
2844 new_ct->set_name(
ct->name());
2845 for (
const int ref : at_most_one) {
2846 new_ct->mutable_at_most_one()->add_literals(ref);
2855bool CpModelPresolver::PresolveLinearOnBooleans(ConstraintProto*
ct) {
2856 if (
ct->constraint_case() != ConstraintProto::kLinear)
return false;
2859 const LinearConstraintProto& arg =
ct->linear();
2860 const int num_vars = arg.vars_size();
2862 int64_t max_coeff = 0;
2863 int64_t min_sum = 0;
2864 int64_t max_sum = 0;
2865 for (
int i = 0; i < num_vars; ++i) {
2867 const int var = arg.vars(i);
2868 const int64_t
coeff = arg.coeffs(i);
2871 if (context_->
MinOf(
var) != 0)
return false;
2872 if (context_->
MaxOf(
var) != 1)
return false;
2894 if ((!rhs_domain.Contains(min_sum) &&
2895 min_sum + min_coeff > rhs_domain.Max()) ||
2896 (!rhs_domain.Contains(max_sum) &&
2897 max_sum - min_coeff < rhs_domain.Min())) {
2898 context_->
UpdateRuleStats(
"linear: all booleans and trivially false");
2899 return MarkConstraintAsFalse(
ct);
2901 if (Domain(min_sum, max_sum).IsIncludedIn(rhs_domain)) {
2903 return RemoveConstraint(
ct);
2910 DCHECK(!rhs_domain.IsEmpty());
2911 if (min_sum + min_coeff > rhs_domain.Max()) {
2914 const auto copy = arg;
2915 ct->mutable_bool_and()->clear_literals();
2916 for (
int i = 0; i < num_vars; ++i) {
2917 ct->mutable_bool_and()->add_literals(
2918 copy.coeffs(i) > 0 ?
NegatedRef(copy.vars(i)) : copy.vars(i));
2920 PresolveBoolAnd(
ct);
2922 }
else if (max_sum - min_coeff < rhs_domain.Min()) {
2925 const auto copy = arg;
2926 ct->mutable_bool_and()->clear_literals();
2927 for (
int i = 0; i < num_vars; ++i) {
2928 ct->mutable_bool_and()->add_literals(
2929 copy.coeffs(i) > 0 ? copy.vars(i) :
NegatedRef(copy.vars(i)));
2931 PresolveBoolAnd(
ct);
2933 }
else if (min_sum + min_coeff >= rhs_domain.Min() &&
2934 rhs_domain.front().end >= max_sum) {
2937 const auto copy = arg;
2938 ct->mutable_bool_or()->clear_literals();
2939 for (
int i = 0; i < num_vars; ++i) {
2940 ct->mutable_bool_or()->add_literals(
2941 copy.coeffs(i) > 0 ? copy.vars(i) :
NegatedRef(copy.vars(i)));
2945 }
else if (max_sum - min_coeff <= rhs_domain.Max() &&
2946 rhs_domain.back().start <= min_sum) {
2949 const auto copy = arg;
2950 ct->mutable_bool_or()->clear_literals();
2951 for (
int i = 0; i < num_vars; ++i) {
2952 ct->mutable_bool_or()->add_literals(
2953 copy.coeffs(i) > 0 ?
NegatedRef(copy.vars(i)) : copy.vars(i));
2958 min_sum + max_coeff <= rhs_domain.Max() &&
2959 min_sum + 2 * min_coeff > rhs_domain.Max() &&
2960 rhs_domain.back().start <= min_sum) {
2963 const auto copy = arg;
2964 ct->mutable_at_most_one()->clear_literals();
2965 for (
int i = 0; i < num_vars; ++i) {
2966 ct->mutable_at_most_one()->add_literals(
2967 copy.coeffs(i) > 0 ? copy.vars(i) :
NegatedRef(copy.vars(i)));
2971 max_sum - max_coeff >= rhs_domain.Min() &&
2972 max_sum - 2 * min_coeff < rhs_domain.Min() &&
2973 rhs_domain.front().end >= max_sum) {
2976 const auto copy = arg;
2977 ct->mutable_at_most_one()->clear_literals();
2978 for (
int i = 0; i < num_vars; ++i) {
2979 ct->mutable_at_most_one()->add_literals(
2980 copy.coeffs(i) > 0 ?
NegatedRef(copy.vars(i)) : copy.vars(i));
2984 min_sum < rhs_domain.Min() &&
2985 min_sum + min_coeff >= rhs_domain.Min() &&
2986 min_sum + 2 * min_coeff > rhs_domain.Max() &&
2987 min_sum + max_coeff <= rhs_domain.Max()) {
2989 ConstraintProto* exactly_one = context_->
working_model->add_constraints();
2990 exactly_one->set_name(
ct->name());
2991 for (
int i = 0; i < num_vars; ++i) {
2992 exactly_one->mutable_exactly_one()->add_literals(
2993 arg.coeffs(i) > 0 ? arg.vars(i) :
NegatedRef(arg.vars(i)));
2996 return RemoveConstraint(
ct);
2998 max_sum > rhs_domain.Max() &&
2999 max_sum - min_coeff <= rhs_domain.Max() &&
3000 max_sum - 2 * min_coeff < rhs_domain.Min() &&
3001 max_sum - max_coeff >= rhs_domain.Min()) {
3003 ConstraintProto* exactly_one = context_->
working_model->add_constraints();
3004 exactly_one->set_name(
ct->name());
3005 for (
int i = 0; i < num_vars; ++i) {
3006 exactly_one->mutable_exactly_one()->add_literals(
3007 arg.coeffs(i) > 0 ?
NegatedRef(arg.vars(i)) : arg.vars(i));
3010 return RemoveConstraint(
ct);
3017 if (num_vars > 3)
return false;
3022 const int max_mask = (1 << arg.vars_size());
3023 for (
int mask = 0; mask < max_mask; ++mask) {
3025 for (
int i = 0; i < num_vars; ++i) {
3026 if ((mask >> i) & 1)
value += arg.coeffs(i);
3028 if (rhs_domain.Contains(
value))
continue;
3031 ConstraintProto* new_ct = context_->
working_model->add_constraints();
3032 auto* new_arg = new_ct->mutable_bool_or();
3034 *new_ct->mutable_enforcement_literal() =
ct->enforcement_literal();
3036 for (
int i = 0; i < num_vars; ++i) {
3037 new_arg->add_literals(((mask >> i) & 1) ?
NegatedRef(arg.vars(i))
3043 return RemoveConstraint(
ct);
3046bool CpModelPresolver::PresolveInterval(
int c, ConstraintProto*
ct) {
3048 IntervalConstraintProto*
interval =
ct->mutable_interval();
3051 if (!
ct->enforcement_literal().empty() && context_->
SizeMax(c) < 0) {
3052 context_->
UpdateRuleStats(
"interval: negative size implies unperformed");
3053 return MarkConstraintAsFalse(
ct);
3056 bool changed =
false;
3057 if (
ct->enforcement_literal().empty()) {
3065 "interval: performed intervals must have a positive size");
3068 changed |= CanonicalizeLinearExpression(*
ct,
interval->mutable_start());
3069 changed |= CanonicalizeLinearExpression(*
ct,
interval->mutable_size());
3070 changed |= CanonicalizeLinearExpression(*
ct,
interval->mutable_end());
3075bool CpModelPresolver::PresolveInverse(ConstraintProto*
ct) {
3076 const int size =
ct->inverse().f_direct().size();
3077 bool changed =
false;
3080 for (
const int ref :
ct->inverse().f_direct()) {
3082 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
3086 for (
const int ref :
ct->inverse().f_inverse()) {
3088 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
3098 absl::flat_hash_set<int> direct_vars;
3099 for (
const int ref :
ct->inverse().f_direct()) {
3100 const auto [it, inserted] = direct_vars.insert(
PositiveRef(ref));
3106 absl::flat_hash_set<int> inverse_vars;
3107 for (
const int ref :
ct->inverse().f_inverse()) {
3108 const auto [it, inserted] = inverse_vars.insert(
PositiveRef(ref));
3118 const auto filter_inverse_domain =
3119 [
this, size, &changed](
const auto& direct,
const auto& inverse) {
3121 std::vector<absl::flat_hash_set<int64_t>> inverse_values(size);
3122 for (
int i = 0; i < size; ++i) {
3123 const Domain domain = context_->
DomainOf(inverse[i]);
3124 for (
const int64_t j : domain.Values()) {
3125 inverse_values[i].insert(j);
3132 std::vector<int64_t> possible_values;
3133 for (
int i = 0; i < size; ++i) {
3134 possible_values.clear();
3135 const Domain domain = context_->
DomainOf(direct[i]);
3136 bool removed_value =
false;
3137 for (
const int64_t j : domain.Values()) {
3138 if (inverse_values[j].contains(i)) {
3139 possible_values.push_back(j);
3141 removed_value =
true;
3144 if (removed_value) {
3148 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
3156 if (!filter_inverse_domain(
ct->inverse().f_direct(),
3157 ct->inverse().f_inverse())) {
3161 if (!filter_inverse_domain(
ct->inverse().f_inverse(),
3162 ct->inverse().f_direct())) {
3173bool CpModelPresolver::PresolveElement(ConstraintProto*
ct) {
3176 if (
ct->element().vars().empty()) {
3181 const int index_ref =
ct->element().index();
3182 const int target_ref =
ct->element().target();
3187 bool all_constants =
true;
3188 absl::flat_hash_set<int64_t> constant_set;
3189 bool all_included_in_target_domain =
true;
3192 bool reduced_index_domain =
false;
3194 Domain(0,
ct->element().vars_size() - 1),
3195 &reduced_index_domain)) {
3203 std::vector<int64_t> possible_indices;
3204 const Domain& index_domain = context_->
DomainOf(index_ref);
3205 for (
const int64_t index_value : index_domain.Values()) {
3206 const int ref =
ct->element().vars(index_value);
3207 const int64_t target_value =
3208 target_ref == index_ref ? index_value : -index_value;
3210 possible_indices.push_back(target_value);
3213 if (possible_indices.size() < index_domain.Size()) {
3219 "element: reduced index domain when target equals index");
3225 Domain infered_domain;
3226 const Domain& initial_index_domain = context_->
DomainOf(index_ref);
3227 const Domain& target_domain = context_->
DomainOf(target_ref);
3228 std::vector<int64_t> possible_indices;
3229 for (
const int64_t
value : initial_index_domain.Values()) {
3232 const int ref =
ct->element().vars(
value);
3233 const Domain& domain = context_->
DomainOf(ref);
3234 if (domain.IntersectionWith(target_domain).IsEmpty())
continue;
3235 possible_indices.push_back(
value);
3236 if (domain.IsFixed()) {
3237 constant_set.insert(domain.Min());
3239 all_constants =
false;
3241 if (!domain.IsIncludedIn(target_domain)) {
3242 all_included_in_target_domain =
false;
3244 infered_domain = infered_domain.
UnionWith(domain);
3246 if (possible_indices.size() < initial_index_domain.Size()) {
3253 bool domain_modified =
false;
3255 &domain_modified)) {
3258 if (domain_modified) {
3264 if (context_->
IsFixed(index_ref)) {
3265 const int var =
ct->element().vars(context_->
MinOf(index_ref));
3266 if (
var != target_ref) {
3267 LinearConstraintProto*
const lin =
3268 context_->
working_model->add_constraints()->mutable_linear();
3270 lin->add_coeffs(-1);
3271 lin->add_vars(target_ref);
3278 return RemoveConstraint(
ct);
3284 if (all_constants && constant_set.size() == 1) {
3287 return RemoveConstraint(
ct);
3292 if (context_->
MinOf(index_ref) == 0 && context_->
MaxOf(index_ref) == 1 &&
3294 const int64_t v0 = context_->
MinOf(
ct->element().vars(0));
3295 const int64_t v1 = context_->
MinOf(
ct->element().vars(1));
3297 LinearConstraintProto*
const lin =
3298 context_->
working_model->add_constraints()->mutable_linear();
3299 lin->add_vars(target_ref);
3301 lin->add_vars(index_ref);
3302 lin->add_coeffs(v0 - v1);
3303 lin->add_domain(v0);
3304 lin->add_domain(v0);
3306 context_->
UpdateRuleStats(
"element: linearize constant element of size 2");
3307 return RemoveConstraint(
ct);
3311 const AffineRelation::Relation r_index =
3313 if (r_index.representative != index_ref) {
3315 if (context_->
DomainOf(r_index.representative).
Size() >
3321 const int64_t r_min = context_->
MinOf(r_ref);
3322 const int64_t r_max = context_->
MaxOf(r_ref);
3323 const int array_size =
ct->element().vars_size();
3325 context_->
UpdateRuleStats(
"TODO element: representative has bad domain");
3326 }
else if (r_index.offset >= 0 && r_index.offset < array_size &&
3327 r_index.offset + r_max * r_index.coeff >= 0 &&
3328 r_index.offset + r_max * r_index.coeff < array_size) {
3330 ElementConstraintProto*
const element =
3331 context_->
working_model->add_constraints()->mutable_element();
3332 for (int64_t v = 0; v <= r_max; ++v) {
3333 const int64_t scaled_index = v * r_index.coeff + r_index.offset;
3335 CHECK_LT(scaled_index, array_size);
3336 element->add_vars(
ct->element().vars(scaled_index));
3338 element->set_index(r_ref);
3339 element->set_target(target_ref);
3341 if (r_index.coeff == 1) {
3347 return RemoveConstraint(
ct);
3358 absl::flat_hash_map<int, int> local_var_occurrence_counter;
3359 local_var_occurrence_counter[
PositiveRef(index_ref)]++;
3360 local_var_occurrence_counter[
PositiveRef(target_ref)]++;
3366 const int ref =
ct->element().vars(
value);
3372 local_var_occurrence_counter.at(
PositiveRef(index_ref)) == 1) {
3373 if (all_constants) {
3377 context_->
UpdateRuleStats(
"element: trivial target domain reduction");
3380 return RemoveConstraint(
ct);
3386 if (!context_->
IsFixed(target_ref) &&
3388 local_var_occurrence_counter.at(
PositiveRef(target_ref)) == 1) {
3389 if (all_included_in_target_domain) {
3393 return RemoveConstraint(
ct);
3402bool CpModelPresolver::PresolveTable(ConstraintProto*
ct) {
3405 if (
ct->table().vars().empty()) {
3407 return RemoveConstraint(
ct);
3410 const int initial_num_vars =
ct->table().vars_size();
3411 bool changed =
true;
3414 std::vector<AffineRelation::Relation> affine_relations;
3415 std::vector<int64_t> old_var_lb;
3416 std::vector<int64_t> old_var_ub;
3418 for (
int v = 0; v < initial_num_vars; ++v) {
3419 const int ref =
ct->table().vars(v);
3421 affine_relations.push_back(r);
3422 old_var_lb.push_back(context_->
MinOf(ref));
3423 old_var_ub.push_back(context_->
MaxOf(ref));
3424 if (r.representative != ref) {
3426 ct->mutable_table()->set_vars(v, r.representative);
3428 "table: replace variable by canonical affine one");
3437 std::vector<int> old_index_of_duplicate_to_new_index_of_first_occurrence(
3438 initial_num_vars, -1);
3440 std::vector<int> old_index_to_new_index(initial_num_vars, -1);
3443 absl::flat_hash_map<int, int> first_visit;
3444 for (
int p = 0; p < initial_num_vars; ++p) {
3445 const int ref =
ct->table().vars(p);
3447 const auto& it = first_visit.find(
var);
3448 if (it != first_visit.end()) {
3449 const int previous = it->second;
3450 old_index_of_duplicate_to_new_index_of_first_occurrence[p] = previous;
3454 ct->mutable_table()->set_vars(num_vars, ref);
3455 first_visit[
var] = num_vars;
3456 old_index_to_new_index[p] = num_vars;
3461 if (num_vars < initial_num_vars) {
3462 ct->mutable_table()->mutable_vars()->Truncate(num_vars);
3469 std::vector<std::vector<int64_t>> new_tuples;
3470 const int initial_num_tuples =
ct->table().values_size() / initial_num_vars;
3471 std::vector<absl::flat_hash_set<int64_t>> new_domains(num_vars);
3474 std::vector<int64_t> tuple(num_vars);
3475 new_tuples.reserve(initial_num_tuples);
3476 for (
int i = 0; i < initial_num_tuples; ++i) {
3477 bool delete_row =
false;
3479 for (
int j = 0; j < initial_num_vars; ++j) {
3480 const int64_t old_value =
ct->table().values(i * initial_num_vars + j);
3484 if (old_value < old_var_lb[j] || old_value > old_var_ub[j]) {
3490 const AffineRelation::Relation& r = affine_relations[j];
3491 const int64_t
value = (old_value - r.offset) / r.coeff;
3492 if (
value * r.coeff + r.offset != old_value) {
3497 const int mapped_position = old_index_to_new_index[j];
3498 if (mapped_position == -1) {
3499 const int new_index_of_first_occurrence =
3500 old_index_of_duplicate_to_new_index_of_first_occurrence[j];
3501 if (
value != tuple[new_index_of_first_occurrence]) {
3506 const int ref =
ct->table().vars(mapped_position);
3511 tuple[mapped_position] =
value;
3518 new_tuples.push_back(tuple);
3519 for (
int j = 0; j < num_vars; ++j) {
3520 new_domains[j].insert(tuple[j]);
3524 if (new_tuples.size() < initial_num_tuples) {
3531 ct->mutable_table()->clear_values();
3532 for (
const std::vector<int64_t>& t : new_tuples) {
3533 for (
const int64_t v : t) {
3534 ct->mutable_table()->add_values(v);
3540 if (
ct->table().negated())
return changed;
3543 for (
int j = 0; j < num_vars; ++j) {
3544 const int ref =
ct->table().vars(j);
3548 new_domains[j].
end())),
3556 if (num_vars == 1) {
3559 return RemoveConstraint(
ct);
3564 for (
int j = 0; j < num_vars; ++j) prod *= new_domains[j].size();
3565 if (prod == new_tuples.size()) {
3567 return RemoveConstraint(
ct);
3573 if (new_tuples.size() > 0.7 * prod) {
3575 std::vector<std::vector<int64_t>> var_to_values(num_vars);
3576 for (
int j = 0; j < num_vars; ++j) {
3577 var_to_values[j].assign(new_domains[j].begin(), new_domains[j].
end());
3579 std::vector<std::vector<int64_t>> all_tuples(prod);
3580 for (
int i = 0; i < prod; ++i) {
3581 all_tuples[i].resize(num_vars);
3583 for (
int j = 0; j < num_vars; ++j) {
3584 all_tuples[i][j] = var_to_values[j][
index % var_to_values[j].size()];
3585 index /= var_to_values[j].size();
3591 std::vector<std::vector<int64_t>> diff(prod - new_tuples.size());
3592 std::set_difference(all_tuples.begin(), all_tuples.end(),
3593 new_tuples.begin(), new_tuples.end(), diff.begin());
3596 ct->mutable_table()->set_negated(!
ct->table().negated());
3597 ct->mutable_table()->clear_values();
3598 for (
const std::vector<int64_t>& t : diff) {
3599 for (
const int64_t v : t)
ct->mutable_table()->add_values(v);
3606bool CpModelPresolver::PresolveAllDiff(ConstraintProto*
ct) {
3610 AllDifferentConstraintProto& all_diff = *
ct->mutable_all_diff();
3612 bool constraint_has_changed =
false;
3613 for (LinearExpressionProto& exp :
3614 *(
ct->mutable_all_diff()->mutable_exprs())) {
3615 constraint_has_changed |= CanonicalizeLinearExpression(*
ct, &exp);
3619 const int size = all_diff.exprs_size();
3622 return RemoveConstraint(
ct);
3626 return RemoveConstraint(
ct);
3629 bool something_was_propagated =
false;
3630 std::vector<LinearExpressionProto> kept_expressions;
3631 for (
int i = 0; i < size; ++i) {
3632 if (!context_->
IsFixed(all_diff.exprs(i))) {
3633 kept_expressions.push_back(all_diff.exprs(i));
3637 const int64_t
value = context_->
MinOf(all_diff.exprs(i));
3638 bool propagated =
false;
3639 for (
int j = 0; j < size; ++j) {
3640 if (i == j)
continue;
3643 Domain(
value).Complement())) {
3651 something_was_propagated =
true;
3658 kept_expressions.begin(), kept_expressions.end(),
3659 [](
const LinearExpressionProto& expr_a,
3660 const LinearExpressionProto& expr_b) {
3661 DCHECK_EQ(expr_a.vars_size(), 1);
3662 DCHECK_EQ(expr_b.vars_size(), 1);
3663 const int ref_a = expr_a.vars(0);
3664 const int ref_b = expr_b.vars(0);
3665 const int64_t coeff_a = expr_a.coeffs(0);
3666 const int64_t coeff_b = expr_b.coeffs(0);
3667 const int64_t abs_coeff_a = std::abs(coeff_a);
3668 const int64_t abs_coeff_b = std::abs(coeff_b);
3669 const int64_t offset_a = expr_a.offset();
3670 const int64_t offset_b = expr_b.offset();
3671 const int64_t abs_offset_a = std::abs(offset_a);
3672 const int64_t abs_offset_b = std::abs(offset_b);
3673 return std::tie(ref_a, abs_coeff_a, coeff_a, abs_offset_a, offset_a) <
3674 std::tie(ref_b, abs_coeff_b, coeff_b, abs_offset_b, offset_b);
3680 for (
int i = 1; i < kept_expressions.size(); ++i) {
3682 kept_expressions[i - 1], 1)) {
3684 "Duplicate variable in all_diff");
3687 kept_expressions[i - 1], -1)) {
3688 bool domain_modified =
false;
3690 Domain(0).Complement(),
3691 &domain_modified)) {
3694 if (domain_modified) {
3696 "all_diff: remove 0 from expression appearing with its "
3702 if (kept_expressions.size() < all_diff.exprs_size()) {
3703 all_diff.clear_exprs();
3704 for (
const LinearExpressionProto& expr : kept_expressions) {
3705 *all_diff.add_exprs() = expr;
3708 something_was_propagated =
true;
3709 constraint_has_changed =
true;
3710 if (kept_expressions.size() <= 1)
continue;
3714 CHECK_GE(all_diff.exprs_size(), 2);
3716 for (
int i = 1; i < all_diff.exprs_size(); ++i) {
3719 if (all_diff.exprs_size() == domain.Size()) {
3720 absl::flat_hash_map<int64_t, std::vector<LinearExpressionProto>>
3722 for (
const LinearExpressionProto& expr : all_diff.exprs()) {
3723 for (
const int64_t v : context_->
DomainOf(expr.vars(0)).
Values()) {
3724 value_to_exprs[expr.coeffs(0) * v + expr.offset()].push_back(expr);
3727 bool propagated =
false;
3728 for (
const auto& it : value_to_exprs) {
3729 if (it.second.size() == 1 && !context_->
IsFixed(it.second.front())) {
3730 const LinearExpressionProto& expr = it.second.
front();
3739 "all_diff: propagated mandatory values in permutation");
3740 something_was_propagated =
true;
3743 if (!something_was_propagated)
break;
3746 return constraint_has_changed;
3753void AddImplication(
int lhs,
int rhs, CpModelProto*
proto,
3754 absl::flat_hash_map<int, int>* ref_to_bool_and) {
3755 if (ref_to_bool_and->contains(lhs)) {
3756 const int ct_index = (*ref_to_bool_and)[lhs];
3757 proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(rhs);
3758 }
else if (ref_to_bool_and->contains(
NegatedRef(rhs))) {
3759 const int ct_index = (*ref_to_bool_and)[
NegatedRef(rhs)];
3760 proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(
3763 (*ref_to_bool_and)[lhs] =
proto->constraints_size();
3764 ConstraintProto*
ct =
proto->add_constraints();
3765 ct->add_enforcement_literal(lhs);
3766 ct->mutable_bool_and()->add_literals(rhs);
3770template <
typename ClauseContainer>
3771void ExtractClauses(
bool use_bool_and,
const ClauseContainer& container,
3772 CpModelProto*
proto) {
3779 absl::flat_hash_map<int, int> ref_to_bool_and;
3780 for (
int i = 0; i < container.NumClauses(); ++i) {
3781 const std::vector<Literal>& clause = container.Clause(i);
3782 if (clause.empty())
continue;
3785 if (use_bool_and && clause.size() == 2) {
3786 const int a = clause[0].IsPositive()
3787 ? clause[0].Variable().value()
3789 const int b = clause[1].IsPositive()
3790 ? clause[1].Variable().value()
3797 ConstraintProto*
ct =
proto->add_constraints();
3798 for (
const Literal l : clause) {
3799 if (l.IsPositive()) {
3800 ct->mutable_bool_or()->add_literals(l.Variable().value());
3802 ct->mutable_bool_or()->add_literals(
NegatedRef(l.Variable().value()));
3810bool CpModelPresolver::PresolveNoOverlap(ConstraintProto*
ct) {
3812 NoOverlapConstraintProto*
proto =
ct->mutable_no_overlap();
3813 bool changed =
false;
3817 const int initial_num_intervals =
proto->intervals_size();
3820 for (
int i = 0; i < initial_num_intervals; ++i) {
3821 const int interval_index =
proto->intervals(i);
3824 proto->set_intervals(new_size++, interval_index);
3827 if (new_size < initial_num_intervals) {
3828 proto->mutable_intervals()->Truncate(new_size);
3835 if (
proto->intervals_size() > 1) {
3836 std::vector<IndexedInterval> indexed_intervals;
3837 for (
int i = 0; i <
proto->intervals().size(); ++i) {
3839 indexed_intervals.push_back({
index,
3843 std::vector<std::vector<int>> components;
3846 if (components.size() > 1) {
3847 for (
const std::vector<int>& intervals : components) {
3848 if (intervals.size() <= 1)
continue;
3850 NoOverlapConstraintProto* new_no_overlap =
3851 context_->
working_model->add_constraints()->mutable_no_overlap();
3854 for (
const int i : intervals) {
3855 new_no_overlap->add_intervals(i);
3859 context_->
UpdateRuleStats(
"no_overlap: split into disjoint components");
3860 return RemoveConstraint(
ct);
3864 std::vector<int> constant_intervals;
3865 int64_t size_min_of_non_constant_intervals =
3867 for (
int i = 0; i <
proto->intervals_size(); ++i) {
3868 const int interval_index =
proto->intervals(i);
3870 constant_intervals.push_back(interval_index);
3872 size_min_of_non_constant_intervals =
3873 std::min(size_min_of_non_constant_intervals,
3874 context_->
SizeMin(interval_index));
3878 bool move_constraint_last =
false;
3879 if (!constant_intervals.empty()) {
3881 std::sort(constant_intervals.begin(), constant_intervals.end(),
3882 [
this](
int i1,
int i2) {
3883 const int64_t s1 = context_->StartMin(i1);
3884 const int64_t e1 = context_->EndMax(i1);
3885 const int64_t s2 = context_->StartMin(i2);
3886 const int64_t e2 = context_->EndMax(i2);
3887 return std::tie(s1, e1) < std::tie(s2, e2);
3893 for (
int i = 0; i + 1 < constant_intervals.size(); ++i) {
3894 if (context_->
EndMax(constant_intervals[i]) >
3895 context_->
StartMin(constant_intervals[i + 1])) {
3901 if (constant_intervals.size() ==
proto->intervals_size()) {
3903 return RemoveConstraint(
ct);
3906 absl::flat_hash_set<int> intervals_to_remove;
3910 for (
int i = 0; i + 1 < constant_intervals.size(); ++i) {
3911 const int start = i;
3912 while (i + 1 < constant_intervals.size() &&
3913 context_->
StartMin(constant_intervals[i + 1]) -
3914 context_->
EndMax(constant_intervals[i]) <
3915 size_min_of_non_constant_intervals) {
3918 if (i ==
start)
continue;
3919 for (
int j =
start; j <= i; ++j) {
3920 intervals_to_remove.insert(constant_intervals[j]);
3922 const int64_t new_start = context_->
StartMin(constant_intervals[
start]);
3923 const int64_t new_end = context_->
EndMax(constant_intervals[i]);
3925 IntervalConstraintProto* new_interval =
3926 context_->
working_model->add_constraints()->mutable_interval();
3927 new_interval->mutable_start()->set_offset(new_start);
3928 new_interval->mutable_size()->set_offset(new_end - new_start);
3929 new_interval->mutable_end()->set_offset(new_end);
3930 move_constraint_last =
true;
3934 if (!intervals_to_remove.empty()) {
3936 const int old_size =
proto->intervals_size();
3937 for (
int i = 0; i < old_size; ++i) {
3938 const int interval_index =
proto->intervals(i);
3939 if (intervals_to_remove.contains(interval_index)) {
3942 proto->set_intervals(new_size++, interval_index);
3945 proto->mutable_intervals()->Truncate(new_size);
3947 "no_overlap: merge constant contiguous intervals");
3948 intervals_to_remove.clear();
3949 constant_intervals.clear();
3955 if (
proto->intervals_size() == 1) {
3957 return RemoveConstraint(
ct);
3959 if (
proto->intervals().empty()) {
3961 return RemoveConstraint(
ct);
3967 if (move_constraint_last) {
3971 return RemoveConstraint(
ct);
3977bool CpModelPresolver::PresolveNoOverlap2D(
int c, ConstraintProto*
ct) {
3982 const NoOverlap2DConstraintProto&
proto =
ct->no_overlap_2d();
3983 const int initial_num_boxes =
proto.x_intervals_size();
3985 bool has_zero_sizes =
false;
3986 bool x_constant =
true;
3987 bool y_constant =
true;
3991 std::vector<Rectangle> bounding_boxes;
3992 std::vector<int> active_boxes;
3993 for (
int i = 0; i <
proto.x_intervals_size(); ++i) {
3994 const int x_interval_index =
proto.x_intervals(i);
3995 const int y_interval_index =
proto.y_intervals(i);
4002 if (
proto.boxes_with_null_area_can_overlap() &&
4003 (context_->
SizeMax(x_interval_index) == 0 ||
4004 context_->
SizeMax(y_interval_index) == 0)) {
4005 if (
proto.boxes_with_null_area_can_overlap())
continue;
4006 has_zero_sizes =
true;
4008 ct->mutable_no_overlap_2d()->set_x_intervals(new_size, x_interval_index);
4009 ct->mutable_no_overlap_2d()->set_y_intervals(new_size, y_interval_index);
4010 bounding_boxes.push_back(
4011 {IntegerValue(context_->
StartMin(x_interval_index)),
4012 IntegerValue(context_->
EndMax(x_interval_index)),
4013 IntegerValue(context_->
StartMin(y_interval_index)),
4014 IntegerValue(context_->
EndMax(y_interval_index))});
4015 active_boxes.push_back(new_size);
4027 bounding_boxes, absl::MakeSpan(active_boxes));
4028 if (components.size() > 1) {
4029 for (
const absl::Span<int> boxes : components) {
4030 if (boxes.size() <= 1)
continue;
4032 NoOverlap2DConstraintProto* new_no_overlap_2d =
4033 context_->
working_model->add_constraints()->mutable_no_overlap_2d();
4034 for (
const int b : boxes) {
4035 new_no_overlap_2d->add_x_intervals(
proto.x_intervals(
b));
4036 new_no_overlap_2d->add_y_intervals(
proto.y_intervals(
b));
4040 context_->
UpdateRuleStats(
"no_overlap_2d: split into disjoint components");
4041 return RemoveConstraint(
ct);
4044 if (!has_zero_sizes && (x_constant || y_constant)) {
4046 "no_overlap_2d: a dimension is constant, splitting into many no "
4048 std::vector<IndexedInterval> indexed_intervals;
4049 for (
int i = 0; i < new_size; ++i) {
4050 int x =
proto.x_intervals(i);
4051 int y =
proto.y_intervals(i);
4053 indexed_intervals.push_back({x, IntegerValue(context_->
StartMin(y)),
4054 IntegerValue(context_->
EndMax(y))});
4056 std::vector<std::vector<int>> no_overlaps;
4059 for (
const std::vector<int>& no_overlap : no_overlaps) {
4060 ConstraintProto* new_ct = context_->
working_model->add_constraints();
4063 for (
const int i : no_overlap) {
4064 new_ct->mutable_no_overlap()->add_intervals(i);
4068 return RemoveConstraint(
ct);
4071 if (new_size < initial_num_boxes) {
4073 ct->mutable_no_overlap_2d()->mutable_x_intervals()->Truncate(new_size);
4074 ct->mutable_no_overlap_2d()->mutable_y_intervals()->Truncate(new_size);
4077 if (new_size == 0) {
4079 return RemoveConstraint(
ct);
4082 if (new_size == 1) {
4084 return RemoveConstraint(
ct);
4087 return new_size < initial_num_boxes;
4091LinearExpressionProto ConstantExpressionProto(int64_t
value) {
4092 LinearExpressionProto expr;
4093 expr.set_offset(
value);
4098bool CpModelPresolver::PresolveCumulative(ConstraintProto*
ct) {
4101 CumulativeConstraintProto*
proto =
ct->mutable_cumulative();
4103 bool changed = CanonicalizeLinearExpression(*
ct,
proto->mutable_capacity());
4104 for (LinearExpressionProto& exp :
4105 *(
ct->mutable_cumulative()->mutable_demands())) {
4106 changed |= CanonicalizeLinearExpression(*
ct, &exp);
4109 const int64_t capacity_max = context_->
MaxOf(
proto->capacity());
4113 bool domain_changed =
false;
4115 proto->capacity(), Domain(0, capacity_max), &domain_changed)) {
4118 if (domain_changed) {
4127 int num_zero_demand_removed = 0;
4128 int num_zero_size_removed = 0;
4129 int num_incompatible_demands = 0;
4130 for (
int i = 0; i <
proto->intervals_size(); ++i) {
4133 const LinearExpressionProto& demand_expr =
proto->demands(i);
4134 const int64_t demand_max = context_->
MaxOf(demand_expr);
4135 if (demand_max == 0) {
4136 num_zero_demand_removed++;
4142 num_zero_size_removed++;
4146 if (context_->
MinOf(demand_expr) > capacity_max) {
4148 ConstraintProto* interval_ct =
4150 DCHECK_EQ(interval_ct->enforcement_literal_size(), 1);
4151 const int literal = interval_ct->enforcement_literal(0);
4155 num_incompatible_demands++;
4159 "cumulative: performed demand exceeds capacity.");
4163 proto->set_intervals(new_size,
proto->intervals(i));
4164 *
proto->mutable_demands(new_size) =
proto->demands(i);
4168 if (new_size < proto->intervals_size()) {
4170 proto->mutable_intervals()->Truncate(new_size);
4171 proto->mutable_demands()->erase(
4172 proto->mutable_demands()->begin() + new_size,
4173 proto->mutable_demands()->end());
4176 if (num_zero_demand_removed > 0) {
4178 "cumulative: removed intervals with no demands");
4180 if (num_zero_size_removed > 0) {
4182 "cumulative: removed intervals with a size of zero");
4184 if (num_incompatible_demands > 0) {
4186 "cumulative: removed intervals demands greater than the capacity");
4192 for (
int i = 0; i <
proto->demands_size(); ++i) {
4194 const LinearExpressionProto& demand_expr =
proto->demands(i);
4196 bool domain_changed =
false;
4201 if (domain_changed) {
4203 "cumulative: fit demand in [0..capacity_max]");
4215 if (
proto->intervals_size() > 1) {
4216 std::vector<IndexedInterval> indexed_intervals;
4217 for (
int i = 0; i <
proto->intervals().size(); ++i) {
4219 indexed_intervals.push_back({i, IntegerValue(context_->
StartMin(
index)),
4222 std::vector<std::vector<int>> components;
4225 if (components.size() > 1) {
4226 for (
const std::vector<int>& component : components) {
4227 CumulativeConstraintProto* new_cumulative =
4228 context_->
working_model->add_constraints()->mutable_cumulative();
4229 for (
const int i : component) {
4230 new_cumulative->add_intervals(
proto->intervals(i));
4231 *new_cumulative->add_demands() =
proto->demands(i);
4233 *new_cumulative->mutable_capacity() =
proto->capacity();
4236 context_->
UpdateRuleStats(
"cumulative: split into disjoint components");
4237 return RemoveConstraint(
ct);
4245 absl::btree_map<int64_t, int64_t> time_to_demand_deltas;
4246 const int64_t capacity_min = context_->
MinOf(
proto->capacity());
4247 for (
int i = 0; i <
proto->intervals_size(); ++i) {
4248 const int interval_index =
proto->intervals(i);
4249 const int64_t demand_max = context_->
MaxOf(
proto->demands(i));
4250 time_to_demand_deltas[context_->
StartMin(interval_index)] += demand_max;
4251 time_to_demand_deltas[context_->
EndMax(interval_index)] -= demand_max;
4260 int num_possible_overloads = 0;
4261 int64_t current_load = 0;
4262 absl::flat_hash_map<int64_t, int64_t> num_possible_overloads_before;
4263 for (
const auto& it : time_to_demand_deltas) {
4264 num_possible_overloads_before[it.first] = num_possible_overloads;
4265 current_load += it.second;
4266 if (current_load > capacity_min) {
4267 ++num_possible_overloads;
4273 if (num_possible_overloads == 0) {
4275 "cumulative: max profile is always under the min capacity");
4276 return RemoveConstraint(
ct);
4286 for (
int i = 0; i <
proto->intervals_size(); ++i) {
4304 const int num_diff = num_possible_overloads_before.at(
end_max) -
4305 num_possible_overloads_before.at(
start_min);
4306 if (num_diff == 0)
continue;
4308 proto->set_intervals(new_size,
proto->intervals(i));
4309 *
proto->mutable_demands(new_size) =
proto->demands(i);
4313 if (new_size < proto->intervals_size()) {
4315 proto->mutable_intervals()->Truncate(new_size);
4316 proto->mutable_demands()->erase(
4317 proto->mutable_demands()->begin() + new_size,
4318 proto->mutable_demands()->end());
4320 "cumulative: remove never conflicting intervals.");
4324 if (
proto->intervals().empty()) {
4326 return RemoveConstraint(
ct);
4330 int64_t max_of_performed_demand_mins = 0;
4331 int64_t sum_of_max_demands = 0;
4332 for (
int i = 0; i <
proto->intervals_size(); ++i) {
4333 const ConstraintProto& interval_ct =
4336 const LinearExpressionProto& demand_expr =
proto->demands(i);
4337 sum_of_max_demands += context_->
MaxOf(demand_expr);
4339 if (interval_ct.enforcement_literal().empty()) {
4340 max_of_performed_demand_mins =
std::max(max_of_performed_demand_mins,
4341 context_->
MinOf(demand_expr));
4345 const LinearExpressionProto& capacity_expr =
proto->capacity();
4346 if (max_of_performed_demand_mins > context_->
MinOf(capacity_expr)) {
4349 capacity_expr, Domain(max_of_performed_demand_mins,
4355 if (max_of_performed_demand_mins > context_->
MaxOf(capacity_expr)) {
4356 context_->
UpdateRuleStats(
"cumulative: cannot fit performed demands");
4360 if (sum_of_max_demands <= context_->MinOf(capacity_expr)) {
4361 context_->
UpdateRuleStats(
"cumulative: capacity exceeds sum of demands");
4362 return RemoveConstraint(
ct);
4368 for (
int i = 0; i <
ct->cumulative().demands_size(); ++i) {
4369 const LinearExpressionProto& demand_expr =
ct->cumulative().demands(i);
4370 if (!context_->
IsFixed(demand_expr)) {
4376 if (gcd == 1)
break;
4380 for (
int i = 0; i <
ct->cumulative().demands_size(); ++i) {
4381 const int64_t
demand = context_->
MinOf(
ct->cumulative().demands(i));
4382 *
proto->mutable_demands(i) = ConstantExpressionProto(
demand / gcd);
4385 const int64_t old_capacity = context_->
MinOf(
proto->capacity());
4386 *
proto->mutable_capacity() = ConstantExpressionProto(old_capacity / gcd);
4388 "cumulative: divide demands and capacity by gcd");
4392 const int num_intervals =
proto->intervals_size();
4393 const LinearExpressionProto& capacity_expr =
proto->capacity();
4395 std::vector<LinearExpressionProto> start_exprs(num_intervals);
4397 int num_duration_one = 0;
4398 int num_greater_half_capacity = 0;
4400 bool has_optional_interval =
false;
4401 for (
int i = 0; i < num_intervals; ++i) {
4405 const ConstraintProto&
ct =
4407 const IntervalConstraintProto&
interval =
ct.interval();
4410 const LinearExpressionProto& demand_expr =
proto->demands(i);
4419 const int64_t demand_min = context_->
MinOf(demand_expr);
4420 const int64_t demand_max = context_->
MaxOf(demand_expr);
4421 if (demand_min > capacity_max / 2) {
4422 num_greater_half_capacity++;
4424 if (demand_min > capacity_max) {
4425 context_->
UpdateRuleStats(
"cumulative: demand_min exceeds capacity max");
4429 CHECK_EQ(
ct.enforcement_literal().size(), 1);
4435 }
else if (demand_max > capacity_max) {
4436 if (
ct.enforcement_literal().empty()) {
4438 "cumulative: demand_max exceeds capacity max.");
4448 "cumulative: demand_max of optional interval exceeds capacity.");
4453 if (num_greater_half_capacity == num_intervals) {
4454 if (num_duration_one == num_intervals && !has_optional_interval) {
4456 ConstraintProto* new_ct = context_->
working_model->add_constraints();
4457 auto* arg = new_ct->mutable_all_diff();
4458 for (
const LinearExpressionProto& expr : start_exprs) {
4459 *arg->add_exprs() = expr;
4461 if (!context_->
IsFixed(capacity_expr)) {
4462 const int64_t capacity_min = context_->
MinOf(capacity_expr);
4463 for (
const LinearExpressionProto& expr :
proto->demands()) {
4464 if (capacity_min >= context_->
MaxOf(expr))
continue;
4465 LinearConstraintProto* fit =
4466 context_->
working_model->add_constraints()->mutable_linear();
4474 return RemoveConstraint(
ct);
4479 for (
int i = 0; i <
proto->demands_size(); ++i) {
4480 const LinearExpressionProto& demand_expr =
proto->demands(i);
4481 const int64_t demand_max = context_->
MaxOf(demand_expr);
4482 if (demand_max > context_->
MinOf(capacity_expr)) {
4483 ConstraintProto* capacity_gt =
4485 *capacity_gt->mutable_enforcement_literal() =
4487 .enforcement_literal();
4488 capacity_gt->mutable_linear()->add_domain(0);
4489 capacity_gt->mutable_linear()->add_domain(
4492 capacity_gt->mutable_linear());
4494 capacity_gt->mutable_linear());
4498 ConstraintProto* new_ct = context_->
working_model->add_constraints();
4499 auto* arg = new_ct->mutable_no_overlap();
4504 return RemoveConstraint(
ct);
4511bool CpModelPresolver::PresolveRoutes(ConstraintProto*
ct) {
4514 RoutesConstraintProto&
proto = *
ct->mutable_routes();
4516 const int old_size =
proto.literals_size();
4518 std::vector<bool> has_incoming_or_outgoing_arcs;
4519 const int num_arcs =
proto.literals_size();
4520 for (
int i = 0; i < num_arcs; ++i) {
4521 const int ref =
proto.literals(i);
4528 proto.set_literals(new_size, ref);
4532 if (
tail >= has_incoming_or_outgoing_arcs.size()) {
4533 has_incoming_or_outgoing_arcs.resize(
tail + 1,
false);
4535 if (
head >= has_incoming_or_outgoing_arcs.size()) {
4536 has_incoming_or_outgoing_arcs.resize(
head + 1,
false);
4538 has_incoming_or_outgoing_arcs[
tail] =
true;
4539 has_incoming_or_outgoing_arcs[
head] =
true;
4542 if (old_size > 0 && new_size == 0) {
4547 "routes: graph with nodes and no arcs");
4550 if (new_size < num_arcs) {
4551 proto.mutable_literals()->Truncate(new_size);
4552 proto.mutable_tails()->Truncate(new_size);
4553 proto.mutable_heads()->Truncate(new_size);
4559 for (
int n = 0; n < has_incoming_or_outgoing_arcs.size(); ++n) {
4560 if (!has_incoming_or_outgoing_arcs[n]) {
4562 "routes: node ", n,
" misses incoming or outgoing arcs"));
4569bool CpModelPresolver::PresolveCircuit(ConstraintProto*
ct) {
4572 CircuitConstraintProto&
proto = *
ct->mutable_circuit();
4576 ct->mutable_circuit()->mutable_heads());
4580 std::vector<std::vector<int>> incoming_arcs;
4581 std::vector<std::vector<int>> outgoing_arcs;
4583 const int num_arcs =
proto.literals_size();
4584 for (
int i = 0; i < num_arcs; ++i) {
4585 const int ref =
proto.literals(i);
4593 incoming_arcs[
head].push_back(ref);
4594 outgoing_arcs[
tail].push_back(ref);
4598 for (
int i = 0; i < num_nodes; ++i) {
4599 if (incoming_arcs[i].empty() || outgoing_arcs[i].empty()) {
4600 return MarkConstraintAsFalse(
ct);
4609 bool loop_again =
true;
4610 int num_fixed_at_true = 0;
4611 while (loop_again) {
4613 for (
const auto* node_to_refs : {&incoming_arcs, &outgoing_arcs}) {
4614 for (
const std::vector<int>& refs : *node_to_refs) {
4615 if (refs.size() == 1) {
4617 ++num_fixed_at_true;
4626 for (
const int ref : refs) {
4636 if (num_true == 1) {
4637 for (
const int ref : refs) {
4638 if (ref != true_ref) {
4639 if (!context_->
IsFixed(ref)) {
4650 if (num_fixed_at_true > 0) {
4657 int circuit_start = -1;
4658 std::vector<int>
next(num_nodes, -1);
4659 std::vector<int> new_in_degree(num_nodes, 0);
4660 std::vector<int> new_out_degree(num_nodes, 0);
4661 for (
int i = 0; i < num_arcs; ++i) {
4662 const int ref =
proto.literals(i);
4670 circuit_start =
proto.tails(i);
4674 ++new_out_degree[
proto.tails(i)];
4675 ++new_in_degree[
proto.heads(i)];
4678 proto.set_literals(new_size,
proto.literals(i));
4688 for (
int i = 0; i < num_nodes; ++i) {
4689 if (new_in_degree[i] == 0 || new_out_degree[i] == 0) {
4695 if (circuit_start != -1) {
4696 std::vector<bool> visited(num_nodes,
false);
4697 int current = circuit_start;
4698 while (current != -1 && !visited[current]) {
4699 visited[current] =
true;
4700 current =
next[current];
4702 if (current == circuit_start) {
4705 std::vector<bool> has_self_arc(num_nodes,
false);
4706 for (
int i = 0; i < num_arcs; ++i) {
4707 if (visited[
proto.tails(i)])
continue;
4709 has_self_arc[
proto.tails(i)] =
true;
4715 for (
int n = 0; n < num_nodes; ++n) {
4716 if (!visited[n] && !has_self_arc[n]) {
4718 return MarkConstraintAsFalse(
ct);
4722 return RemoveConstraint(
ct);
4726 if (num_true == new_size) {
4728 return RemoveConstraint(
ct);
4734 for (
int i = 0; i < num_nodes; ++i) {
4735 for (
const std::vector<int>* arc_literals :
4736 {&incoming_arcs[i], &outgoing_arcs[i]}) {
4737 std::vector<int> literals;
4738 for (
const int ref : *arc_literals) {
4744 literals.push_back(ref);
4746 if (literals.size() == 2 && literals[0] !=
NegatedRef(literals[1])) {
4755 if (new_size < num_arcs) {
4756 proto.mutable_tails()->Truncate(new_size);
4757 proto.mutable_heads()->Truncate(new_size);
4758 proto.mutable_literals()->Truncate(new_size);
4765bool CpModelPresolver::PresolveAutomaton(ConstraintProto*
ct) {
4768 AutomatonConstraintProto&
proto = *
ct->mutable_automaton();
4769 if (
proto.vars_size() == 0 ||
proto.transition_label_size() == 0) {
4773 bool all_have_same_affine_relation =
true;
4774 std::vector<AffineRelation::Relation> affine_relations;
4775 for (
int v = 0; v <
proto.vars_size(); ++v) {
4776 const int var =
ct->automaton().vars(v);
4778 affine_relations.push_back(r);
4779 if (r.representative ==
var) {
4780 all_have_same_affine_relation =
false;
4783 if (v > 0 && (r.coeff != affine_relations[v - 1].coeff ||
4784 r.offset != affine_relations[v - 1].offset)) {
4785 all_have_same_affine_relation =
false;
4790 if (all_have_same_affine_relation) {
4791 for (
int v = 0; v <
proto.vars_size(); ++v) {
4794 const AffineRelation::Relation rep = affine_relations.front();
4796 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
4797 const int64_t label =
proto.transition_label(t);
4798 int64_t inverse_label = (label - rep.offset) / rep.coeff;
4799 if (inverse_label * rep.coeff + rep.offset == label) {
4800 if (new_size != t) {
4801 proto.set_transition_tail(new_size,
proto.transition_tail(t));
4802 proto.set_transition_head(new_size,
proto.transition_head(t));
4804 proto.set_transition_label(new_size, inverse_label);
4808 if (new_size <
proto.transition_tail_size()) {
4809 proto.mutable_transition_tail()->Truncate(new_size);
4810 proto.mutable_transition_label()->Truncate(new_size);
4811 proto.mutable_transition_head()->Truncate(new_size);
4819 for (
int v = 1; v <
proto.vars_size(); ++v) {
4824 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
4825 const int64_t label =
proto.transition_label(t);
4826 if (hull.Contains(label)) {
4827 if (new_size != t) {
4828 proto.set_transition_tail(new_size,
proto.transition_tail(t));
4829 proto.set_transition_label(new_size, label);
4830 proto.set_transition_head(new_size,
proto.transition_head(t));
4835 if (new_size <
proto.transition_tail_size()) {
4836 proto.mutable_transition_tail()->Truncate(new_size);
4837 proto.mutable_transition_label()->Truncate(new_size);
4838 proto.mutable_transition_head()->Truncate(new_size);
4843 const int n =
proto.vars_size();
4844 const std::vector<int> vars = {
proto.vars().begin(),
proto.vars().end()};
4847 std::vector<absl::btree_set<int64_t>> reachable_states(n + 1);
4848 reachable_states[0].insert(
proto.starting_state());
4849 reachable_states[n] = {
proto.final_states().begin(),
4850 proto.final_states().end()};
4854 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
4855 const int64_t
tail =
proto.transition_tail(t);
4856 const int64_t label =
proto.transition_label(t);
4857 const int64_t
head =
proto.transition_head(t);
4860 reachable_states[
time + 1].insert(
head);
4864 std::vector<absl::btree_set<int64_t>> reached_values(n);
4868 absl::btree_set<int64_t> new_set;
4869 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
4870 const int64_t
tail =
proto.transition_tail(t);
4871 const int64_t label =
proto.transition_label(t);
4872 const int64_t
head =
proto.transition_head(t);
4877 new_set.insert(
tail);
4878 reached_values[
time].insert(label);
4880 reachable_states[
time].swap(new_set);
4883 bool removed_values =
false;
4888 {reached_values[time].begin(), reached_values[time].end()}),
4893 if (removed_values) {
4899bool CpModelPresolver::PresolveReservoir(ConstraintProto*
ct) {
4903 ReservoirConstraintProto&
proto = *
ct->mutable_reservoir();
4904 bool changed =
false;
4905 for (LinearExpressionProto& exp : *(
proto.mutable_time_exprs())) {
4906 changed |= CanonicalizeLinearExpression(*
ct, &exp);
4909 if (
proto.active_literals().empty()) {
4911 for (
int i = 0; i <
proto.time_exprs_size(); ++i) {
4912 proto.add_active_literals(true_literal);
4917 const auto& demand_is_null = [&](
int i) {
4918 return proto.level_changes(i) == 0 ||
4924 for (
int i = 0; i <
proto.level_changes_size(); ++i) {
4925 if (demand_is_null(i)) num_zeros++;
4928 if (num_zeros > 0) {
4931 for (
int i = 0; i <
proto.level_changes_size(); ++i) {
4932 if (demand_is_null(i))
continue;
4933 proto.set_level_changes(new_size,
proto.level_changes(i));
4934 *
proto.mutable_time_exprs(new_size) =
proto.time_exprs(i);
4935 proto.set_active_literals(new_size,
proto.active_literals(i));
4939 proto.mutable_level_changes()->Truncate(new_size);
4940 proto.mutable_time_exprs()->erase(
4941 proto.mutable_time_exprs()->begin() + new_size,
4942 proto.mutable_time_exprs()->end());
4943 proto.mutable_active_literals()->Truncate(new_size);
4946 "reservoir: remove zero level_changes or inactive events.");
4949 const int num_events =
proto.level_changes_size();
4951 proto.level_changes().empty() ? 0 : std::abs(
proto.level_changes(0));
4952 int num_positives = 0;
4953 int num_negatives = 0;
4954 int64_t max_sum_of_positive_level_changes = 0;
4955 int64_t min_sum_of_negative_level_changes = 0;
4956 for (
int i = 0; i < num_events; ++i) {
4961 max_sum_of_positive_level_changes +=
demand;
4965 min_sum_of_negative_level_changes +=
demand;
4969 if (min_sum_of_negative_level_changes >=
proto.min_level() &&
4970 max_sum_of_positive_level_changes <=
proto.max_level()) {
4972 return RemoveConstraint(
ct);
4975 if (min_sum_of_negative_level_changes >
proto.max_level() ||
4976 max_sum_of_positive_level_changes <
proto.min_level()) {
4981 if (min_sum_of_negative_level_changes >
proto.min_level()) {
4982 proto.set_min_level(min_sum_of_negative_level_changes);
4984 "reservoir: increase min_level to reachable value");
4987 if (max_sum_of_positive_level_changes <
proto.max_level()) {
4988 proto.set_max_level(max_sum_of_positive_level_changes);
4989 context_->
UpdateRuleStats(
"reservoir: reduce max_level to reachable value");
4992 if (
proto.min_level() <= 0 &&
proto.max_level() >= 0 &&
4993 (num_positives == 0 || num_negatives == 0)) {
4997 context_->
working_model->add_constraints()->mutable_linear();
4998 int64_t fixed_contrib = 0;
4999 for (
int i = 0; i <
proto.level_changes_size(); ++i) {
5003 const int active =
proto.active_literals(i);
5005 sum->add_vars(active);
5009 sum->add_coeffs(-
demand);
5013 sum->add_domain(
proto.min_level() - fixed_contrib);
5014 sum->add_domain(
proto.max_level() - fixed_contrib);
5016 return RemoveConstraint(
ct);
5020 for (
int i = 0; i <
proto.level_changes_size(); ++i) {
5021 proto.set_level_changes(i,
proto.level_changes(i) / gcd);
5027 const Domain reduced_domain = Domain({
proto.min_level(),
proto.max_level()})
5028 .InverseMultiplicationBy(gcd);
5029 proto.set_min_level(reduced_domain.Min());
5030 proto.set_max_level(reduced_domain.Max());
5032 "reservoir: simplify level_changes and levels by gcd.");
5035 if (num_positives == 1 && num_negatives > 0) {
5037 "TODO reservoir: one producer, multiple consumers.");
5040 absl::flat_hash_set<std::tuple<int, int64_t, int64_t, int>> time_active_set;
5041 for (
int i = 0; i <
proto.level_changes_size(); ++i) {
5042 const LinearExpressionProto&
time =
proto.time_exprs(i);
5046 const std::tuple<int, int64_t, int64_t, int> key = std::make_tuple(
5049 proto.active_literals(i));
5050 if (time_active_set.contains(key)) {
5051 context_->
UpdateRuleStats(
"TODO reservoir: merge synchronized events.");
5054 time_active_set.insert(key);
5064void CpModelPresolver::ExtractBoolAnd() {
5065 absl::flat_hash_map<int, int> ref_to_bool_and;
5066 const int num_constraints = context_->
working_model->constraints_size();
5067 std::vector<int> to_remove;
5068 for (
int c = 0; c < num_constraints; ++c) {
5072 if (
ct.constraint_case() == ConstraintProto::kBoolOr &&
5073 ct.bool_or().literals().size() == 2) {
5077 to_remove.push_back(c);
5081 if (
ct.constraint_case() == ConstraintProto::kAtMostOne &&
5082 ct.at_most_one().literals().size() == 2) {
5083 AddImplication(
ct.at_most_one().literals(0),
5086 to_remove.push_back(c);
5092 for (
const int c : to_remove) {
5093 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
5101void CpModelPresolver::Probe() {
5109 auto* implication_graph =
model.GetOrCreate<BinaryImplicationGraph>();
5110 auto* sat_solver =
model.GetOrCreate<SatSolver>();
5111 auto* mapping =
model.GetOrCreate<CpModelMapping>();
5112 auto* prober =
model.GetOrCreate<Prober>();
5113 prober->ProbeBooleanVariables(1.0);
5115 model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
5116 if (sat_solver->IsModelUnsat() || !implication_graph->DetectEquivalences()) {
5121 CHECK_EQ(sat_solver->CurrentDecisionLevel(), 0);
5122 for (
int i = 0; i < sat_solver->LiteralTrail().
Index(); ++i) {
5123 const Literal l = sat_solver->LiteralTrail()[i];
5124 const int var = mapping->GetProtoVariableFromBooleanVariable(l.Variable());
5131 const int num_variables = context_->
working_model->variables().size();
5132 auto* integer_trail =
model.GetOrCreate<IntegerTrail>();
5133 for (
int var = 0;
var < num_variables; ++
var) {
5136 if (!mapping->IsBoolean(
var)) {
5139 integer_trail->InitialVariableDomain(mapping->Integer(
var)))) {
5146 const Literal l = mapping->Literal(
var);
5147 const Literal r = implication_graph->RepresentativeOf(l);
5150 mapping->GetProtoVariableFromBooleanVariable(r.Variable());
5160void CpModelPresolver::PresolvePureSatPart() {
5165 const int num_variables = context_->
working_model->variables_size();
5166 SatPostsolver sat_postsolver(num_variables);
5167 SatPresolver sat_presolver(&sat_postsolver, logger_);
5168 sat_presolver.SetNumVariables(num_variables);
5169 sat_presolver.SetTimeLimit(context_->
time_limit());
5171 SatParameters params = context_->
params();
5178 if (params.debug_postsolve_with_full_solver()) {
5179 params.set_presolve_blocked_clause(
false);
5185 params.set_presolve_use_bva(
false);
5186 sat_presolver.SetParameters(params);
5189 absl::flat_hash_set<int> used_variables;
5190 auto convert = [&used_variables](
int ref) {
5192 if (
RefIsPositive(ref))
return Literal(BooleanVariable(ref),
true);
5193 return Literal(BooleanVariable(
NegatedRef(ref)),
false);
5201 for (
int c = 0; c < context_->
working_model->constraints_size(); ++c) {
5203 if (
ct.constraint_case() == ConstraintProto::kBoolOr ||
5204 ct.constraint_case() == ConstraintProto::kBoolAnd) {
5223 std::vector<Literal> clause;
5224 int num_removed_constraints = 0;
5225 for (
int i = 0; i < context_->
working_model->constraints_size(); ++i) {
5228 if (
ct.constraint_case() == ConstraintProto::kBoolOr) {
5229 ++num_removed_constraints;
5231 for (
const int ref :
ct.bool_or().literals()) {
5232 clause.push_back(convert(ref));
5234 for (
const int ref :
ct.enforcement_literal()) {
5235 clause.push_back(convert(ref).Negated());
5237 sat_presolver.AddClause(clause);
5244 if (
ct.constraint_case() == ConstraintProto::kBoolAnd) {
5245 ++num_removed_constraints;
5246 std::vector<Literal> clause;
5247 for (
const int ref :
ct.enforcement_literal()) {
5248 clause.push_back(convert(ref).Negated());
5251 for (
const int ref :
ct.bool_and().literals()) {
5252 clause.back() = convert(ref);
5253 sat_presolver.AddClause(clause);
5263 if (num_removed_constraints == 0)
return;
5273 std::vector<bool> can_be_removed(num_variables,
false);
5274 for (
int i = 0; i < num_variables; ++i) {
5276 can_be_removed[i] =
true;
5282 if (used_variables.contains(i) && context_->
IsFixed(i)) {
5284 sat_presolver.AddClause({convert(i)});
5286 sat_presolver.AddClause({convert(
NegatedRef(i))});
5294 const int num_passes = params.presolve_use_bva() ? 4 : 1;
5295 for (
int i = 0; i < num_passes; ++i) {
5296 const int old_num_clause = sat_postsolver.NumClauses();
5297 if (!sat_presolver.Presolve(can_be_removed)) {
5298 VLOG(1) <<
"UNSAT during SAT presolve.";
5301 if (old_num_clause == sat_postsolver.NumClauses())
break;
5305 const int new_num_variables = sat_presolver.NumVariables();
5306 if (new_num_variables > context_->
working_model->variables_size()) {
5307 VLOG(1) <<
"New variables added by the SAT presolver.";
5309 i < new_num_variables; ++i) {
5310 IntegerVariableProto* var_proto =
5312 var_proto->add_domain(0);
5313 var_proto->add_domain(1);
5319 ExtractClauses(
true, sat_presolver, context_->
working_model);
5327 ExtractClauses(
false, sat_postsolver,
5335void CpModelPresolver::ExpandObjective() {
5354 int unique_expanded_constraint = -1;
5355 const bool objective_was_a_single_variable =
5360 const int num_variables = context_->
working_model->variables_size();
5361 const int num_constraints = context_->
working_model->constraints_size();
5362 absl::flat_hash_set<int> relevant_constraints;
5363 std::vector<int> var_to_num_relevant_constraints(num_variables, 0);
5364 for (
int ct_index = 0; ct_index < num_constraints; ++ct_index) {
5365 const ConstraintProto&
ct = context_->
working_model->constraints(ct_index);
5367 if (!
ct.enforcement_literal().empty() ||
5368 ct.constraint_case() != ConstraintProto::kLinear ||
5369 ct.linear().domain().size() != 2 ||
5370 ct.linear().domain(0) !=
ct.linear().domain(1)) {
5374 relevant_constraints.insert(ct_index);
5375 const int num_terms =
ct.linear().vars_size();
5376 for (
int i = 0; i < num_terms; ++i) {
5377 var_to_num_relevant_constraints[
PositiveRef(
ct.linear().vars(i))]++;
5381 absl::btree_set<int> var_to_process;
5383 const int var = entry.first;
5385 if (var_to_num_relevant_constraints[
var] != 0) {
5386 var_to_process.insert(
var);
5391 int num_expansions = 0;
5392 int last_expanded_objective_var;
5393 absl::flat_hash_set<int> processed_vars;
5394 std::vector<int> new_vars_in_objective;
5395 while (!relevant_constraints.empty()) {
5397 int objective_var = -1;
5398 while (!var_to_process.empty()) {
5399 const int var = *var_to_process.begin();
5400 CHECK(!processed_vars.contains(
var));
5401 if (var_to_num_relevant_constraints[
var] == 0) {
5402 processed_vars.insert(
var);
5403 var_to_process.erase(
var);
5408 var_to_process.erase(
var);
5411 objective_var =
var;
5415 if (objective_var == -1)
break;
5417 processed_vars.insert(objective_var);
5418 var_to_process.erase(objective_var);
5420 int expanded_linear_index = -1;
5421 int64_t objective_coeff_in_expanded_constraint;
5422 int64_t size_of_expanded_constraint = 0;
5423 const auto& non_deterministic_list =
5425 std::vector<int> constraints_with_objective(non_deterministic_list.begin(),
5426 non_deterministic_list.end());
5427 std::sort(constraints_with_objective.begin(),
5428 constraints_with_objective.end());
5429 for (
const int ct_index : constraints_with_objective) {
5430 if (relevant_constraints.count(ct_index) == 0)
continue;
5431 const ConstraintProto&
ct =
5436 relevant_constraints.erase(ct_index);
5437 const int num_terms =
ct.linear().vars_size();
5438 for (
int i = 0; i < num_terms; ++i) {
5439 var_to_num_relevant_constraints[
PositiveRef(
ct.linear().vars(i))]--;
5451 bool is_present =
false;
5452 int64_t objective_coeff;
5453 for (
int i = 0; i < num_terms; ++i) {
5454 const int ref =
ct.linear().vars(i);
5455 const int64_t
coeff =
ct.linear().coeffs(i);
5457 CHECK(!is_present) <<
"Duplicate variables not supported.";
5459 objective_coeff = (ref == objective_var) ?
coeff : -
coeff;
5472 if (std::abs(objective_coeff) == 1 &&
5473 num_terms > size_of_expanded_constraint) {
5474 expanded_linear_index = ct_index;
5475 size_of_expanded_constraint = num_terms;
5476 objective_coeff_in_expanded_constraint = objective_coeff;
5480 if (expanded_linear_index != -1) {
5483 CHECK_EQ(std::abs(objective_coeff_in_expanded_constraint), 1);
5484 const ConstraintProto&
ct =
5485 context_->
working_model->constraints(expanded_linear_index);
5487 objective_var, objective_coeff_in_expanded_constraint,
ct,
5488 &new_vars_in_objective)) {
5493 context_->
UpdateRuleStats(
"objective: expanded objective constraint.");
5496 for (
const int var : new_vars_in_objective) {
5497 if (!processed_vars.contains(
var)) var_to_process.insert(
var);
5510 for (
int i = 0; i < size_of_expanded_constraint; ++i) {
5511 const int ref =
ct.linear().vars(i);
5516 -
ct.linear().coeffs(i)))
5517 .RelaxIfTooComplex();
5519 implied_domain = implied_domain.InverseMultiplicationBy(
5520 objective_coeff_in_expanded_constraint);
5524 if (implied_domain.IsIncludedIn(context_->
DomainOf(objective_var))) {
5526 context_->
UpdateRuleStats(
"objective: removed objective constraint.");
5528 context_->
working_model->mutable_constraints(expanded_linear_index)
5532 unique_expanded_constraint = expanded_linear_index;
5537 last_expanded_objective_var = objective_var;
5543 if (num_expansions == 1 && objective_was_a_single_variable &&
5544 unique_expanded_constraint != -1) {
5546 "objective: removed unique objective constraint.");
5547 ConstraintProto* mutable_ct = context_->
working_model->mutable_constraints(
5548 unique_expanded_constraint);
5549 *(context_->
mapping_model->add_constraints()) = *mutable_ct;
5550 mutable_ct->Clear();
5563void CpModelPresolver::MergeNoOverlapConstraints() {
5566 const int num_constraints = context_->
working_model->constraints_size();
5567 int old_num_no_overlaps = 0;
5568 int old_num_intervals = 0;
5571 std::vector<int> disjunctive_index;
5572 std::vector<std::vector<Literal>> cliques;
5573 for (
int c = 0; c < num_constraints; ++c) {
5575 if (
ct.constraint_case() != ConstraintProto::kNoOverlap)
continue;
5576 std::vector<Literal> clique;
5577 for (
const int i :
ct.no_overlap().intervals()) {
5578 clique.push_back(Literal(BooleanVariable(i),
true));
5580 cliques.push_back(clique);
5581 disjunctive_index.push_back(c);
5583 old_num_no_overlaps++;
5584 old_num_intervals += clique.size();
5586 if (old_num_no_overlaps == 0)
return;
5590 local_model.GetOrCreate<Trail>()->Resize(num_constraints);
5591 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
5592 graph->Resize(num_constraints);
5593 for (
const std::vector<Literal>& clique : cliques) {
5596 CHECK(graph->AddAtMostOne(clique));
5598 CHECK(graph->DetectEquivalences());
5599 graph->TransformIntoMaxCliques(
5600 &cliques, context_->
params().merge_no_overlap_work_limit());
5603 int new_num_no_overlaps = 0;
5604 int new_num_intervals = 0;
5605 for (
int i = 0; i < cliques.size(); ++i) {
5606 const int ct_index = disjunctive_index[i];
5607 ConstraintProto*
ct =
5610 if (cliques[i].empty())
continue;
5611 for (
const Literal l : cliques[i]) {
5612 CHECK(l.IsPositive());
5613 ct->mutable_no_overlap()->add_intervals(l.Variable().value());
5615 new_num_no_overlaps++;
5616 new_num_intervals += cliques[i].size();
5618 if (old_num_intervals != new_num_intervals ||
5619 old_num_no_overlaps != new_num_no_overlaps) {
5620 VLOG(1) << absl::StrCat(
"Merged ", old_num_no_overlaps,
" no-overlaps (",
5621 old_num_intervals,
" intervals) into ",
5622 new_num_no_overlaps,
" no-overlaps (",
5623 new_num_intervals,
" intervals).");
5632void CpModelPresolver::TransformIntoMaxCliques() {
5635 auto convert = [](
int ref) {
5636 if (
RefIsPositive(ref))
return Literal(BooleanVariable(ref),
true);
5637 return Literal(BooleanVariable(
NegatedRef(ref)),
false);
5639 const int num_constraints = context_->
working_model->constraints_size();
5642 std::vector<std::vector<Literal>> cliques;
5644 for (
int c = 0; c < num_constraints; ++c) {
5645 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
5646 if (
ct->constraint_case() == ConstraintProto::kAtMostOne) {
5647 std::vector<Literal> clique;
5648 for (
const int ref :
ct->at_most_one().literals()) {
5649 clique.push_back(convert(ref));
5651 cliques.push_back(clique);
5652 if (RemoveConstraint(
ct)) {
5655 }
else if (
ct->constraint_case() == ConstraintProto::kBoolAnd) {
5656 if (
ct->enforcement_literal().size() != 1)
continue;
5657 const Literal enforcement = convert(
ct->enforcement_literal(0));
5658 for (
const int ref :
ct->bool_and().literals()) {
5659 if (ref ==
ct->enforcement_literal(0))
continue;
5660 cliques.push_back({enforcement, convert(ref).Negated()});
5662 if (RemoveConstraint(
ct)) {
5668 int64_t num_literals_before = 0;
5669 const int num_old_cliques = cliques.size();
5673 const int num_variables = context_->
working_model->variables().size();
5674 local_model.GetOrCreate<Trail>()->Resize(num_variables);
5675 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
5676 graph->Resize(num_variables);
5677 for (
const std::vector<Literal>& clique : cliques) {
5678 num_literals_before += clique.size();
5679 if (!graph->AddAtMostOne(clique)) {
5683 if (!graph->DetectEquivalences()) {
5686 graph->TransformIntoMaxCliques(
5687 &cliques, context_->
params().merge_at_most_one_work_limit());
5692 for (
int var = 0;
var < num_variables; ++
var) {
5693 const Literal l = Literal(BooleanVariable(
var),
true);
5694 if (graph->RepresentativeOf(l) != l) {
5695 const Literal r = graph->RepresentativeOf(l);
5697 var, r.IsPositive() ? r.Variable().value()
5702 int num_new_cliques = 0;
5703 int64_t num_literals_after = 0;
5704 for (
const std::vector<Literal>& clique : cliques) {
5705 if (clique.empty())
continue;
5707 num_literals_after += clique.size();
5709 for (
const Literal
literal : clique) {
5711 ct->mutable_at_most_one()->add_literals(
literal.Variable().value());
5713 ct->mutable_at_most_one()->add_literals(
5719 PresolveAtMostOne(
ct);
5722 if (num_new_cliques != num_old_cliques) {
5723 context_->
UpdateRuleStats(
"at_most_one: transformed into max clique.");
5726 if (num_old_cliques != num_new_cliques ||
5727 num_literals_before != num_literals_after) {
5728 SOLVER_LOG(logger_,
"[MaxClique] Merged ", num_old_cliques,
"(",
5729 num_literals_before,
" literals) into ", num_new_cliques,
"(",
5730 num_literals_after,
" literals) at_most_ones.");
5736bool IsAffineIntAbs(ConstraintProto*
ct) {
5737 if (
ct->constraint_case() != ConstraintProto::kLinMax ||
5738 ct->lin_max().exprs_size() != 2 ||
5739 ct->lin_max().target().vars_size() > 1 ||
5740 ct->lin_max().exprs(0).vars_size() != 1 ||
5741 ct->lin_max().exprs(1).vars_size() != 1) {
5745 const LinearArgumentProto& lin_max =
ct->lin_max();
5746 if (lin_max.exprs(0).offset() != -lin_max.exprs(1).offset())
return false;
5752 const int64_t left_coeff =
RefIsPositive(lin_max.exprs(0).vars(0))
5753 ? lin_max.exprs(0).coeffs(0)
5754 : -lin_max.exprs(0).coeffs(0);
5755 const int64_t right_coeff =
RefIsPositive(lin_max.exprs(1).vars(0))
5756 ? lin_max.exprs(1).coeffs(0)
5757 : -lin_max.exprs(1).coeffs(0);
5758 return left_coeff == -right_coeff;
5765 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
5768 if (ExploitEquivalenceRelations(c,
ct)) {
5773 if (PresolveEnforcementLiteral(
ct)) {
5778 switch (
ct->constraint_case()) {
5779 case ConstraintProto::kBoolOr:
5780 return PresolveBoolOr(
ct);
5781 case ConstraintProto::kBoolAnd:
5782 return PresolveBoolAnd(
ct);
5783 case ConstraintProto::kAtMostOne:
5784 return PresolveAtMostOne(
ct);
5785 case ConstraintProto::kExactlyOne:
5786 return PresolveExactlyOne(
ct);
5787 case ConstraintProto::kBoolXor:
5788 return PresolveBoolXor(
ct);
5789 case ConstraintProto::kLinMax:
5790 if (CanonicalizeLinearArgument(*
ct,
ct->mutable_lin_max())) {
5793 if (IsAffineIntAbs(
ct)) {
5794 return PresolveIntAbs(
ct);
5796 return PresolveLinMax(
ct);
5798 case ConstraintProto::kIntProd:
5799 if (CanonicalizeLinearArgument(*
ct,
ct->mutable_int_prod())) {
5802 return PresolveIntProd(
ct);
5803 case ConstraintProto::kIntDiv:
5804 if (CanonicalizeLinearArgument(*
ct,
ct->mutable_int_div())) {
5807 return PresolveIntDiv(
ct);
5808 case ConstraintProto::kIntMod:
5809 if (CanonicalizeLinearArgument(*
ct,
ct->mutable_int_mod())) {
5812 return PresolveIntMod(
ct);
5813 case ConstraintProto::kLinear: {
5822 for (
const int ref :
ct->linear().vars()) {
5828 if (CanonicalizeLinear(
ct)) {
5831 if (PropagateDomainsInLinear(c,
ct)) {
5834 if (PresolveSmallLinear(
ct)) {
5837 if (PresolveLinearEqualityWithModulo(
ct)) {
5841 if (RemoveSingletonInLinear(
ct)) {
5846 if (PresolveSmallLinear(
ct)) {
5850 if (PresolveSmallLinear(
ct)) {
5853 if (PresolveLinearOnBooleans(
ct)) {
5858 const int old_num_enforcement_literals =
ct->enforcement_literal_size();
5859 ExtractEnforcementLiteralFromLinearConstraint(c,
ct);
5860 if (
ct->enforcement_literal_size() > old_num_enforcement_literals) {
5861 if (DivideLinearByGcd(
ct)) {
5864 if (PresolveSmallLinear(
ct)) {
5869 TryToReduceCoefficientsOfLinearConstraint(c,
ct);
5874 if (DetectAndProcessOneSidedLinearConstraint(c,
ct)) {
5880 case ConstraintProto::kInterval:
5881 return PresolveInterval(c,
ct);
5882 case ConstraintProto::kInverse:
5883 return PresolveInverse(
ct);
5884 case ConstraintProto::kElement:
5885 return PresolveElement(
ct);
5886 case ConstraintProto::kTable:
5887 return PresolveTable(
ct);
5888 case ConstraintProto::kAllDiff:
5889 return PresolveAllDiff(
ct);
5890 case ConstraintProto::kNoOverlap:
5891 return PresolveNoOverlap(
ct);
5892 case ConstraintProto::kNoOverlap2D:
5893 return PresolveNoOverlap2D(c,
ct);
5894 case ConstraintProto::kCumulative:
5895 return PresolveCumulative(
ct);
5896 case ConstraintProto::kCircuit:
5897 return PresolveCircuit(
ct);
5898 case ConstraintProto::kRoutes:
5899 return PresolveRoutes(
ct);
5900 case ConstraintProto::kAutomaton:
5901 return PresolveAutomaton(
ct);
5902 case ConstraintProto::kReservoir:
5903 return PresolveReservoir(
ct);
5910bool CpModelPresolver::ProcessSetPPCSubset(
int subset_c,
int superset_c,
5911 absl::flat_hash_set<int>* tmp_set,
5912 bool* remove_subset,
5913 bool* remove_superset) {
5914 ConstraintProto* subset_ct =
5916 ConstraintProto* superset_ct =
5919 if ((subset_ct->constraint_case() == ConstraintProto::kBoolOr ||
5920 subset_ct->constraint_case() == ConstraintProto::kExactlyOne) &&
5921 (superset_ct->constraint_case() == ConstraintProto::kAtMostOne ||
5922 superset_ct->constraint_case() == ConstraintProto::kExactlyOne)) {
5926 if (subset_ct->constraint_case() == ConstraintProto::kBoolOr) {
5927 tmp_set->insert(subset_ct->bool_or().literals().begin(),
5928 subset_ct->bool_or().literals().end());
5930 tmp_set->insert(subset_ct->exactly_one().literals().begin(),
5931 subset_ct->exactly_one().literals().end());
5937 superset_ct->constraint_case() == ConstraintProto::kAtMostOne
5938 ? superset_ct->at_most_one().literals()
5939 : superset_ct->exactly_one().literals()) {
5940 if (tmp_set->contains(
literal))
continue;
5946 if (superset_ct->constraint_case() != ConstraintProto::kExactlyOne) {
5947 ConstraintProto copy = *superset_ct;
5948 (*superset_ct->mutable_exactly_one()->mutable_literals()) =
5949 copy.at_most_one().literals();
5952 *remove_subset =
true;
5956 if ((subset_ct->constraint_case() == ConstraintProto::kBoolOr ||
5957 subset_ct->constraint_case() == ConstraintProto::kExactlyOne) &&
5958 superset_ct->constraint_case() == ConstraintProto::kBoolOr) {
5960 *remove_superset =
true;
5964 if (subset_ct->constraint_case() == ConstraintProto::kAtMostOne &&
5965 (superset_ct->constraint_case() == ConstraintProto::kAtMostOne ||
5966 superset_ct->constraint_case() == ConstraintProto::kExactlyOne)) {
5968 *remove_subset =
true;
5972 if (subset_ct->constraint_case() == ConstraintProto::kExactlyOne &&
5973 superset_ct->constraint_case() == ConstraintProto::kLinear) {
5981 tmp_set->insert(subset_ct->exactly_one().literals().begin(),
5982 subset_ct->exactly_one().literals().end());
5984 int num_matches = 0;
5985 for (
int i = 0; i < superset_ct->linear().vars().size(); ++i) {
5986 const int var = superset_ct->linear().vars(i);
5987 if (tmp_set->contains(
var)) {
5990 std::min(min_coeff, std::abs(superset_ct->linear().coeffs(i)));
5996 if (num_matches != tmp_set->size())
return true;
6002 for (
int i = 0; i < superset_ct->linear().vars().size(); ++i) {
6003 const int var = superset_ct->linear().vars(i);
6004 int64_t
coeff = superset_ct->linear().coeffs(i);
6005 if (tmp_set->contains(
var)) {
6007 if (
coeff == min_coeff)
continue;
6010 superset_ct->mutable_linear()->set_vars(new_size,
var);
6011 superset_ct->mutable_linear()->set_coeffs(new_size,
coeff);
6015 superset_ct->mutable_linear()->mutable_vars()->Truncate(new_size);
6016 superset_ct->mutable_linear()->mutable_coeffs()->Truncate(new_size);
6019 superset_ct->mutable_linear());
6033void CpModelPresolver::ProcessSetPPC() {
6036 if (context_->
params().presolve_inclusion_work_limit() == 0)
return;
6042 std::vector<int> relevant_constraints;
6043 CompactVectorVector<int> storage;
6045 detector.SetWorkLimit(context_->
params().presolve_inclusion_work_limit());
6048 std::vector<int> temp_literals;
6049 const int num_constraints = context_->
working_model->constraints_size();
6050 for (
int c = 0; c < num_constraints; ++c) {
6051 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
6052 const auto type =
ct->constraint_case();
6053 if (type == ConstraintProto::kBoolOr ||
6054 type == ConstraintProto::kAtMostOne ||
6055 type == ConstraintProto::kExactlyOne) {
6064 temp_literals.clear();
6065 for (
const int ref :
6066 type == ConstraintProto::kAtMostOne ?
ct->at_most_one().literals()
6067 : type == ConstraintProto::kBoolOr ?
ct->bool_or().literals()
6068 :
ct->exactly_one().literals()) {
6069 temp_literals.push_back(
6074 relevant_constraints.push_back(c);
6075 detector.AddPotentialSet(storage.Add(temp_literals));
6076 }
else if (type == ConstraintProto::kLinear) {
6086 const int size =
ct->linear().vars().size();
6087 if (size <= 2)
continue;
6092 temp_literals.clear();
6093 for (
int i = 0; i < size; ++i) {
6094 const int var =
ct->linear().vars(i);
6095 const int64_t
coeff =
ct->linear().coeffs(i);
6098 if (
coeff < 0)
continue;
6099 temp_literals.push_back(
6102 if (temp_literals.size() <= 2)
continue;
6105 relevant_constraints.push_back(c);
6106 detector.AddPotentialSuperset(storage.Add(temp_literals));
6110 int64_t num_inclusions = 0;
6111 absl::flat_hash_set<int> tmp_set;
6112 detector.DetectInclusions([&](
int subset,
int superset) {
6114 bool remove_subset =
false;
6115 bool remove_superset =
false;
6116 const int subset_c = relevant_constraints[subset];
6117 const int superset_c = relevant_constraints[superset];
6118 detector.IncreaseWorkDone(storage[subset].size());
6119 detector.IncreaseWorkDone(storage[superset].size());
6120 if (!ProcessSetPPCSubset(subset_c, superset_c, &tmp_set, &remove_subset,
6121 &remove_superset)) {
6125 if (remove_subset) {
6126 context_->
working_model->mutable_constraints(subset_c)->Clear();
6128 detector.StopProcessingCurrentSubset();
6130 if (remove_superset) {
6131 context_->
working_model->mutable_constraints(superset_c)->Clear();
6133 detector.StopProcessingCurrentSuperset();
6138 " #relevant_constraints=", relevant_constraints.size(),
6139 " #num_inclusions=", num_inclusions,
6140 " work=", detector.work_done(),
" time=",
wall_timer.
Get(),
"s");
6151bool CpModelPresolver::ProcessEncodingFromLinear(
6152 const int linear_encoding_ct_index,
6153 const ConstraintProto& at_most_or_exactly_one, int64_t* num_unique_terms,
6154 int64_t* num_multiple_terms) {
6156 bool in_exactly_one =
false;
6157 absl::flat_hash_map<int, int> var_to_ref;
6158 if (at_most_or_exactly_one.constraint_case() == ConstraintProto::kAtMostOne) {
6159 for (
const int ref : at_most_or_exactly_one.at_most_one().literals()) {
6164 CHECK_EQ(at_most_or_exactly_one.constraint_case(),
6165 ConstraintProto::kExactlyOne);
6166 in_exactly_one =
true;
6167 for (
const int ref : at_most_or_exactly_one.exactly_one().literals()) {
6174 const ConstraintProto& linear_encoding =
6175 context_->
working_model->constraints(linear_encoding_ct_index);
6176 int64_t rhs = linear_encoding.linear().domain(0);
6178 std::vector<std::pair<int, int64_t>> ref_to_coeffs;
6179 const int num_terms = linear_encoding.linear().vars().size();
6180 for (
int i = 0; i < num_terms; ++i) {
6181 const int ref = linear_encoding.linear().vars(i);
6182 const int64_t
coeff = linear_encoding.linear().coeffs(i);
6183 const auto it = var_to_ref.find(
PositiveRef(ref));
6185 if (it == var_to_ref.end()) {
6194 if (it->second == ref) {
6196 ref_to_coeffs.push_back({ref,
coeff});
6208 context_->
UpdateRuleStats(
"encoding: candidate linear is all Boolean now.");
6213 std::vector<int64_t> all_values;
6214 absl::btree_map<int64_t, std::vector<int>> value_to_refs;
6215 for (
const auto& [ref,
coeff] : ref_to_coeffs) {
6217 all_values.push_back(
value);
6218 value_to_refs[
value].push_back(ref);
6222 for (
const auto& [
var, ref] : var_to_ref) {
6223 all_values.push_back(rhs);
6224 value_to_refs[rhs].push_back(ref);
6226 if (!in_exactly_one) {
6229 all_values.push_back(rhs);
6234 bool domain_reduced =
false;
6238 if (domain_reduced) {
6244 context_->
UpdateRuleStats(
"encoding: candidate linear is all Boolean now.");
6249 absl::flat_hash_set<int64_t> value_set;
6250 for (
const int64_t v : context_->
DomainOf(target_ref).
Values()) {
6251 value_set.insert(v);
6253 for (
const auto& [
value, literals] : value_to_refs) {
6255 if (!value_set.contains(
value)) {
6256 for (
const int lit : literals) {
6262 if (literals.size() == 1 && (in_exactly_one ||
value != rhs)) {
6265 ++*num_unique_terms;
6270 ++*num_multiple_terms;
6271 const int associated_lit =
6273 for (
const int lit : literals) {
6279 if (in_exactly_one ||
value != rhs) {
6285 context_->
working_model->add_constraints()->mutable_bool_or();
6286 for (
const int lit : literals) bool_or->add_literals(lit);
6287 bool_or->add_literals(
NegatedRef(associated_lit));
6293 context_->
working_model->mutable_constraints(linear_encoding_ct_index)
6300void CpModelPresolver::DetectDuplicateConstraints() {
6318 const std::vector<std::pair<int, int>> duplicates =
6320 for (
const auto& [dup, rep] : duplicates) {
6327 : context_->
working_model->constraints(rep).constraint_case();
6331 if (type == ConstraintProto::kLinear) {
6336 if (rep_domain != d) {
6347 context_->
UpdateRuleStats(
"duplicate: merged rhs of linear constraint");
6348 const Domain rhs = rep_domain.IntersectionWith(d);
6349 if (rhs.IsEmpty()) {
6350 if (!MarkConstraintAsFalse(
6352 SOLVER_LOG(logger_,
"Unsat after merging two linear constraints");
6363 ->mutable_linear());
6369 "duplicate: linear constraint parallel to objective");
6370 const Domain objective_domain =
6374 if (objective_domain != d) {
6376 const Domain new_domain = objective_domain.IntersectionWith(d);
6377 if (new_domain.IsEmpty()) {
6379 "Constraint parallel to the objective makes the objective domain "
6399 SOLVER_LOG(logger_,
"[DetectDuplicateConstraints]",
6400 " #duplicates=", duplicates.size(),
" time=",
wall_timer.
Get(),
6404void CpModelPresolver::DetectDominatedLinearConstraints() {
6407 if (context_->
params().presolve_inclusion_work_limit() == 0)
return;
6415 detector.SetWorkLimit(context_->
params().presolve_inclusion_work_limit());
6419 std::vector<int> constraint_indices_to_clean;
6423 absl::flat_hash_map<int, Domain> cached_expr_domain;
6425 const int num_constraints = context_->
working_model->constraints().size();
6426 for (
int c = 0; c < num_constraints; ++c) {
6428 if (!
ct.enforcement_literal().empty())
continue;
6429 if (
ct.constraint_case() != ConstraintProto::kLinear)
continue;
6432 const LinearConstraintProto& lin =
ct.linear();
6434 for (
int i = 0; i < lin.vars().size(); ++i) {
6435 const int var = lin.vars(i);
6436 const int64_t
coeff = lin.coeffs(i);
6446 .RelaxIfTooComplex();
6448 if (abort)
continue;
6451 detector.AddPotentialSet(c);
6452 cached_expr_domain[c] = std::move(implied);
6455 int64_t num_inclusions = 0;
6456 absl::flat_hash_map<int, int64_t> coeff_map;
6457 detector.DetectInclusions([&](
int subset_c,
int superset_c) {
6461 const LinearConstraintProto& subset_lin =
6464 detector.IncreaseWorkDone(subset_lin.vars().size());
6465 for (
int i = 0; i < subset_lin.vars().size(); ++i) {
6466 coeff_map[subset_lin.vars(i)] = subset_lin.coeffs(i);
6472 bool perfect_match =
true;
6479 const LinearConstraintProto& superset_lin =
6481 Domain diff_domain(0);
6482 detector.IncreaseWorkDone(superset_lin.vars().size());
6483 for (
int i = 0; i < superset_lin.vars().size(); ++i) {
6484 const int var = superset_lin.vars(i);
6485 int64_t
coeff = superset_lin.coeffs(i);
6486 const auto it = coeff_map.find(
var);
6487 if (it != coeff_map.end()) {
6488 const int64_t subset_coeff = it->second;
6489 if (perfect_match) {
6490 if (
coeff % subset_coeff == 0) {
6491 const int64_t div =
coeff / subset_coeff;
6495 }
else if (factor != div) {
6496 perfect_match =
false;
6499 perfect_match =
false;
6504 coeff -= subset_coeff;
6506 if (
coeff == 0)
continue;
6511 .RelaxIfTooComplex();
6519 const Domain implied_superset_domain =
6520 subset_ct_domain.AdditionWith(diff_domain)
6521 .IntersectionWith(cached_expr_domain[superset_c]);
6522 if (implied_superset_domain.IsIncludedIn(superset_ct_domain)) {
6524 "linear inclusion: redundant containing constraint");
6529 context_->
working_model->mutable_constraints(superset_c)->Clear();
6530 constraint_indices_to_clean.push_back(superset_c);
6531 detector.StopProcessingCurrentSuperset();
6536 const Domain implied_subset_domain =
6537 superset_ct_domain.AdditionWith(diff_domain.Negation())
6538 .IntersectionWith(cached_expr_domain[subset_c]);
6539 if (implied_subset_domain.IsIncludedIn(subset_ct_domain)) {
6541 "linear inclusion: redundant included constraint");
6546 context_->
working_model->mutable_constraints(subset_c)->Clear();
6547 constraint_indices_to_clean.push_back(subset_c);
6548 detector.StopProcessingCurrentSubset();
6555 if (perfect_match) {
6557 if (subset_ct_domain.IsFixed()) {
6566 auto* mutable_linear =
6569 for (
int i = 0; i < mutable_linear->vars().size(); ++i) {
6570 const int var = mutable_linear->vars(i);
6571 const int64_t
coeff = mutable_linear->coeffs(i);
6572 const auto it = coeff_map.find(
var);
6573 if (it != coeff_map.end()) {
6577 mutable_linear->set_vars(new_size,
var);
6578 mutable_linear->set_coeffs(new_size,
coeff);
6581 mutable_linear->mutable_vars()->Truncate(new_size);
6582 mutable_linear->mutable_coeffs()->Truncate(new_size);
6584 subset_ct_domain.MultiplicationBy(-factor)),
6586 constraint_indices_to_clean.push_back(superset_c);
6587 detector.StopProcessingCurrentSuperset();
6590 if (superset_ct_domain.IsFixed()) {
6593 "TODO linear inclusion: superset is equality");
6598 for (
const int c : constraint_indices_to_clean) {
6602 SOLVER_LOG(logger_,
"[DetectDominatedLinearConstraints]",
6603 " #relevant_constraints=", detector.num_potential_supersets(),
6604 " #work_done=", detector.work_done(),
6605 " #num_inclusions=", num_inclusions,
6606 " #num_redundant=", constraint_indices_to_clean.size(),
6610bool CpModelPresolver::IsImpliedFree(
const std::vector<int>& constraints,
int x,
6611 int64_t* work_done) {
6612 const Domain dx = context_->
DomainOf(x);
6614 for (
const int c : constraints) {
6617 if (!
ct.enforcement_literal().empty())
continue;
6618 const LinearConstraintProto& lin =
ct.linear();
6623 int64_t min_sum = 0;
6624 int64_t max_sum = 0;
6626 int64_t coeff_x = 0;
6627 const int num_terms = lin.vars().size();
6628 *work_done += num_terms;
6629 for (
int i = 0; i < num_terms; ++i) {
6630 const int v = lin.vars(i);
6631 const int64_t
coeff = lin.coeffs(i);
6647 overall_implied_domain = overall_implied_domain.IntersectionWith(
6649 .AdditionWith(Domain(-max_sum, -min_sum))
6650 .InverseMultiplicationBy(coeff_x));
6651 if (overall_implied_domain.IsIncludedIn(dx)) {
6658void CpModelPresolver::PerformFreeColumnSubstitution(
6659 const std::vector<std::pair<int, int64_t>>& constraints_with_x_coeff,
int x,
6660 int y, int64_t factor) {
6662 DCHECK_EQ(constraints_with_x_coeff.size(),
6675 int num_cancelled = 0;
6677 for (
const auto& [c, coeff_x] : constraints_with_x_coeff) {
6678 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
6679 LinearConstraintProto* mutable_linear =
ct->mutable_linear();
6684 const int num_terms = mutable_linear->vars().size();
6685 for (
int i = 0; i < num_terms; ++i) {
6686 const int var = mutable_linear->vars(i);
6687 const int64_t
coeff = mutable_linear->coeffs(i);
6689 int64_t new_coeff =
coeff;
6694 }
else if (
var == y) {
6696 new_coeff =
coeff - factor * coeff_x;
6698 if (new_coeff == 0) {
6702 mutable_linear->set_vars(new_size, new_var);
6703 mutable_linear->set_coeffs(new_size, new_coeff);
6706 mutable_linear->mutable_vars()->Truncate(new_size);
6707 mutable_linear->mutable_coeffs()->Truncate(new_size);
6710 mutable_linear->add_vars(y);
6711 mutable_linear->add_coeffs(-factor * coeff_x);
6722 LinearConstraintProto* lin =
6723 context_->
mapping_model->add_constraints()->mutable_linear();
6727 lin->add_coeffs(factor);
6729 lin->add_coeffs(-1);
6738 context_->
UpdateRuleStats(
"columns: dual sparsify using substitution");
6746void CpModelPresolver::DetectOverlappingColumns() {
6749 if (context_->
params().presolve_inclusion_work_limit() == 0)
return;
6755 const int kMinReduction = 3;
6764 int num_equal_coeff;
6765 int num_opposite_coeff;
6767 std::vector<ColInfo> infos;
6768 std::vector<bool> processed;
6769 std::vector<int> to_clean;
6781 int num_processed = 0;
6782 int64_t work_done = 0;
6783 int64_t nz_reduction = 0;
6784 const int64_t kMaxWork = 1e7;
6785 std::vector<int> constraints;
6786 for (
int x = 0; x < context_->
working_model->variables().size(); ++x) {
6787 if (work_done > kMaxWork)
break;
6796 if (constraints.size() < kMinReduction)
continue;
6797 std::sort(constraints.begin(), constraints.end());
6800 for (
const int c : constraints) {
6807 if (
ct.constraint_case() != ConstraintProto::kLinear) {
6814 for (
const int ref :
ct.enforcement_literal()) {
6825 const int num_variables = context_->
working_model->variables().size();
6826 infos.resize(num_variables);
6827 processed.resize(num_variables,
false);
6837 if (!IsImpliedFree(constraints, x, &work_done))
continue;
6846 int only_candidate = kNoRestriction;
6848 std::vector<std::pair<int, int64_t>> constraints_with_x_coeff;
6849 for (
const int c : constraints) {
6850 const LinearConstraintProto& lin =
6852 const int num_terms = lin.vars().size();
6854 if (num_terms == 2) {
6855 for (
int i = 0; i < num_terms; ++i) {
6856 if (lin.vars(i) == x)
continue;
6857 if (only_candidate == kNoRestriction) {
6858 only_candidate = lin.vars(i);
6859 }
else if (only_candidate != lin.vars(i)) {
6868 int64_t x_coeff = 0;
6869 for (
int i = 0; i < num_terms; ++i) {
6871 const int var = lin.vars(i);
6878 x_coeff = lin.coeffs(i);
6884 constraints_with_x_coeff.push_back({c, x_coeff});
6890 work_done += num_terms;
6891 for (
int i = 0; i < num_terms; ++i) {
6892 const int var = lin.vars(i);
6893 if (
var == x)
continue;
6894 if (!processed[
var]) {
6895 to_clean.push_back(
var);
6896 processed[
var] =
true;
6897 infos[
var].num_seen = 0;
6898 infos[
var].num_opposite_coeff = 0;
6899 infos[
var].num_equal_coeff = 0;
6901 infos[
var].num_seen++;
6902 if (lin.coeffs(i) == x_coeff) infos[
var].num_equal_coeff++;
6903 if (lin.coeffs(i) == -x_coeff) infos[
var].num_opposite_coeff++;
6907 for (
const int var : to_clean) processed[
var] =
false;
6918 int best_nz_reduction =
6919 std::max(kMinReduction - 1,
static_cast<int>(constraints.size()) / 4);
6922 int best_choice = -1;
6923 int64_t best_factor;
6924 CHECK(!processed[x]);
6925 for (
const int var : to_clean) {
6926 if (only_candidate != kNoRestriction &&
var != only_candidate) {
6927 processed[
var] =
false;
6931 const int num_not_seen = constraints.size() - infos[
var].num_seen;
6933 const int nz_reduction = infos[
var].num_equal_coeff - num_not_seen;
6934 if (nz_reduction > best_nz_reduction) {
6936 best_nz_reduction = nz_reduction;
6941 const int nz_reduction = infos[
var].num_opposite_coeff - num_not_seen;
6942 if (nz_reduction > best_nz_reduction) {
6944 best_nz_reduction = nz_reduction;
6950 processed[
var] =
false;
6954 if (best_choice >= 0) {
6955 nz_reduction += best_nz_reduction;
6956 PerformFreeColumnSubstitution(constraints_with_x_coeff, x, best_choice,
6962 SOLVER_LOG(logger_,
"[DetectOverlappingColumns]",
6963 " #processed_columns=", num_processed,
" #work_done=", work_done,
6964 " #nz_reduction=", nz_reduction,
" time=",
wall_timer.
Get(),
"s");
6967void CpModelPresolver::ExtractEncodingFromLinear() {
6970 if (context_->
params().presolve_inclusion_work_limit() == 0)
return;
6976 std::vector<int> relevant_constraints;
6977 CompactVectorVector<int> storage;
6979 detector.SetWorkLimit(context_->
params().presolve_inclusion_work_limit());
6985 std::vector<int> vars;
6986 const int num_constraints = context_->
working_model->constraints().size();
6987 for (
int c = 0; c < num_constraints; ++c) {
6989 switch (
ct.constraint_case()) {
6990 case ConstraintProto::kAtMostOne: {
6992 for (
const int ref :
ct.at_most_one().literals()) {
6995 relevant_constraints.push_back(c);
6996 detector.AddPotentialSuperset(storage.Add(vars));
6999 case ConstraintProto::kExactlyOne: {
7001 for (
const int ref :
ct.exactly_one().literals()) {
7004 relevant_constraints.push_back(c);
7005 detector.AddPotentialSuperset(storage.Add(vars));
7008 case ConstraintProto::kLinear: {
7010 if (!
ct.enforcement_literal().empty())
continue;
7011 if (
ct.linear().domain().size() != 2)
continue;
7012 if (
ct.linear().domain(0) !=
ct.linear().domain(1))
continue;
7016 bool is_candidate =
true;
7017 int num_integers = 0;
7019 const int num_terms =
ct.linear().vars().size();
7020 for (
int i = 0; i < num_terms; ++i) {
7021 const int ref =
ct.linear().vars(i);
7026 if (std::abs(
ct.linear().coeffs(i)) != 1) {
7027 is_candidate =
false;
7030 if (num_integers == 2) {
7031 is_candidate =
false;
7039 if (is_candidate && num_integers == 1 && vars.size() > 1) {
7040 relevant_constraints.push_back(c);
7041 detector.AddPotentialSubset(storage.Add(vars));
7051 int64_t num_exactly_one_encodings = 0;
7052 int64_t num_at_most_one_encodings = 0;
7053 int64_t num_literals = 0;
7054 int64_t num_unique_terms = 0;
7055 int64_t num_multiple_terms = 0;
7057 detector.DetectInclusions([&](
int subset,
int superset) {
7058 const int subset_c = relevant_constraints[subset];
7059 const int superset_c = relevant_constraints[superset];
7060 const ConstraintProto& superset_ct =
7062 if (superset_ct.constraint_case() == ConstraintProto::kAtMostOne) {
7063 ++num_at_most_one_encodings;
7065 ++num_exactly_one_encodings;
7067 num_literals += storage[subset].size();
7070 if (!ProcessEncodingFromLinear(subset_c, superset_ct, &num_unique_terms,
7071 &num_multiple_terms)) {
7075 detector.StopProcessingCurrentSubset();
7078 SOLVER_LOG(logger_,
"[ExtractEncodingFromLinear]",
7079 " #potential_supersets=", detector.num_potential_supersets(),
7080 " #potential_subsets=", detector.num_potential_subsets(),
7081 " #at_most_one_encodings=", num_at_most_one_encodings,
7082 " #exactly_one_encodings=", num_exactly_one_encodings,
7083 " #unique_terms=", num_unique_terms,
7084 " #multiple_terms=", num_multiple_terms,
7085 " #literals=", num_literals,
" time=",
wall_timer.
Get(),
"s");
7099void CpModelPresolver::LookAtVariableWithDegreeTwo(
int var) {
7111 Domain union_of_domain;
7112 int num_positive = 0;
7113 std::vector<int> constraint_indices_to_remove;
7119 constraint_indices_to_remove.push_back(c);
7121 if (
ct.enforcement_literal().size() != 1 ||
7123 ct.constraint_case() != ConstraintProto::kLinear ||
7124 ct.linear().vars().size() != 1) {
7128 if (
ct.enforcement_literal(0) ==
var) ++num_positive;
7129 if (ct_var != -1 &&
PositiveRef(
ct.linear().vars(0)) != ct_var) {
7134 union_of_domain = union_of_domain.UnionWith(
7137 ?
ct.linear().coeffs(0)
7138 : -
ct.linear().coeffs(0)));
7141 if (num_positive != 1)
return;
7144 context_->
UpdateRuleStats(
"variables: removable enforcement literal");
7145 for (
const int c : constraint_indices_to_remove) {
7149 ->mutable_constraints(context_->
mapping_model->constraints().size() - 1)
7165void CpModelPresolver::ProcessVariableOnlyUsedInEncoding(
int var) {
7171 if (context_->
params().search_branching() == SatParameters::FIXED_SEARCH) {
7181 absl::flat_hash_set<int64_t> values_set;
7182 absl::flat_hash_map<int64_t, std::vector<int>> value_to_equal_literals;
7183 absl::flat_hash_map<int64_t, std::vector<int>> value_to_not_equal_literals;
7186 if (c < 0)
continue;
7188 CHECK_EQ(
ct.constraint_case(), ConstraintProto::kLinear);
7190 int64_t
coeff =
ct.linear().coeffs(0);
7191 if (std::abs(
coeff) != 1 ||
ct.enforcement_literal().size() != 1) {
7199 if (rhs.IsFixed()) {
7205 values_set.insert(rhs.FixedValue());
7206 value_to_equal_literals[rhs.FixedValue()].push_back(
7207 ct.enforcement_literal(0));
7210 const Domain complement =
7212 if (complement.IsEmpty()) {
7217 if (complement.IsFixed()) {
7219 values_set.insert(complement.FixedValue());
7220 value_to_not_equal_literals[complement.FixedValue()].push_back(
7221 ct.enforcement_literal(0));
7232 }
else if (value_to_not_equal_literals.empty() &&
7233 value_to_equal_literals.empty()) {
7240 std::vector<int64_t> encoded_values(values_set.begin(), values_set.end());
7241 std::sort(encoded_values.begin(), encoded_values.end());
7242 CHECK(!encoded_values.empty());
7243 const bool is_fully_encoded =
7249 for (
const int64_t v : encoded_values) {
7251 const auto eq_it = value_to_equal_literals.find(v);
7252 if (eq_it != value_to_equal_literals.end()) {
7253 for (
const int lit : eq_it->second) {
7257 const auto neq_it = value_to_not_equal_literals.find(v);
7258 if (neq_it != value_to_not_equal_literals.end()) {
7259 for (
const int lit : neq_it->second) {
7267 Domain other_values;
7268 if (!is_fully_encoded) {
7278 if (is_fully_encoded) {
7282 obj_coeff > 0 ? encoded_values.front() : encoded_values.back();
7291 Domain(obj_coeff > 0 ? other_values.Min() : other_values.Max());
7292 min_value = other_values.FixedValue();
7297 int64_t accumulated = std::abs(min_value);
7298 for (
const int64_t
value : encoded_values) {
7302 "TODO variables: only used in objective and in encoding");
7307 ConstraintProto encoding_ct;
7308 LinearConstraintProto* linear = encoding_ct.mutable_linear();
7309 const int64_t coeff_in_equality = -1;
7310 linear->add_vars(
var);
7311 linear->add_coeffs(coeff_in_equality);
7313 linear->add_domain(-min_value);
7314 linear->add_domain(-min_value);
7315 for (
const int64_t
value : encoded_values) {
7316 if (
value == min_value)
continue;
7320 linear->add_vars(enf);
7321 linear->add_coeffs(
coeff);
7324 linear->set_domain(0, encoding_ct.linear().domain(0) -
coeff);
7325 linear->set_domain(1, encoding_ct.linear().domain(1) -
coeff);
7327 linear->add_coeffs(-
coeff);
7333 "TODO variables: only used in objective and in encoding");
7337 "variables: only used in objective and in encoding");
7344 for (
const int c : copy) {
7345 if (c < 0)
continue;
7352 for (
const int64_t
value : encoded_values) {
7355 ct->add_enforcement_literal(enf);
7356 ct->mutable_linear()->add_vars(
var);
7357 ct->mutable_linear()->add_coeffs(1);
7358 ct->mutable_linear()->add_domain(
value);
7359 ct->mutable_linear()->add_domain(
value);
7363 ConstraintProto* new_ct = context_->
working_model->add_constraints();
7364 if (is_fully_encoded) {
7366 for (
const int64_t
value : encoded_values) {
7367 new_ct->mutable_exactly_one()->add_literals(
7370 PresolveExactlyOne(new_ct);
7373 ConstraintProto* mapping_ct = context_->
mapping_model->add_constraints();
7374 mapping_ct->mutable_linear()->add_vars(
var);
7375 mapping_ct->mutable_linear()->add_coeffs(1);
7378 for (
const int64_t
value : encoded_values) {
7381 new_ct->mutable_at_most_one()->add_literals(
literal);
7383 PresolveAtMostOne(new_ct);
7390void CpModelPresolver::TryToSimplifyDomain(
int var) {
7399 if (r.representative !=
var)
return;
7405 if (domain.Size() == 2 && (domain.Min() != 0 || domain.Max() != 1)) {
7410 if (domain.NumIntervals() != domain.Size())
return;
7412 const int64_t var_min = domain.Min();
7413 int64_t gcd = domain[1].start - var_min;
7415 const ClosedInterval& i = domain[
index];
7417 const int64_t shifted_value = i.start - var_min;
7421 if (gcd == 1)
break;
7423 if (gcd == 1)
return;
7430void CpModelPresolver::EncodeAllAffineRelations() {
7431 int64_t num_added = 0;
7436 if (r.representative ==
var)
continue;
7443 if (!PresolveAffineRelationIfAny(
var))
break;
7450 auto* arg =
ct->mutable_linear();
7453 arg->add_vars(r.representative);
7454 arg->add_coeffs(-r.coeff);
7455 arg->add_domain(r.offset);
7456 arg->add_domain(r.offset);
7464 if (num_added > 0) {
7465 SOLVER_LOG(logger_, num_added,
" affine relations still in the model.");
7470bool CpModelPresolver::PresolveAffineRelationIfAny(
int var) {
7472 if (r.representative ==
var)
return true;
7491 auto* arg =
ct->mutable_linear();
7494 arg->add_vars(r.representative);
7495 arg->add_coeffs(-r.coeff);
7496 arg->add_domain(r.offset);
7497 arg->add_domain(r.offset);
7503void CpModelPresolver::PresolveToFixPoint() {
7507 const int64_t max_num_operations =
7508 context_->
params().debug_max_num_presolve_operations() > 0
7509 ? context_->
params().debug_max_num_presolve_operations()
7515 absl::flat_hash_set<std::pair<int, int>> var_constraint_pair_already_called;
7520 std::vector<bool> in_queue(context_->
working_model->constraints_size(),
7522 std::deque<int> queue;
7523 for (
int c = 0; c < in_queue.size(); ++c) {
7524 if (context_->
working_model->constraints(c).constraint_case() !=
7525 ConstraintProto::CONSTRAINT_NOT_SET) {
7535 if (context_->
params().permute_presolve_constraint_order()) {
7536 std::shuffle(queue.begin(), queue.end(), *context_->
random());
7538 std::sort(queue.begin(), queue.end(), [
this](
int a,
int b) {
7539 const int score_a = context_->ConstraintToVars(a).size();
7540 const int score_b = context_->ConstraintToVars(b).size();
7541 return score_a < score_b || (score_a == score_b && a < b);
7551 const int c = queue.front();
7552 in_queue[c] =
false;
7555 const int old_num_constraint =
7559 SOLVER_LOG(logger_,
"Unsat after presolving constraint #", c,
7560 " (warning, dump might be inconsistent): ",
7561 context_->
working_model->constraints(c).ShortDebugString());
7565 const int new_num_constraints =
7567 if (new_num_constraints > old_num_constraint) {
7569 in_queue.resize(new_num_constraints,
true);
7570 for (
int c = old_num_constraint; c < new_num_constraints; ++c) {
7584 in_queue.resize(context_->
working_model->constraints_size(),
false);
7585 const auto& vector_that_can_grow_during_iter =
7587 for (
int i = 0; i < vector_that_can_grow_during_iter.size(); ++i) {
7588 const int v = vector_that_can_grow_during_iter[i];
7593 if (!PresolveAffineRelationIfAny(v))
return;
7596 if (degree == 0)
continue;
7597 if (degree == 2) LookAtVariableWithDegreeTwo(v);
7602 if (degree != 1)
continue;
7604 if (c < 0)
continue;
7609 if (var_constraint_pair_already_called.contains(
7610 std::pair<int, int>(v, c))) {
7613 var_constraint_pair_already_called.insert({v, c});
7622 for (
int i = 0; i < 2; ++i) {
7627 in_queue.resize(context_->
working_model->constraints_size(),
false);
7628 const auto& vector_that_can_grow_during_iter =
7630 for (
int i = 0; i < vector_that_can_grow_during_iter.size(); ++i) {
7631 const int v = vector_that_can_grow_during_iter[i];
7633 if (!PresolveAffineRelationIfAny(v))
return;
7636 TryToSimplifyDomain(v);
7645 in_queue.resize(context_->
working_model->constraints_size(),
false);
7647 if (c >= 0 && !in_queue[c]) {
7656 if (!queue.empty() || i == 1)
break;
7659 for (
int v = 0; v < context_->
working_model->variables().size(); ++v) {
7660 ProcessVariableOnlyUsedInEncoding(v);
7671 VarDomination var_dom;
7672 DualBoundStrengthening dual_bound_strengthening;
7674 &dual_bound_strengthening);
7675 if (!dual_bound_strengthening.Strengthen(context_))
return;
7687 std::sort(queue.begin(), queue.end());
7700 const int num_constraints = context_->
working_model->constraints_size();
7701 for (
int c = 0; c < num_constraints; ++c) {
7702 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
7703 switch (
ct->constraint_case()) {
7704 case ConstraintProto::kNoOverlap:
7706 if (PresolveNoOverlap(
ct)) {
7710 case ConstraintProto::kNoOverlap2D:
7712 if (PresolveNoOverlap2D(c,
ct)) {
7716 case ConstraintProto::kCumulative:
7718 if (PresolveCumulative(
ct)) {
7722 case ConstraintProto::kBoolOr: {
7725 for (
const auto& pair :
7727 bool modified =
false;
7749 const CpModelProto& in_model) {
7750 if (context_->
params().ignore_names()) {
7753 in_model.variables_size());
7754 for (
const IntegerVariableProto& var_proto : in_model.variables()) {
7755 *context_->
working_model->add_variables()->mutable_domain() =
7759 *context_->
working_model->mutable_variables() = in_model.variables();
7769 const CpModelProto& in_model,
const std::vector<int>& ignored_constraints,
7771 const absl::flat_hash_set<int> ignored_constraints_set(
7772 ignored_constraints.begin(), ignored_constraints.end());
7774 const bool ignore_names = context_->
params().ignore_names();
7778 std::vector<int> constraints_using_intervals;
7780 starting_constraint_index_ = context_->
working_model->constraints_size();
7781 for (
int c = 0; c < in_model.constraints_size(); ++c) {
7782 if (ignored_constraints_set.contains(c))
continue;
7784 const ConstraintProto&
ct = in_model.constraints(c);
7785 if (OneEnforcementLiteralIsFalse(
ct))
continue;
7790 switch (
ct.constraint_case()) {
7791 case ConstraintProto::CONSTRAINT_NOT_SET:
7793 case ConstraintProto::kBoolOr:
7795 if (!CopyBoolOrWithDupSupport(
ct))
return CreateUnsatModel();
7797 if (!CopyBoolOr(
ct))
return CreateUnsatModel();
7800 case ConstraintProto::kBoolAnd:
7801 if (!CopyBoolAnd(
ct))
return CreateUnsatModel();
7803 case ConstraintProto::kLinear:
7804 if (!CopyLinear(
ct))
return CreateUnsatModel();
7806 case ConstraintProto::kAtMostOne:
7807 if (!CopyAtMostOne(
ct))
return CreateUnsatModel();
7809 case ConstraintProto::kExactlyOne:
7810 if (!CopyExactlyOne(
ct))
return CreateUnsatModel();
7812 case ConstraintProto::kInterval:
7813 if (!CopyInterval(
ct, c, ignore_names))
return CreateUnsatModel();
7815 case ConstraintProto::kNoOverlap:
7817 constraints_using_intervals.push_back(c);
7819 CopyAndMapNoOverlap(
ct);
7822 case ConstraintProto::kNoOverlap2D:
7824 constraints_using_intervals.push_back(c);
7826 CopyAndMapNoOverlap2D(
ct);
7829 case ConstraintProto::kCumulative:
7831 constraints_using_intervals.push_back(c);
7833 CopyAndMapCumulative(
ct);
7837 ConstraintProto* new_ct = context_->
working_model->add_constraints();
7841 new_ct->clear_name();
7848 DCHECK(first_copy || constraints_using_intervals.empty());
7849 for (
const int c : constraints_using_intervals) {
7850 const ConstraintProto&
ct = in_model.constraints(c);
7851 switch (
ct.constraint_case()) {
7852 case ConstraintProto::kNoOverlap:
7853 CopyAndMapNoOverlap(
ct);
7855 case ConstraintProto::kNoOverlap2D:
7856 CopyAndMapNoOverlap2D(
ct);
7858 case ConstraintProto::kCumulative:
7859 CopyAndMapCumulative(
ct);
7862 LOG(DFATAL) <<
"Shouldn't be here.";
7869void ModelCopy::CopyEnforcementLiterals(
const ConstraintProto& orig,
7870 ConstraintProto* dest) {
7871 temp_enforcement_literals_.clear();
7872 for (
const int lit : orig.enforcement_literal()) {
7874 skipped_non_zero_++;
7877 temp_enforcement_literals_.push_back(lit);
7879 dest->mutable_enforcement_literal()->Add(temp_enforcement_literals_.begin(),
7880 temp_enforcement_literals_.end());
7883bool ModelCopy::OneEnforcementLiteralIsFalse(
const ConstraintProto&
ct)
const {
7884 for (
const int lit :
ct.enforcement_literal()) {
7892bool ModelCopy::CopyBoolOr(
const ConstraintProto&
ct) {
7893 temp_literals_.clear();
7894 for (
const int lit :
ct.enforcement_literal()) {
7898 for (
const int lit :
ct.bool_or().literals()) {
7903 skipped_non_zero_++;
7905 temp_literals_.push_back(lit);
7911 ->mutable_literals()
7912 ->Add(temp_literals_.begin(), temp_literals_.end());
7913 return !temp_literals_.empty();
7916bool ModelCopy::CopyBoolOrWithDupSupport(
const ConstraintProto&
ct) {
7917 temp_literals_.clear();
7918 tmp_literals_set_.clear();
7919 for (
const int enforcement_lit :
ct.enforcement_literal()) {
7930 skipped_non_zero_++;
7933 if (tmp_literals_set_.contains(
NegatedRef(lit))) {
7937 const auto [it, inserted] = tmp_literals_set_.insert(lit);
7938 if (inserted) temp_literals_.push_back(lit);
7940 for (
const int lit :
ct.bool_or().literals()) {
7946 skipped_non_zero_++;
7949 if (tmp_literals_set_.contains(
NegatedRef(lit))) {
7953 const auto [it, inserted] = tmp_literals_set_.insert(lit);
7954 if (inserted) temp_literals_.push_back(lit);
7959 ->mutable_literals()
7960 ->Add(temp_literals_.begin(), temp_literals_.end());
7961 return !temp_literals_.empty();
7964bool ModelCopy::CopyBoolAnd(
const ConstraintProto&
ct) {
7965 bool at_least_one_false =
false;
7966 int num_non_fixed_literals = 0;
7967 for (
const int lit :
ct.bool_and().literals()) {
7969 at_least_one_false =
true;
7973 num_non_fixed_literals++;
7977 if (at_least_one_false) {
7978 ConstraintProto* new_ct = context_->
working_model->add_constraints();
7979 BoolArgumentProto* bool_or = new_ct->mutable_bool_or();
7982 for (
const int lit :
ct.enforcement_literal()) {
7984 skipped_non_zero_++;
7989 return !bool_or->literals().empty();
7990 }
else if (num_non_fixed_literals > 0) {
7991 ConstraintProto* new_ct = context_->
working_model->add_constraints();
7992 CopyEnforcementLiterals(
ct, new_ct);
7993 BoolArgumentProto* bool_and = new_ct->mutable_bool_and();
7994 bool_and->mutable_literals()->Reserve(num_non_fixed_literals);
7995 for (
const int lit :
ct.bool_and().literals()) {
7997 skipped_non_zero_++;
8000 bool_and->add_literals(lit);
8006bool ModelCopy::CopyLinear(
const ConstraintProto&
ct) {
8007 non_fixed_variables_.clear();
8008 non_fixed_coefficients_.clear();
8010 for (
int i = 0; i <
ct.linear().vars_size(); ++i) {
8011 const int ref =
ct.linear().vars(i);
8012 const int64_t
coeff =
ct.linear().coeffs(i);
8015 skipped_non_zero_++;
8017 non_fixed_variables_.push_back(ref);
8018 non_fixed_coefficients_.push_back(
coeff);
8022 const Domain new_domain =
8024 if (non_fixed_variables_.empty() && !new_domain.Contains(0)) {
8025 if (
ct.enforcement_literal().empty()) {
8028 temp_literals_.clear();
8029 for (
const int literal :
ct.enforcement_literal()) {
8031 skipped_non_zero_++;
8038 ->mutable_literals()
8039 ->Add(temp_literals_.begin(), temp_literals_.end());
8040 return !temp_literals_.empty();
8045 ConstraintProto* new_ct = context_->
working_model->add_constraints();
8046 CopyEnforcementLiterals(
ct, new_ct);
8047 LinearConstraintProto* linear = new_ct->mutable_linear();
8048 linear->mutable_vars()->Add(non_fixed_variables_.begin(),
8049 non_fixed_variables_.end());
8050 linear->mutable_coeffs()->Add(non_fixed_coefficients_.begin(),
8051 non_fixed_coefficients_.end());
8056bool ModelCopy::CopyAtMostOne(
const ConstraintProto&
ct) {
8058 temp_literals_.clear();
8059 for (
const int lit :
ct.at_most_one().literals()) {
8061 skipped_non_zero_++;
8064 temp_literals_.push_back(lit);
8068 if (temp_literals_.size() <= 1)
return true;
8069 if (num_true > 1)
return false;
8072 ConstraintProto* new_ct = context_->
working_model->add_constraints();
8073 CopyEnforcementLiterals(
ct, new_ct);
8074 new_ct->mutable_at_most_one()->mutable_literals()->Add(temp_literals_.begin(),
8075 temp_literals_.end());
8079bool ModelCopy::CopyExactlyOne(
const ConstraintProto&
ct) {
8081 temp_literals_.clear();
8082 for (
const int lit :
ct.exactly_one().literals()) {
8084 skipped_non_zero_++;
8087 temp_literals_.push_back(lit);
8091 if (temp_literals_.empty() || num_true > 1)
return false;
8092 if (temp_literals_.size() == 1 && num_true == 1)
return true;
8095 ConstraintProto* new_ct = context_->
working_model->add_constraints();
8096 CopyEnforcementLiterals(
ct, new_ct);
8097 new_ct->mutable_exactly_one()->mutable_literals()->Add(temp_literals_.begin(),
8098 temp_literals_.end());
8102bool ModelCopy::CopyInterval(
const ConstraintProto&
ct,
int c,
8103 bool ignore_names) {
8104 CHECK_EQ(starting_constraint_index_, 0)
8105 <<
"Adding new interval constraints to partially filled model is not "
8107 interval_mapping_[c] = context_->
working_model->constraints_size();
8108 ConstraintProto* new_ct = context_->
working_model->add_constraints();
8110 *new_ct->mutable_enforcement_literal() =
ct.enforcement_literal();
8111 *new_ct->mutable_interval()->mutable_start() =
ct.interval().start();
8112 *new_ct->mutable_interval()->mutable_size() =
ct.interval().size();
8113 *new_ct->mutable_interval()->mutable_end() =
ct.interval().end();
8121void ModelCopy::CopyAndMapNoOverlap(
const ConstraintProto&
ct) {
8124 context_->
working_model->add_constraints()->mutable_no_overlap();
8125 new_ct->mutable_intervals()->Reserve(
ct.no_overlap().intervals().size());
8126 for (
const int index :
ct.no_overlap().intervals()) {
8127 const auto it = interval_mapping_.find(
index);
8128 if (it == interval_mapping_.end())
continue;
8129 new_ct->add_intervals(it->second);
8133void ModelCopy::CopyAndMapNoOverlap2D(
const ConstraintProto&
ct) {
8136 context_->
working_model->add_constraints()->mutable_no_overlap_2d();
8137 new_ct->set_boxes_with_null_area_can_overlap(
8138 ct.no_overlap_2d().boxes_with_null_area_can_overlap());
8140 const int num_intervals =
ct.no_overlap_2d().x_intervals().size();
8141 new_ct->mutable_x_intervals()->Reserve(num_intervals);
8142 new_ct->mutable_y_intervals()->Reserve(num_intervals);
8143 for (
int i = 0; i < num_intervals; ++i) {
8144 const auto x_it = interval_mapping_.find(
ct.no_overlap_2d().x_intervals(i));
8145 if (x_it == interval_mapping_.end())
continue;
8146 const auto y_it = interval_mapping_.find(
ct.no_overlap_2d().y_intervals(i));
8147 if (y_it == interval_mapping_.end())
continue;
8148 new_ct->add_x_intervals(x_it->second);
8149 new_ct->add_y_intervals(y_it->second);
8153void ModelCopy::CopyAndMapCumulative(
const ConstraintProto&
ct) {
8156 context_->
working_model->add_constraints()->mutable_cumulative();
8157 *new_ct->mutable_capacity() =
ct.cumulative().capacity();
8159 const int num_intervals =
ct.cumulative().intervals().size();
8160 new_ct->mutable_intervals()->Reserve(num_intervals);
8161 new_ct->mutable_demands()->Reserve(num_intervals);
8162 for (
int i = 0; i < num_intervals; ++i) {
8163 const auto it = interval_mapping_.find(
ct.cumulative().intervals(i));
8164 if (it == interval_mapping_.end())
continue;
8165 new_ct->add_intervals(it->second);
8166 *new_ct->add_demands() =
ct.cumulative().demands(i);
8170bool ModelCopy::CreateUnsatModel() {
8172 context_->
working_model->add_constraints()->mutable_bool_or();
8185 return context->NotifyThatModelIsUnsat();
8190 if (!in_model.name().empty()) {
8191 context->working_model->set_name(in_model.name());
8193 if (in_model.has_objective()) {
8194 *
context->working_model->mutable_objective() = in_model.objective();
8196 if (in_model.has_floating_point_objective()) {
8197 *
context->working_model->mutable_floating_point_objective() =
8198 in_model.floating_point_objective();
8200 if (!in_model.search_strategy().empty()) {
8201 *
context->working_model->mutable_search_strategy() =
8202 in_model.search_strategy();
8204 if (!in_model.assumptions().empty()) {
8205 *
context->working_model->mutable_assumptions() = in_model.assumptions();
8207 if (in_model.has_symmetry()) {
8208 *
context->working_model->mutable_symmetry() = in_model.symmetry();
8210 if (in_model.has_solution_hint()) {
8211 *
context->working_model->mutable_solution_hint() = in_model.solution_hint();
8220 std::vector<int>* postsolve_mapping) {
8226 std::vector<int>* postsolve_mapping)
8227 : postsolve_mapping_(postsolve_mapping),
8231CpSolverStatus CpModelPresolver::InfeasibleStatus() {
8233 return CpSolverStatus::INFEASIBLE;
8254 context_->
params().keep_all_feasible_solutions_in_presolve() ||
8255 context_->
params().enumerate_all_solutions() ||
8256 context_->
params().fill_tightened_domains_in_response() ||
8258 !context_->
params().cp_model_presolve();
8261 for (
const auto& decision_strategy :
8263 *(context_->
mapping_model->add_search_strategy()) = decision_strategy;
8274 if (context_->
params().cp_model_presolve()) {
8275 for (
int c = 0; c < context_->
working_model->constraints_size(); ++c) {
8276 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
8277 PresolveEnforcementLiteral(
ct);
8278 switch (
ct->constraint_case()) {
8279 case ConstraintProto::kBoolOr:
8282 case ConstraintProto::kBoolAnd:
8283 PresolveBoolAnd(
ct);
8285 case ConstraintProto::kAtMostOne:
8286 PresolveAtMostOne(
ct);
8288 case ConstraintProto::kExactlyOne:
8289 PresolveExactlyOne(
ct);
8291 case ConstraintProto::kLinear:
8292 CanonicalizeLinear(
ct);
8300 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
8307 if (context_->
working_model->has_floating_point_objective()) {
8310 "The floating point objective cannot be scaled with enough "
8312 return CpSolverStatus::MODEL_INVALID;
8331 if (!context_->
params().cp_model_presolve()) {
8333 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
8336 EncodeAllAffineRelations();
8338 return CpSolverStatus::UNKNOWN;
8343 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
8346 if (!PresolveAffineRelationIfAny(
var))
return InfeasibleStatus();
8351 TryToSimplifyDomain(
var);
8352 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
8358 for (
int iter = 0; iter < context_->
params().max_presolve_iterations();
8363 int old_num_non_empty_constraints = 0;
8364 for (
int c = 0; c < context_->
working_model->constraints_size(); ++c) {
8367 if (type == ConstraintProto::CONSTRAINT_NOT_SET)
continue;
8368 old_num_non_empty_constraints++;
8375 PresolveToFixPoint();
8379 ExtractEncodingFromLinear();
8381 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
8385 const int num_vars = context_->
working_model->variables().size();
8386 for (
int var = 0;
var < num_vars; ++
var) {
8395 if (context_->
params().cp_model_probing_level() > 0) {
8398 PresolveToFixPoint();
8405 if (context_->
params().cp_model_use_sat_presolve()) {
8407 PresolvePureSatPart();
8420 const int old_size = context_->
working_model->constraints_size();
8421 for (
int c = 0; c < old_size; ++c) {
8422 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
8423 if (
ct->constraint_case() != ConstraintProto::kLinear)
continue;
8424 ExtractAtMostOneFromLinear(
ct);
8431 if (iter == 0) TransformIntoMaxCliques();
8437 DetectDuplicateConstraints();
8438 DetectDominatedLinearConstraints();
8439 DetectOverlappingColumns();
8441 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
8457 PresolveToFixPoint();
8462 if (context_->
params().max_presolve_iterations() >= 5)
continue;
8465 old_num_non_empty_constraints)) {
8469 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
8472 MergeNoOverlapConstraints();
8473 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
8478 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
8482 EncodeAllAffineRelations();
8483 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
8493 absl::flat_hash_set<int> used_variables;
8494 for (DecisionStrategyProto& strategy :
8496 DecisionStrategyProto copy = strategy;
8497 strategy.clear_variables();
8498 strategy.clear_transformations();
8499 for (
const int ref : copy.variables()) {
8507 if (used_variables.contains(
var))
continue;
8508 used_variables.insert(
var);
8516 if (strategy.variable_selection_strategy() !=
8517 DecisionStrategyProto::CHOOSE_FIRST) {
8518 DecisionStrategyProto::AffineTransformation* t =
8519 strategy.add_transformations();
8520 t->set_index(strategy.variables_size());
8522 t->set_positive_coeff(std::abs(r.
coeff));
8524 strategy.add_variables(rep);
8531 strategy.add_variables(ref);
8537 for (
int i = 0; i < context_->
working_model->variables_size(); ++i) {
8548 postsolve_mapping_->clear();
8549 std::vector<int> mapping(context_->
working_model->variables_size(), -1);
8550 int num_free_variables = 0;
8551 for (
int i = 0; i < context_->
working_model->variables_size(); ++i) {
8552 if (mapping[i] != -1)
continue;
8561 mapping[r] = postsolve_mapping_->size();
8562 postsolve_mapping_->push_back(r);
8574 ++num_free_variables;
8580 mapping[i] = postsolve_mapping_->size();
8581 postsolve_mapping_->push_back(i);
8583 context_->
UpdateRuleStats(absl::StrCat(
"presolve: ", num_free_variables,
8584 " unused variables removed."));
8586 if (context_->
params().permute_variable_randomly()) {
8587 std::shuffle(postsolve_mapping_->begin(), postsolve_mapping_->end(),
8589 for (
int i = 0; i < postsolve_mapping_->size(); ++i) {
8590 mapping[(*postsolve_mapping_)[i]] = i;
8613 const std::string error =
8615 if (!error.empty()) {
8616 SOLVER_LOG(logger_,
"Error while validating postsolved model: ", error);
8617 return CpSolverStatus::MODEL_INVALID;
8622 if (!error.empty()) {
8624 "Error while validating mapping_model model: ", error);
8625 return CpSolverStatus::MODEL_INVALID;
8629 return CpSolverStatus::UNKNOWN;
8638 auto mapping_function = [&mapping](
int* ref) {
8643 for (ConstraintProto& ct_ref : *
proto->mutable_constraints()) {
8649 if (
proto->has_objective()) {
8650 for (
int& mutable_ref : *
proto->mutable_objective()->mutable_vars()) {
8651 mapping_function(&mutable_ref);
8656 for (
int& mutable_ref : *
proto->mutable_assumptions()) {
8657 mapping_function(&mutable_ref);
8662 for (DecisionStrategyProto& strategy : *
proto->mutable_search_strategy()) {
8663 const DecisionStrategyProto copy = strategy;
8664 strategy.clear_variables();
8665 std::vector<int> new_indices(copy.variables().size(), -1);
8666 for (
int i = 0; i < copy.variables().size(); ++i) {
8667 const int ref = copy.variables(i);
8670 new_indices[i] = strategy.variables_size();
8674 strategy.clear_transformations();
8675 for (
const auto& transform : copy.transformations()) {
8676 CHECK_LT(transform.index(), new_indices.size());
8677 const int new_index = new_indices[transform.index()];
8678 if (new_index == -1)
continue;
8679 auto* new_transform = strategy.add_transformations();
8680 *new_transform = transform;
8681 CHECK_LT(new_index, strategy.variables().size());
8682 new_transform->set_index(new_index);
8688 if (
proto->has_solution_hint()) {
8689 absl::flat_hash_set<int> used_vars;
8690 auto* mutable_hint =
proto->mutable_solution_hint();
8692 for (
int i = 0; i < mutable_hint->vars_size(); ++i) {
8693 const int old_ref = mutable_hint->vars(i);
8694 int64_t old_value = mutable_hint->values(i);
8698 if (old_value <
context.MinOf(old_ref)) {
8699 old_value =
context.MinOf(old_ref);
8701 if (old_value >
context.MaxOf(old_ref)) {
8702 old_value =
context.MaxOf(old_ref);
8711 const int image = mapping[
var];
8713 if (!used_vars.insert(image).second)
continue;
8714 mutable_hint->set_vars(new_size, image);
8715 mutable_hint->set_values(new_size,
value);
8720 mutable_hint->mutable_vars()->Truncate(new_size);
8721 mutable_hint->mutable_values()->Truncate(new_size);
8723 proto->clear_solution_hint();
8728 std::vector<IntegerVariableProto> new_variables;
8729 for (
int i = 0; i < mapping.size(); ++i) {
8730 const int image = mapping[i];
8731 if (image < 0)
continue;
8732 if (image >= new_variables.size()) {
8733 new_variables.resize(image + 1, IntegerVariableProto());
8735 new_variables[image].Swap(
proto->mutable_variables(i));
8737 proto->clear_variables();
8738 for (IntegerVariableProto& proto_ref : new_variables) {
8739 proto->add_variables()->Swap(&proto_ref);
8743 for (
const IntegerVariableProto& v :
proto->variables()) {
8750ConstraintProto CopyConstraintForDuplicateDetection(
const ConstraintProto&
ct) {
8751 ConstraintProto copy =
ct;
8753 if (
ct.constraint_case() == ConstraintProto::kLinear) {
8754 copy.mutable_linear()->clear_domain();
8760ConstraintProto CopyObjectiveForDuplicateDetection(
8761 const CpObjectiveProto& objective) {
8762 ConstraintProto copy;
8763 *copy.mutable_linear()->mutable_vars() = objective.vars();
8764 *copy.mutable_linear()->mutable_coeffs() = objective.coeffs();
8772 std::vector<std::pair<int, int>> result;
8775 ConstraintProto copy;
8777 absl::flat_hash_map<uint64_t, int> equiv_constraints;
8781 copy = CopyObjectiveForDuplicateDetection(
model_proto.objective());
8782 s = copy.SerializeAsString();
8786 const int num_constraints =
model_proto.constraints().size();
8787 for (
int c = 0; c < num_constraints; ++c) {
8788 const auto type =
model_proto.constraints(c).constraint_case();
8789 if (type == ConstraintProto::CONSTRAINT_NOT_SET)
continue;
8793 if (type == ConstraintProto::kInterval)
continue;
8798 copy = CopyConstraintForDuplicateDetection(
model_proto.constraints(c));
8799 s = copy.SerializeAsString();
8801 const uint64_t
hash = absl::Hash<std::string>()(s);
8802 const auto [it, inserted] = equiv_constraints.insert({
hash, c});
8805 const int other_c_with_same_hash = it->second;
8807 ? CopyObjectiveForDuplicateDetection(
model_proto.objective())
8808 : CopyConstraintForDuplicateDetection(
8810 if (s == copy.SerializeAsString()) {
8811 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].
static Domain AllValues()
Returns the full domain Int64.
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 ContinuousMultiplicationBy(int64_t coeff) const
Returns a superset of MultiplicationBy() to avoid the explosion in the representation size.
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 SquareSuperset() const
Returns a superset of {x ∈ Int64, ∃ y ∈ D, x = y * y }.
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
void Set(IntegerType index)
const std::vector< IntegerType > & PositionsSetAtLeastOnce() 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.
CpSolverStatus Presolve()
void RemoveEmptyConstraints()
CpModelPresolver(PresolveContext *context, std::vector< int > *postsolve_mapping)
bool PresolveOneConstraint(int c)
std::vector< std::pair< int, Domain > > ProcessClause(absl::Span< const int > clause)
void MarkProcessingAsDoneForNow()
int NumDeductions() const
void AddDeduction(int literal_ref, int var, Domain domain)
ModelCopy(PresolveContext *context)
bool ImportAndSimplifyConstraints(const CpModelProto &in_model, const std::vector< int > &ignored_constraints, bool first_copy=false)
void ImportVariablesAndMaybeIgnoreNames(const CpModelProto &in_model)
bool VariableIsRemovable(int ref) const
int64_t MaxOf(int ref) const
bool CanonicalizeAffineVariable(int ref, int64_t coeff, int64_t mod, int64_t rhs)
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
ABSL_MUST_USE_RESULT bool CanonicalizeOneObjectiveVariable(int var)
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)
SparseBitset< int > modified_domains
Domain DomainOf(int ref) const
int64_t num_presolve_operations
void InitializeNewDomains()
int NewIntVar(const Domain &domain)
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)
SparseBitset< int > var_with_reduced_small_degree
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
const std::vector< std::vector< int > > & ConstraintToVarsGraph() 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
CpModelProto const * model_proto
ModelSharedTimeLimit * time_limit
GurobiMPCallbackContext * context
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
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)
int64_t ClosestMultiple(int64_t value, int64_t base)
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)
std::string ValidateCpModel(const CpModelProto &model, bool after_presolve)
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)
std::vector< std::pair< int, int > > FindDuplicateConstraints(const CpModelProto &model_proto)
int64_t FloorSquareRoot(int64_t a)
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)
InclusionDetector(const Storage &storage) -> InclusionDetector< Storage >
constexpr int kObjectiveConstraint
bool ImportModelWithBasicPresolveIntoContext(const CpModelProto &in_model, PresolveContext *context)
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)
bool LinearInequalityCanBeReducedWithClosestMultiple(int64_t base, const std::vector< int64_t > &coeffs, const std::vector< int64_t > &lbs, const std::vector< int64_t > &ubs, int64_t rhs, int64_t *new_rhs)
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)
std::optional< int64_t > end
#define SOLVER_LOG(logger,...)